\ Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
\
\ 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 *)(void *)((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
;