\ Copyright (c) 2016 Thomas Pornin \ \ Permission is hereby granted, free of charge, to any person obtaining \ a copy of this software and associated documentation files (the \ "Software"), to deal in the Software without restriction, including \ without limitation the rights to use, copy, modify, merge, publish, \ distribute, sublicense, and/or sell copies of the Software, and to \ permit persons to whom the Software is furnished to do so, subject to \ the following conditions: \ \ The above copyright notice and this permission notice shall be \ included in all copies or substantial portions of the Software. \ \ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, \ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF \ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND \ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS \ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN \ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN \ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE \ SOFTWARE. preamble { #include "inner.h" #define CTX ((br_x509_decoder_context *)((unsigned char *)t0ctx - offsetof(br_x509_decoder_context, cpu))) #define CONTEXT_NAME br_x509_decoder_context /* see bearssl_x509.h */ void br_x509_decoder_init(br_x509_decoder_context *ctx, void (*append_dn)(void *ctx, const void *buf, size_t len), void *append_dn_ctx) { memset(ctx, 0, sizeof *ctx); /* obsolete ctx->err = 0; ctx->hbuf = NULL; ctx->hlen = 0; */ ctx->append_dn = append_dn; ctx->append_dn_ctx = append_dn_ctx; ctx->cpu.dp = &ctx->dp_stack[0]; ctx->cpu.rp = &ctx->rp_stack[0]; br_x509_decoder_init_main(&ctx->cpu); br_x509_decoder_run(&ctx->cpu); } /* see bearssl_x509.h */ void br_x509_decoder_push(br_x509_decoder_context *ctx, const void *data, size_t len) { ctx->hbuf = data; ctx->hlen = len; br_x509_decoder_run(&ctx->cpu); } } addr: decoded addr: notbefore_days addr: notbefore_seconds addr: notafter_days addr: notafter_seconds addr: isCA addr: copy_dn addr: signer_key_type addr: signer_hash_id cc: read8-low ( -- x ) { if (CTX->hlen == 0) { T0_PUSHi(-1); } else { unsigned char x = *CTX->hbuf ++; if (CTX->copy_dn && CTX->append_dn) { CTX->append_dn(CTX->append_dn_ctx, &x, 1); } CTX->hlen --; T0_PUSH(x); } } cc: read-blob-inner ( addr len -- addr len ) { uint32_t len = T0_POP(); uint32_t addr = T0_POP(); size_t clen = CTX->hlen; if (clen > len) { clen = (size_t)len; } if (addr != 0) { memcpy((unsigned char *)CTX + addr, CTX->hbuf, clen); } if (CTX->copy_dn && CTX->append_dn) { CTX->append_dn(CTX->append_dn_ctx, CTX->hbuf, clen); } CTX->hbuf += clen; CTX->hlen -= clen; T0_PUSH(addr + clen); T0_PUSH(len - clen); } \ Get the address and length for the pkey_data buffer. : addr-len-pkey_data ( -- addr len ) CX 0 8191 { offsetof(br_x509_decoder_context, pkey_data) } CX 0 8191 { BR_X509_BUFSIZE_KEY } ; \ Copy the public key (RSA) to the permanent buffer. cc: copy-rsa-pkey ( nlen elen -- ) { size_t elen = T0_POP(); size_t nlen = T0_POP(); CTX->pkey.key_type = BR_KEYTYPE_RSA; CTX->pkey.key.rsa.n = CTX->pkey_data; CTX->pkey.key.rsa.nlen = nlen; CTX->pkey.key.rsa.e = CTX->pkey_data + nlen; CTX->pkey.key.rsa.elen = elen; } \ Copy the public key (EC) to the permanent buffer. cc: copy-ec-pkey ( curve qlen -- ) { size_t qlen = T0_POP(); uint32_t curve = T0_POP(); CTX->pkey.key_type = BR_KEYTYPE_EC; CTX->pkey.key.ec.curve = curve; CTX->pkey.key.ec.q = CTX->pkey_data; CTX->pkey.key.ec.qlen = qlen; } \ Extensions with specific processing. OID: basicConstraints 2.5.29.19 \ Process a Basic Constraints extension. We want the "CA" flag only. : process-basicConstraints ( lim -- lim ) read-sequence-open read-tag-or-end dup 0x01 = if read-boolean 1 and addr-isCA set8 else 2drop then skip-close-elt ; \ Decode a certificate. : main ( -- ! ) \ Initialise state flags. 0 addr-decoded set8 0 addr-copy_dn set8 \ An arbitrary limit for the total certificate size. 0xFFFFFF \ Open the outer SEQUENCE. read-sequence-open \ TBS read-sequence-open \ First element may be an explicit version. We accept only \ versions 0 to 2 (certificates v1 to v3). read-tag dup 0x20 = if drop check-constructed read-length-open-elt read-tag 0x02 check-tag-primitive read-small-int-value 2 > if ERR_X509_UNSUPPORTED fail then close-elt read-tag then \ Serial number. We just check that the tag is correct. 0x02 check-tag-primitive read-length-skip \ Signature algorithm. read-sequence-open skip-close-elt \ Issuer name. read-sequence-open skip-close-elt \ Validity dates. read-sequence-open read-date addr-notbefore_seconds set32 addr-notbefore_days set32 read-date addr-notafter_seconds set32 addr-notafter_days set32 close-elt \ Subject name. 1 addr-copy_dn set8 read-sequence-open skip-close-elt 0 addr-copy_dn set8 \ Public Key. read-sequence-open \ Algorithm Identifier. Right now we are only interested in the \ OID, since we only support RSA keys. \ TODO: support EC keys read-sequence-open read-OID ifnot ERR_X509_UNSUPPORTED fail then choice \ RSA public key. rsaEncryption eqOID uf skip-close-elt \ Public key itself: the BIT STRING contains bytes \ (no partial byte) and these bytes encode the \ actual value. read-bits-open \ RSA public key is a SEQUENCE of two \ INTEGER. We get both INTEGER values into \ the pkey_data[] buffer, if they fit. read-sequence-open addr-len-pkey_data read-integer { nlen } addr-len-pkey_data swap nlen + swap nlen - read-integer { elen } close-elt close-elt nlen elen copy-rsa-pkey enduf \ EC public key. id-ecPublicKey eqOID uf \ We support only named curves, for which the \ "parameters" field in the AlgorithmIdentifier \ field should be an OID. read-curve-ID { curve } close-elt read-bits-open dup { qlen } dup addr-len-pkey_data rot < if ERR_X509_LIMIT_EXCEEDED fail then read-blob curve qlen copy-ec-pkey enduf ERR_X509_UNSUPPORTED fail endchoice close-elt \ This flag will be set to true if the Basic Constraints extension \ is encountered. 0 addr-isCA set8 \ Skip issuerUniqueID and subjectUniqueID, and process extensions \ if present. Extensions are an explicit context tag of value 3 \ around a SEQUENCE OF extensions. Each extension is a SEQUENCE \ with an OID, an optional boolean, and a value; the value is \ an OCTET STRING. read-tag-or-end 0x21 iftag-skip 0x22 iftag-skip dup 0x23 = if drop check-constructed read-length-open-elt read-sequence-open begin dup while read-sequence-open read-OID drop read-tag dup 0x01 = if read-boolean drop read-tag then 0x04 check-tag-primitive read-length-open-elt choice \ Extensions with specific processing. basicConstraints eqOID uf process-basicConstraints enduf skip-remaining endchoice close-elt close-elt repeat close-elt close-elt else -1 = ifnot ERR_X509_UNEXPECTED fail then drop then close-elt \ signature algorithm read-sequence-open read-OID if choice sha1WithRSAEncryption eqOID uf 2 KEYTYPE_RSA enduf sha224WithRSAEncryption eqOID uf 3 KEYTYPE_RSA enduf sha256WithRSAEncryption eqOID uf 4 KEYTYPE_RSA enduf sha384WithRSAEncryption eqOID uf 5 KEYTYPE_RSA enduf sha512WithRSAEncryption eqOID uf 6 KEYTYPE_RSA enduf ecdsa-with-SHA1 eqOID uf 2 KEYTYPE_EC enduf ecdsa-with-SHA224 eqOID uf 3 KEYTYPE_EC enduf ecdsa-with-SHA256 eqOID uf 4 KEYTYPE_EC enduf ecdsa-with-SHA384 eqOID uf 5 KEYTYPE_EC enduf ecdsa-with-SHA512 eqOID uf 6 KEYTYPE_EC enduf 0 0 endchoice else 0 0 then addr-signer_key_type set8 addr-signer_hash_id set8 skip-close-elt \ read-sequence-open skip-close-elt \ signature value read-bits-open skip-close-elt \ Close the outer SEQUENCE. close-elt drop \ Mark the decoding as successful. 1 addr-decoded set8 \ Read one byte, then fail: if the read succeeds, then there is \ some trailing byte. read8-nc ERR_X509_EXTRA_ELEMENT fail ;