X-Git-Url: https://bearssl.org/gitweb//home/git/?p=BearSSL;a=blobdiff_plain;f=src%2Fx509%2Fx509_minimal.t0;h=1e60016dcd713e279ce48f0409704ba395ab9368;hp=bdb3e18c6ad47840e758cb3a3340d6383c1bb590;hb=af9c79a0710a45361f9ae4313f8bb5bf738c3b7a;hpb=3210f38e0491b39aec1ef419cb4114e9483089fb diff --git a/src/x509/x509_minimal.t0 b/src/x509/x509_minimal.t0 index bdb3e18..1e60016 100644 --- a/src/x509/x509_minimal.t0 +++ b/src/x509/x509_minimal.t0 @@ -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 #endif @@ -171,8 +157,13 @@ preamble { #include #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