Typo fix in comment.
[BearSSL] / src / x509 / x509_minimal.t0
index bdb3e18..50995dc 100644 (file)
@@ -106,7 +106,7 @@ preamble {
  *     -- Extensions: extension values are processed in due order.
  *
  *        -- Basic Constraints: for all certificates except EE, must be
- *        present, indicate a CA, and have a path legnth compatible with
+ *        present, indicate a CA, and have a path length compatible with
  *        the chain length so far.
  *
  *        -- Key Usage: for the EE, if present, must allow signatures
@@ -149,20 +149,6 @@ preamble {
  *  then validation is reported as failed.
  */
 
-#ifndef BR_USE_UNIX_TIME
-#if defined __unix__ || defined __linux__ \
-       || defined _POSIX_SOURCE || defined _POSIX_C_SOURCE \
-       || (defined __APPLE__ && defined __MACH__)
-#define BR_USE_UNIX_TIME   1
-#endif
-#endif
-
-#ifndef BR_USE_WIN32_TIME
-#if defined _WIN32 || defined _WIN64
-#define BR_USE_WIN32_TIME   1
-#endif
-#endif
-
 #if BR_USE_UNIX_TIME
 #include <time.h>
 #endif
@@ -171,8 +157,13 @@ preamble {
 #include <windows.h>
 #endif
 
+/*
+ * The T0 compiler will produce these prototypes declarations in the
+ * header.
+ *
 void br_x509_minimal_init_main(void *ctx);
 void br_x509_minimal_run(void *ctx);
+ */
 
 /* see bearssl_x509.h */
 void
@@ -188,19 +179,22 @@ br_x509_minimal_init(br_x509_minimal_context *ctx,
 }
 
 static void
-xm_start_chain(const br_x509_class **ctx,
-       unsigned expected_key_type, const char *server_name)
+xm_start_chain(const br_x509_class **ctx, const char *server_name)
 {
        br_x509_minimal_context *cc;
+       size_t u;
 
-       cc = (br_x509_minimal_context *)ctx;
+       cc = (br_x509_minimal_context *)(void *)ctx;
+       for (u = 0; u < cc->num_name_elts; u ++) {
+               cc->name_elts[u].status = 0;
+               cc->name_elts[u].buf[0] = 0;
+       }
        memset(&cc->pkey, 0, sizeof cc->pkey);
        cc->num_certs = 0;
        cc->err = 0;
        cc->cpu.dp = cc->dp_stack;
        cc->cpu.rp = cc->rp_stack;
        br_x509_minimal_init_main(&cc->cpu);
-       cc->expected_key_type = expected_key_type;
        if (server_name == NULL || *server_name == 0) {
                cc->server_name = NULL;
        } else {
@@ -213,7 +207,7 @@ xm_start_cert(const br_x509_class **ctx, uint32_t length)
 {
        br_x509_minimal_context *cc;
 
-       cc = (br_x509_minimal_context *)ctx;
+       cc = (br_x509_minimal_context *)(void *)ctx;
        if (cc->err != 0) {
                return;
        }
@@ -229,7 +223,7 @@ xm_append(const br_x509_class **ctx, const unsigned char *buf, size_t len)
 {
        br_x509_minimal_context *cc;
 
-       cc = (br_x509_minimal_context *)ctx;
+       cc = (br_x509_minimal_context *)(void *)ctx;
        if (cc->err != 0) {
                return;
        }
@@ -243,7 +237,7 @@ xm_end_cert(const br_x509_class **ctx)
 {
        br_x509_minimal_context *cc;
 
-       cc = (br_x509_minimal_context *)ctx;
+       cc = (br_x509_minimal_context *)(void *)ctx;
        if (cc->err == 0 && cc->cert_length != 0) {
                cc->err = BR_ERR_X509_TRUNCATED;
        }
@@ -255,7 +249,7 @@ xm_end_chain(const br_x509_class **ctx)
 {
        br_x509_minimal_context *cc;
 
-       cc = (br_x509_minimal_context *)ctx;
+       cc = (br_x509_minimal_context *)(void *)ctx;
        if (cc->err == 0) {
                if (cc->num_certs == 0) {
                        cc->err = BR_ERR_X509_EMPTY_CHAIN;
@@ -269,15 +263,18 @@ xm_end_chain(const br_x509_class **ctx)
 }
 
 static const br_x509_pkey *
-xm_get_pkey(const br_x509_class *const *ctx)
+xm_get_pkey(const br_x509_class *const *ctx, unsigned *usages)
 {
        br_x509_minimal_context *cc;
 
-       cc = (br_x509_minimal_context *)ctx;
+       cc = (br_x509_minimal_context *)(void *)ctx;
        if (cc->err == BR_ERR_X509_OK
                || cc->err == BR_ERR_X509_NOT_TRUSTED)
        {
-               return &((br_x509_minimal_context *)ctx)->pkey;
+               if (usages != NULL) {
+                       *usages = cc->key_usages;
+               }
+               return &((br_x509_minimal_context *)(void *)ctx)->pkey;
        } else {
                return NULL;
        }
@@ -294,7 +291,7 @@ const br_x509_class br_x509_minimal_vtable = {
        xm_get_pkey
 };
 
-#define CTX   ((br_x509_minimal_context *)((unsigned char *)t0ctx - offsetof(br_x509_minimal_context, cpu)))
+#define CTX   ((br_x509_minimal_context *)(void *)((unsigned char *)t0ctx - offsetof(br_x509_minimal_context, cpu)))
 #define CONTEXT_NAME   br_x509_minimal_context
 
 #define DNHASH_LEN   ((CTX->dn_hash_impl->desc >> BR_HASHDESC_OUT_OFF) & BR_HASHDESC_OUT_MASK)
@@ -335,6 +332,42 @@ eqbigint(const unsigned char *b1, size_t len1,
        return memcmp(b1, b2, len1) == 0;
 }
 
+/*
+ * Compare two strings for equality, in a case-insensitive way. This
+ * function handles casing only for ASCII letters.
+ */
+static int
+eqnocase(const void *s1, const void *s2, size_t len)
+{
+       const unsigned char *buf1, *buf2;
+
+       buf1 = s1;
+       buf2 = s2;
+       while (len -- > 0) {
+               int x1, x2;
+
+               x1 = *buf1 ++;
+               x2 = *buf2 ++;
+               if (x1 >= 'A' && x1 <= 'Z') {
+                       x1 += 'a' - 'A';
+               }
+               if (x2 >= 'A' && x2 <= 'Z') {
+                       x2 += 'a' - 'A';
+               }
+               if (x1 != x2) {
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+static int verify_signature(br_x509_minimal_context *ctx,
+       const br_x509_pkey *pk);
+
+}
+
+postamble {
+
 /*
  * Verify the signature on the certificate with the provided public key.
  * This function checks the public key type with regards to the expected
@@ -384,35 +417,6 @@ verify_signature(br_x509_minimal_context *ctx, const br_x509_pkey *pk)
        }
 }
 
-/*
- * Compare two strings for equality, in a case-insensitive way. This
- * function handles casing only for ASCII letters.
- */
-static int
-eqnocase(const void *s1, const void *s2, size_t len)
-{
-       const unsigned char *buf1, *buf2;
-
-       buf1 = s1;
-       buf2 = s2;
-       while (len -- > 0) {
-               int x1, x2;
-
-               x1 = *buf1 ++;
-               x2 = *buf2 ++;
-               if (x1 >= 'A' && x1 <= 'Z') {
-                       x1 += 'a' - 'A';
-               }
-               if (x2 >= 'A' && x2 <= 'Z') {
-                       x2 += 'a' - 'A';
-               }
-               if (x1 != x2) {
-                       return 0;
-               }
-       }
-       return 1;
-}
-
 }
 
 cc: read8-low ( -- x ) {
@@ -472,7 +476,7 @@ cc: zero-server-name ( -- bool ) {
        T0_PUSHi(-(CTX->server_name == NULL));
 }
 
-addr: expected_key_type
+addr: key_usages
 addr: cert_sig
 addr: cert_sig_len
 addr: cert_signer_key_type
@@ -522,17 +526,124 @@ addr: current_dn_hash
 addr: next_dn_hash
 addr: saved_dn_hash
 
-\ Read a DN. The normalized DN hash is computed and stored in the
-\ current_dn_hash. The Common Name is also extracted to the pad, if
-\ it is present and small enough (255 bytes at most); the CN length is
-\ then written in pad[0]. If these conditions are not met, then pad[0]
-\ is set to 0.
+\ Read a DN, hashing it into current_dn_hash. The DN contents are not
+\ inspected (only the outer tag, for SEQUENCE, is checked).
 : read-DN ( lim -- lim )
-       \ Activate DN hashing.
        start-dn-hash
+       read-sequence-open skip-close-elt
+       compute-dn-hash ;
+
+cc: offset-name-element ( san -- n ) {
+       unsigned san = T0_POP();
+       size_t u;
+
+       for (u = 0; u < CTX->num_name_elts; u ++) {
+               if (CTX->name_elts[u].status == 0) {
+                       const unsigned char *oid;
+                       size_t len, off;
+
+                       oid = CTX->name_elts[u].oid;
+                       if (san) {
+                               if (oid[0] != 0 || oid[1] != 0) {
+                                       continue;
+                               }
+                               off = 2;
+                       } else {
+                               off = 0;
+                       }
+                       len = oid[off];
+                       if (len != 0 && len == CTX->pad[0]
+                               && memcmp(oid + off + 1,
+                                       CTX->pad + 1, len) == 0)
+                       {
+                               T0_PUSH(u);
+                               T0_RET();
+                       }
+               }
+       }
+       T0_PUSHi(-1);
+}
+
+cc: copy-name-element ( bool offbuf -- ) {
+       size_t len;
+       int32_t off = T0_POPi();
+       int ok = T0_POPi();
+
+       if (off >= 0) {
+               br_name_element *ne = &CTX->name_elts[off];
+
+               if (ok) {
+                       len = CTX->pad[0];
+                       if (len < ne->len) {
+                               memcpy(ne->buf, CTX->pad + 1, len);
+                               ne->buf[len] = 0;
+                               ne->status = 1;
+                       } else {
+                               ne->status = -1;
+                       }
+               } else {
+                       ne->status = -1;
+               }
+       }
+}
+
+cc: copy-name-SAN ( bool tag -- ) {
+       unsigned tag = T0_POP();
+       unsigned ok = T0_POP();
+       size_t u, len;
+
+       len = CTX->pad[0];
+       for (u = 0; u < CTX->num_name_elts; u ++) {
+               br_name_element *ne;
+
+               ne = &CTX->name_elts[u];
+               if (ne->status == 0 && ne->oid[0] == 0 && ne->oid[1] == tag) {
+                       if (ok && ne->len > len) {
+                               memcpy(ne->buf, CTX->pad + 1, len);
+                               ne->buf[len] = 0;
+                               ne->status = 1;
+                       } else {
+                               ne->status = -1;
+                       }
+                       break;
+               }
+       }
+}
 
-       \ Prepare pad.
-       0 addr-pad set8
+\ Read a value, decoding string types. If the string type is recognised
+\ and the value could be converted to UTF-8 into the pad, then true (-1)
+\ is returned; in all other cases, false (0) is returned. Either way, the
+\ object is consumed.
+: read-string ( lim -- lim bool )
+       read-tag case
+               \ UTF8String
+               12 of check-primitive read-value-UTF8 endof
+               \ NumericString
+               18 of check-primitive read-value-latin1 endof
+               \ PrintableString
+               19 of check-primitive read-value-latin1 endof
+               \ TeletexString
+               20 of check-primitive read-value-latin1 endof
+               \ IA5String
+               22 of check-primitive read-value-latin1 endof
+               \ BMPString
+               30 of check-primitive read-value-UTF16 endof
+               2drop read-length-skip 0 0
+       endcase ;
+
+\ Read a DN for the EE. The normalized DN hash is computed and stored in the
+\ current_dn_hash.
+\ Name elements are gathered. Also, the Common Name is matched against the
+\ intended server name.
+\ Returned value is true (-1) if the CN matches the intended server name,
+\ false (0) otherwise.
+: read-DN-EE ( lim -- lim bool )
+       \ Flag will be set to true if there is a CN and it matches the
+       \ intended server name.
+       0 { eename-matches }
+
+       \ Activate DN hashing.
+       start-dn-hash
 
        \ Parse the DN structure: it is a SEQUENCE of SET of
        \ AttributeTypeAndValue. Each AttributeTypeAndValue is a
@@ -547,44 +658,45 @@ addr: saved_dn_hash
                        dup while
 
                        read-sequence-open
-                       \ We want to recognize the OID for Common Name,
-                       \ but we don't want to use read-OID because we
-                       \ need to preserve the pad contents. Instead, we
-                       \ use the fact that the encoding for the value of
-                       \ id-at-commonName is 55 04 03 (three bytes).
-                       read-tag 0x06 check-tag-primitive read-length-open-elt
-                       dup 3 = if
-                               read8 16 << { tmp }
-                               read8 8 << tmp + >tmp
-                               read8 tmp +
-                               0x550403 = { isCN }
-                       then
-                       skip-close-elt
-                       
-                       \ If this is a Common Name, then we want to copy
-                       \ it to the pad, but only if it uses a mono-byte
-                       \ encoding (Printable, Teletex or UTF-8).
-                       isCN if
-                               read-tag
-                               dup dup 0x0C = swap 0x13 = or swap 0x14 = or if
-                                       check-primitive
-                                       read-small-value drop
-                                       close-elt
-                               else
-                                       drop
-                                       0 addr-pad set8
-                                       skip-close-elt
+
+                       \ Read the OID. If the OID could not be read (too
+                       \ long) then the first pad byte will be 0.
+                       read-OID drop
+
+                       \ If it is the Common Name then we'll need to
+                       \ match it against the intended server name (if
+                       \ applicable).
+                       id-at-commonName eqOID { isCN }
+
+                       \ Get offset for reception buffer for that element
+                       \ (or -1).
+                       0 offset-name-element { offbuf }
+
+                       \ Try to read the value as a string.
+                       read-string
+
+                       \ If the value could be decoded as a string,
+                       \ copy it and/or match it, as appropriate.
+                       dup isCN and if
+                               match-server-name if
+                                       -1 >eename-matches
                                then
-                       else
-                               skip-close-elt
                        then
+                       offbuf copy-name-element
+
+                       \ Close the SEQUENCE
+                       close-elt
+
                repeat
                close-elt
        repeat
        close-elt
 
        \ Compute DN hash and deactivate DN hashing.
-       compute-dn-hash ;
+       compute-dn-hash
+
+       \ Return the CN match flag.
+       eename-matches ;
 
 \ Get the validation date and time from the context or system.
 cc: get-system-date ( -- days seconds ) {
@@ -705,7 +817,7 @@ cc: check-direct-trust ( -- ) {
                if (ta->flags & BR_X509_TA_CA) {
                        continue;
                }
-               hash_dn(CTX, ta->dn, ta->dn_len, hashed_DN);
+               hash_dn(CTX, ta->dn.data, ta->dn.len, hashed_DN);
                if (memcmp(hashed_DN, CTX->current_dn_hash, DNHASH_LEN)) {
                        continue;
                }
@@ -765,7 +877,7 @@ cc: check-trust-anchor-CA ( -- ) {
                if (!(ta->flags & BR_X509_TA_CA)) {
                        continue;
                }
-               hash_dn(CTX, ta->dn, ta->dn_len, hashed_DN);
+               hash_dn(CTX, ta->dn.data, ta->dn.len, hashed_DN);
                if (memcmp(hashed_DN, CTX->saved_dn_hash, DNHASH_LEN)) {
                        continue;
                }
@@ -853,9 +965,13 @@ cc: printOID ( -- ) {
 }
 
 \ Extensions with specific processing.
-OID: basicConstraints    2.5.29.19
-OID: keyUsage            2.5.29.15
-OID: subjectAltName      2.5.29.17
+OID: basicConstraints      2.5.29.19
+OID: keyUsage              2.5.29.15
+OID: subjectAltName        2.5.29.17
+OID: certificatePolicies   2.5.29.32
+
+\ Policy qualifier "pointer to CPS"
+OID: id-qt-cps             1.3.6.1.5.5.7.2.1
 
 \ Extensions which are ignored when encountered, even if critical.
 OID: authorityKeyIdentifier        2.5.29.35
@@ -892,20 +1008,14 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
 
 \ Process a Key Usage extension.
 \ For the EE certificate:
-\   -- if the expected key usage is "key exchange", then the extension
-\      must contain either keyEncipherment (2) or dataEncipherment (3);
-\   -- if the expected key usage is "signature", then the extension
-\      must contain either digitalSignature (0) or nonRepudiation (1).
+\   -- if the key usage contains keyEncipherment (2), dataEncipherment (3)
+\      or keyAgreement (4), then the "key exchange" usage is allowed;
+\   -- if the key usage contains digitalSignature (0) or nonRepudiation (1),
+\      then the "signature" usage is allowed.
 \ For CA certificates, the extension must contain keyCertSign (5).
 : process-keyUsage ( lim ee -- lim )
-       \ Compute flags, depending on EE status and expected key usage.
-       \ This is a mask of bits in the first byte.
-       if
-               addr-expected_key_type get8 0x10 and if 0x30 else 0xC0 then
-       else
-               0x04
-       then
-       { mask }
+       { ee }
+
        \ Read tag for the BIT STRING and open it.
        read-tag 0x03 check-tag-primitive
        read-length-open-elt
@@ -919,9 +1029,65 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
                1 of read8 ign >> ign << endof
                drop read8 0
        endcase
-       mask and ifnot ERR_X509_FORBIDDEN_KEY_USAGE fail then
+
+       \ Check bits.
+       ee if
+               \ EE: get usages.
+               0
+               over 0x38 and if 0x10 or then
+               swap 0xC0 and if 0x20 or then
+               addr-key_usages set8
+       else
+               \ Not EE: keyCertSign must be set.
+               0x04 and ifnot ERR_X509_FORBIDDEN_KEY_USAGE fail then
+       then
+
+       \ We don't care about subsequent bytes.
        skip-close-elt ;
 
+\ Process a Certificate Policies extension.
+\
+\ Since we don't actually support full policies processing, this function
+\ only checks that the extension contents can be safely ignored. Indeed,
+\ we don't validate against a specific set of policies (in RFC 5280
+\ terminology, user-initial-policy-set only contains the special value
+\ any-policy). Moreover, we don't support policy constraints (if a
+\ critical Policy Constraints extension is encountered, the validation
+\ will fail). Therefore, we can safely ignore the contents of this
+\ extension, except if it is critical AND one of the policy OID has a
+\ qualifier which is distinct from id-qt-cps (because id-qt-cps is
+\ specially designated by RFC 5280 has having no mandated action).
+\
+\ This function is called only if the extension is critical.
+: process-certPolicies ( lim -- lim )
+       \ Extension value is a SEQUENCE OF PolicyInformation.
+       read-sequence-open
+       begin dup while
+               \ PolicyInformation ::= SEQUENCE {
+               \    policyIdentifier  OBJECT IDENTIFIER,
+               \    policyQualifiers  SEQUENCE OF PolicyQualifierInfo OPTIONAL
+               \ }
+               read-sequence-open
+               read-OID drop
+               dup if
+                       read-sequence-open
+                       begin dup while
+                               \ PolicyQualifierInfo ::= SEQUENCE {
+                               \    policyQualifierId  OBJECT IDENTIFIER,
+                               \    qualifier          ANY
+                               \ }
+                               read-sequence-open
+                               read-OID drop id-qt-cps eqOID ifnot
+                                       ERR_X509_CRITICAL_EXTENSION fail
+                               then
+                               skip-close-elt
+                       repeat
+                       close-elt
+               then
+               close-elt
+       repeat
+       close-elt ;
+
 \ Process a Subject Alt Name extension. Returned value is a boolean set
 \ to true if the expected server name was matched against a dNSName in
 \ the extension.
@@ -929,15 +1095,55 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
        0 { m }
        read-sequence-open
        begin dup while
+               \ Read the tag. If the tag is context-0, then parse an
+               \ 'otherName'. If the tag is context-2, then parse a
+               \ dNSName. If the tag is context-1 or context-6,
+               \ parse 
+               read-tag case
+                       \ OtherName
+                       0x20 of
+                               \ OtherName ::= SEQUENCE {
+                               \     type-id   OBJECT IDENTIFIER,
+                               \     value     [0] EXPLICIT ANY
+                               \ }
+                               check-constructed read-length-open-elt
+                               read-OID drop
+                               -1 offset-name-element { offbuf }
+                               read-tag 0x20 check-tag-constructed
+                               read-length-open-elt
+                               read-string offbuf copy-name-element
+                               close-elt
+                               close-elt
+                       endof
+                       \ rfc822Name (IA5String)
+                       0x21 of
+                               check-primitive
+                               read-value-UTF8 1 copy-name-SAN
+                       endof
+                       \ dNSName (IA5String)
+                       0x22 of
+                               check-primitive
+                               read-value-UTF8
+                               dup if match-server-name m or >m then
+                               2 copy-name-SAN
+                       endof
+                       \ uniformResourceIdentifier (IA5String)
+                       0x26 of
+                               check-primitive
+                               read-value-UTF8 6 copy-name-SAN
+                       endof
+                       2drop read-length-skip 0
+               endcase
+
                \ We check only names of type dNSName; they use IA5String,
                \ which is basically ASCII.
-               read-tag 0x22 = if
-                       check-primitive
-                       read-small-value drop
-                       match-server-name m or >m
-               else
-                       drop read-length-skip
-               then
+               read-tag 0x22 = if
+               \       check-primitive
+               \       read-small-value drop
+               \       match-server-name m or >m
+               else
+               \       drop read-length-skip
+               then
        repeat
        close-elt
        m ;
@@ -988,14 +1194,14 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
        close-elt
 
        \ Subject name.
-       read-DN
        ee if
                \ For the EE, we must check whether the Common Name, if
                \ any, matches the expected server name.
-               match-server-name { eename }
+               read-DN-EE { eename }
        else
                \ For a non-EE certificate, the hashed subject DN must match
                \ the saved hashed issuer DN from the previous certificate.
+               read-DN
                addr-current_dn_hash addr-saved_dn_hash dn-hash-length eqblob
                ifnot ERR_X509_DN_MISMATCH fail then
        then
@@ -1070,15 +1276,8 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
 
        \ Process public key.
        ee if
-               \ For the EE certificate, check that the key type
-               \ matches that which was expected, then copy the
-               \ data to the relevant buffer.
-               addr-expected_key_type get8 0x0F and
-               dup if
-                       pkey-type = ifnot ERR_X509_WRONG_KEY_TYPE fail then
-               else
-                       drop
-               then
+               \ For the EE certificate, copy the key data to the
+               \ relevant buffer.
                pkey-type case
                        KEYTYPE_RSA of nlen elen copy-ee-rsa-pkey endof
                        KEYTYPE_EC of curve qlen copy-ee-ec-pkey endof
@@ -1143,6 +1342,18 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
                                        then
                                enduf
 
+                               \ We don't implement full processing of
+                               \ policies. The call below mostly checks
+                               \ that the contents of the Certificate
+                               \ Policies extension can be safely ignored.
+                               certificatePolicies eqOID uf
+                                       critical if
+                                               process-certPolicies
+                                       else
+                                               skip-remaining
+                                       then
+                               enduf
+
                                \ Extensions which are always ignored,
                                \ even if critical.
                                authorityKeyIdentifier     eqOID uf
@@ -1286,6 +1497,9 @@ OID: subjectInfoAccess             1.3.6.1.5.5.7.1.11
        check-trust-anchor-CA ;
 
 : main
+       \ Unless restricted by a Key Usage extension, all usages are
+       \ deemed allowed.
+       0x30 addr-key_usages set8
        -1 decode-certificate
        co
        begin