--- sys/src/libsec/port/x509.c
+++ sys/src/libsec/port/x509.c
@@ -2795,7 +2795,86 @@ enum {
MAXVERIFY = 100,
};
+static int
+allDigit(char *s, int n)
+{
+ int i;
+
+ for(i = 0; i < n; i++)
+ if(s[i] < '0' || s[i] > '9')
+ return 0;
+ return 1;
+}
+
/*
+ * asn1time_to_sec: parse an ASN.1 time string into seconds since epoch.
+ * Accepts UTCTime "YYMMDDHHMMSSZ" (RFC 5280 Section 4.1.2.5: YY < 50 is 20YY,
+ * else 19YY) or GeneralizedTime "YYYYMMDDHHMMSSZ". Z (UTC) only, which
+ * is all RFC 5280 permits in X.509. Returns -1 on parse failure.
+ */
+static long
+asn1time_to_sec(char *s)
+{
+ Tm tm;
+ int n, y;
+
+ if(s == nil)
+ return -1;
+ memset(&tm, 0, sizeof tm);
+ strcpy(tm.zone, "GMT");
+ n = strlen(s);
+ if(n == 13 && s[12] == 'Z'){
+ if(!allDigit(s, 12))
+ return -1;
+ y = (s[0]-'0')*10 + (s[1]-'0');
+ tm.year = (y < 50 ? 100 + y : y);
+ tm.mon = (s[2]-'0')*10 + (s[3]-'0') - 1;
+ tm.mday = (s[4]-'0')*10 + (s[5]-'0');
+ tm.hour = (s[6]-'0')*10 + (s[7]-'0');
+ tm.min = (s[8]-'0')*10 + (s[9]-'0');
+ tm.sec = (s[10]-'0')*10 + (s[11]-'0');
+ }else if(n == 15 && s[14] == 'Z'){
+ if(!allDigit(s, 14))
+ return -1;
+ y = (s[0]-'0')*1000 + (s[1]-'0')*100
+ + (s[2]-'0')*10 + (s[3]-'0');
+ tm.year = y - 1900;
+ tm.mon = (s[4]-'0')*10 + (s[5]-'0') - 1;
+ tm.mday = (s[6]-'0')*10 + (s[7]-'0');
+ tm.hour = (s[8]-'0')*10 + (s[9]-'0');
+ tm.min = (s[10]-'0')*10 + (s[11]-'0');
+ tm.sec = (s[12]-'0')*10 + (s[13]-'0');
+ }else
+ return -1;
+ /* Reject obviously-bogus dates so tm2sec can't roll over silently. */
+ if(tm.mon < 0 || tm.mon > 11
+ || tm.mday < 1 || tm.mday > 31
+ || tm.hour > 23 || tm.min > 59 || tm.sec > 60) /* 60: leap second */
+ return -1;
+ return tm2sec(&tm);
+}
+
+/*
+ * cert_validity_check: compare [notBefore, notAfter] of a decoded cert
+ * against `now`. Returns nil on OK or a short error string.
+ */
+static char*
+cert_validity_check(CertX509 *c, long now)
+{
+ long nb, na;
+
+ nb = asn1time_to_sec(c->validity_start);
+ na = asn1time_to_sec(c->validity_end);
+ if(nb < 0 || na < 0)
+ return "cert has unparseable validity dates";
+ if(now < nb)
+ return "cert not yet valid";
+ if(now > na)
+ return "cert expired";
+ return nil;
+}
+
+/*
* Recursive DFS path-builder. `cert` is the cert at the head of the
* partially-built path (depth 0 = leaf). Try every entry in `pool`
* and `roots` whose subject matches cert->issuer and whose key
@@ -2819,7 +2898,7 @@ verify_chain_dfs(CertX509 *cert, uchar *cert_pem, int
verify_chain_dfs(CertX509 *cert, uchar *cert_pem, int cert_pemlen,
PEMChain *pool, PEMChain *roots,
PEMChain **visited, int nvisited,
- int depth, int *budget)
+ int depth, int *budget, long now)
{
PEMChain *cands[2];
PEMChain *p;
@@ -2831,6 +2910,10 @@ verify_chain_dfs(CertX509 *cert, uchar *cert_pem, int
if(*budget <= 0)
return "signature-verify budget exhausted";
+ err = cert_validity_check(cert, now);
+ if(err != nil)
+ return err;
+
err = "no path to a trust anchor";
cands[0] = pool; /* intermediates first */
cands[1] = roots; /* trust anchors last */
@@ -2883,7 +2966,7 @@ verify_chain_dfs(CertX509 *cert, uchar *cert_pem, int
visited[nvisited] = p;
suberr = verify_chain_dfs(parent, p->pem, p->pemlen,
pool, roots, visited, nvisited+1,
- depth+1, budget);
+ depth+1, budget, now);
freecert(parent);
if(suberr == nil)
return nil;
@@ -2909,13 +2992,15 @@ verify_chain_dfs(CertX509 *cert, uchar *cert_pem, int
* candidate parents are present (e.g. a cross-signed intermediate), each is
* tried in turn and a dead-end branch unwinds to try the next candidate.
*
- * Also calls X509matchhostname on the leaf when `hostname` is non-nil/empty.
+ * Also calls X509matchhostname on the leaf when `hostname` is non-nil/empty,
+ * and checks the validity window (notBefore/notAfter) of every cert traversed
+ * during path discovery (RFC 5280 Section 6.1.3(a)(2)).
*
* Bounded by MAXHOPS chain length and MAXVERIFY total signature checks.
*
- * BUG: does not currently check validity dates, basicConstraints CA:TRUE,
- * keyUsage/extKeyUsage, or nameConstraints. The cryptographic chain and
- * hostname binding are enforced.
+ * BUG: does not currently check basicConstraints CA:TRUE, keyUsage /
+ * extKeyUsage, or nameConstraints. Cryptographic chain, hostname binding,
+ * and validity window are enforced.
*
* Returns nil on success, error string on failure.
*/
@@ -2927,6 +3012,7 @@ X509verifychain(PEMChain *chain, PEMChain *roots, char
CertX509 *leaf;
PEMChain *visited[MAXHOPS+1];
int budget;
+ long now;
if(chain == nil || chain->pem == nil)
return "empty chain";
@@ -2945,10 +3031,11 @@ X509verifychain(PEMChain *chain, PEMChain *roots, char
if(leaf == nil)
return "cannot decode leaf cert";
+ now = time(0);
visited[0] = chain;
budget = MAXVERIFY;
err = verify_chain_dfs(leaf, chain->pem, chain->pemlen,
- chain->next, roots, visited, 1, 0, &budget);
+ chain->next, roots, visited, 1, 0, &budget, now);
freecert(leaf);
return err;
}
|