Plan 9 from Bell Labs’s /usr/web/sources/contrib/mospak/tls-1.2/libsec-x509-validity-dates.diff

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


--- 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;
 }

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.