* 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
#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
}
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 {
{
br_x509_minimal_context *cc;
- cc = (br_x509_minimal_context *)ctx;
+ cc = (br_x509_minimal_context *)(void *)ctx;
if (cc->err != 0) {
return;
}
{
br_x509_minimal_context *cc;
- cc = (br_x509_minimal_context *)ctx;
+ cc = (br_x509_minimal_context *)(void *)ctx;
if (cc->err != 0) {
return;
}
{
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;
}
{
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;
}
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;
}
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)
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
}
}
-/*
- * 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 ) {
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
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
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 ) {
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;
}
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;
}
}
\ 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
\ 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
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.
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 ;
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
\ 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
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
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