diff options
Diffstat (limited to 'chip/g/dcrypto/x509.c')
-rw-r--r-- | chip/g/dcrypto/x509.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/chip/g/dcrypto/x509.c b/chip/g/dcrypto/x509.c new file mode 100644 index 0000000000..81f1674db1 --- /dev/null +++ b/chip/g/dcrypto/x509.c @@ -0,0 +1,545 @@ +/* Copyright 2016 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "dcrypto.h" + +#include <stdint.h> + +/* Limit the size of long form encoded objects to < 64 kB. */ +#define MAX_ASN1_OBJ_LEN_BYTES 3 + +/* Reserve space for TLV encoding */ +#define SEQ_SMALL 2 /* < 128 bytes (1B type, 1B 7-bit length) */ +#define SEQ_MEDIUM 3 /* < 256 bytes (1B type, 1B length size, 1B length) */ +#define SEQ_LARGE 4 /* < 65536 bytes (1B type, 1B length size, 2B length) */ + +/* Tag related constants. */ +enum { + V_ASN1_INT = 0x02, + V_ASN1_BIT_STRING = 0x03, + V_ASN1_BYTES = 0x04, + V_ASN1_OBJ = 0x06, + V_ASN1_UTF8 = 0x0c, + V_ASN1_SEQUENCE = 0x10, + V_ASN1_SET = 0x11, + V_ASN1_ASCII = 0x13, + V_ASN1_TIME = 0x18, + V_ASN1_CONSTRUCTED = 0x20, + /* short helpers */ + V_BITS = V_ASN1_BIT_STRING, + V_SEQ = V_ASN1_CONSTRUCTED | V_ASN1_SEQUENCE, + V_SET = V_ASN1_CONSTRUCTED | V_ASN1_SET, +}; + +struct asn1 { + uint8_t *p; + size_t n; +}; + + +#define SEQ_START(X, T, L) \ + do { \ + int __old = (X).n; \ + uint8_t __t = (T); \ + int __l = (L); \ + (X).n += __l; +#define SEQ_END(X) \ + (X).n = asn1_seq((X).p + __old, __t, __l, (X).n - __old - __l) + __old;\ + } \ + while (0) + +/* The SHA256 OID, from https://tools.ietf.org/html/rfc5754#section-3.2 + * Only the object bytes below, the DER encoding header ([0x30 0x0d]) + * is verified by the parser. */ +static const uint8_t OID_SHA256_WITH_RSA_ENCRYPTION[13] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x01, 0x0b, 0x05, 0x00 +}; +static const uint8_t OID_commonName[3] = {0x55, 0x04, 0x03}; +static const uint8_t OID_ecdsa_with_SHA256[8] = {0x2A, 0x86, 0x48, 0xCE, + 0x3D, 0x04, 0x03, 0x02}; +static const uint8_t OID_id_ecPublicKey[7] = {0x2A, 0x86, 0x48, 0xCE, 0x3D, + 0x02, 0x01}; +static const uint8_t OID_prime256v1[8] = {0x2A, 0x86, 0x48, 0xCE, + 0x3D, 0x03, 0x01, 0x07}; +static const uint8_t OID_fido_u2f[11] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, + 0xE5, 0x1C, 0x02, 0x01, 0x01}; +#define OID(X) sizeof(OID_##X), OID_##X + +/* ---- ASN.1 Generation ---- */ + +/* start a tag and return write ptr */ +static uint8_t *asn1_tag(struct asn1 *ctx, uint8_t tag) +{ + ctx->p[(ctx->n)++] = tag; + return ctx->p + ctx->n; +} + +/* DER encode length and return encoded size thereof */ +static int asn1_len(uint8_t *p, size_t size) +{ + if (size < 128) { + p[0] = size; + return 1; + } else if (size < 256) { + p[0] = 0x81; + p[1] = size; + return 2; + } else { + p[0] = 0x82; + p[1] = size >> 8; + p[2] = size; + return 3; + } +} + +/* + * close sequence and move encapsulated data if needed + * return total length. + */ +static size_t asn1_seq(uint8_t *p, uint8_t tag, size_t l, size_t size) +{ + size_t tl; + + p[0] = tag; + tl = asn1_len(p + 1, size) + 1; + /* TODO: tl > l fail */ + if (tl < l) + memmove(p + tl, p + l, size); + + return tl + size; +} + +/* DER encode (small positive) integer */ +static void asn1_int(struct asn1 *ctx, uint32_t val) +{ + uint8_t *p = asn1_tag(ctx, V_ASN1_INT); + + if (!val) { + *p++ = 1; + *p++ = 0; + } else { + int nbits = 32 - __builtin_clz(val); + int nbytes = (nbits + 7) / 8; + + if ((nbits & 7) == 0) { + *p++ = nbytes + 1; + *p++ = 0; + } else { + *p++ = nbytes; + } + while (nbytes--) + *p++ = val >> (nbytes * 8); + } + + ctx->n = p - ctx->p; +} + +/* DER encode positive p256_int */ +static void asn1_p256_int(struct asn1 *ctx, const p256_int *n) +{ + uint8_t *p = asn1_tag(ctx, V_ASN1_INT); + uint8_t bn[P256_NBYTES]; + int i; + + p256_to_bin(n, bn); + for (i = 0; i < P256_NBYTES; ++i) { + if (bn[i] != 0) + break; + } + if (bn[i] & 0x80) { + *p++ = P256_NBYTES - i + 1; + *p++ = 0; + } else { + *p++ = P256_NBYTES - i; + } + for (; i < P256_NBYTES; ++i) + *p++ = bn[i]; + + ctx->n = p - ctx->p; +} + +/* DER encode p256 signature */ +static void asn1_sig(struct asn1 *ctx, const p256_int *r, const p256_int *s) +{ + SEQ_START(*ctx, V_SEQ, SEQ_SMALL) { + asn1_p256_int(ctx, r); + asn1_p256_int(ctx, s); + } + SEQ_END(*ctx); +} + +/* DER encode printable string */ +static void asn1_string(struct asn1 *ctx, uint8_t tag, const char *s) +{ + uint8_t *p = asn1_tag(ctx, tag); + size_t n = strlen(s); + + p += asn1_len(p, n); + while (n--) + *p++ = *s++; + + ctx->n = p - ctx->p; +} + +/* DER encode bytes */ +static void asn1_object(struct asn1 *ctx, size_t n, const uint8_t *b) +{ + uint8_t *p = asn1_tag(ctx, V_ASN1_OBJ); + + p += asn1_len(p, n); + while (n--) + *p++ = *b++; + + ctx->n = p - ctx->p; +} + +/* DER encode p256 pk */ +static void asn1_pub(struct asn1 *ctx, const p256_int *x, const p256_int *y) +{ + uint8_t *p = asn1_tag(ctx, 4); /* uncompressed format */ + + p256_to_bin(x, p); p += P256_NBYTES; + p256_to_bin(y, p); p += P256_NBYTES; + + ctx->n = p - ctx->p; +} + +size_t DCRYPTO_asn1_sigp(uint8_t *buf, const p256_int *r, const p256_int *s) +{ + struct asn1 asn1 = {buf, 0}; + + asn1_sig(&asn1, r, s); + return asn1.n; +} + +size_t DCRYPTO_asn1_pubp(uint8_t *buf, const p256_int *x, const p256_int *y) +{ + struct asn1 asn1 = {buf, 0}; + + asn1_pub(&asn1, x, y); + return asn1.n; +} + +/* ---- ASN.1 Parsing ---- */ + +/* + * An ASN.1 DER (Definite Encoding Rules) parser. + * Details about the format are available here: + * https://en.wikipedia.org/wiki/X.690#Definite_form + */ +static size_t asn1_parse(const uint8_t **p, size_t available, + uint8_t expected_type, const uint8_t **out, + size_t *out_len, size_t *remaining) +{ + const size_t tag_len = 1; + const uint8_t *in = *p; + size_t obj_len = 0; + size_t obj_len_bytes; + size_t consumed; + + if (available < 2) + return 0; + if (in[0] != expected_type) /* in[0] specifies the tag. */ + return 0; + + if ((in[1] & 128) == 0) { + /* Short-length encoding (i.e. obj_len <= 127). */ + obj_len = in[1]; + obj_len_bytes = 1; + } else { + int i; + + obj_len_bytes = 1 + (in[1] & 127); + if (obj_len_bytes > MAX_ASN1_OBJ_LEN_BYTES || + tag_len + obj_len_bytes > available) + return 0; + + if (in[2] == 0) + /* Definite form encoding requires minimal + * length encoding. */ + return 0; + for (i = 0; i < obj_len_bytes - 1; i++) { + obj_len <<= 8; + obj_len |= in[tag_len + 1 + i]; + } + } + + consumed = tag_len + obj_len_bytes + obj_len; + if (consumed > available) + return 0; /* Invalid object length.*/ + if (out) + *out = &in[tag_len + obj_len_bytes]; + if (out_len) + *out_len = obj_len; + + *p = in + consumed; + if (remaining) + *remaining = available - consumed; + return consumed; +} + +static size_t asn1_parse_certificate(const uint8_t **p, size_t *available) +{ + size_t consumed; + size_t obj_len; + const uint8_t *in = *p; + + consumed = asn1_parse(&in, *available, + V_ASN1_CONSTRUCTED | V_ASN1_SEQUENCE, + NULL, &obj_len, NULL); + if (consumed == 0 || consumed != *available) /* Invalid SEQUENCE. */ + return 0; + *p += consumed - obj_len; + *available -= consumed - obj_len; + return 1; +} + +static size_t asn1_parse_tbs(const uint8_t **p, size_t *available, + size_t *tbs_len) +{ + size_t consumed; + + consumed = asn1_parse(p, *available, + V_ASN1_CONSTRUCTED | V_ASN1_SEQUENCE, + NULL, NULL, available); + if (consumed == 0) + return 0; + *tbs_len = consumed; + return 1; +} + +static size_t asn1_parse_signature_algorithm(const uint8_t **p, + size_t *available) +{ + const uint8_t *alg_oid; + size_t alg_oid_len; + + if (!asn1_parse(p, *available, V_ASN1_CONSTRUCTED | V_ASN1_SEQUENCE, + &alg_oid, &alg_oid_len, available)) + return 0; + if (alg_oid_len != sizeof(OID_SHA256_WITH_RSA_ENCRYPTION)) + return 0; + if (memcmp(alg_oid, OID_SHA256_WITH_RSA_ENCRYPTION, + sizeof(OID_SHA256_WITH_RSA_ENCRYPTION)) != 0) + return 0; + return 1; +} + +static size_t asn1_parse_signature_value(const uint8_t **p, size_t *available, + const uint8_t **sig, size_t *sig_len) +{ + if (!asn1_parse(p, *available, V_ASN1_BIT_STRING, + sig, sig_len, available)) + return 0; + if (*available != 0) + return 0; /* Not all input bytes consumed. */ + return 1; +} + +/* This method verifies that the provided X509 certificate was issued + * by the specified certifcate authority. + * + * cert is a pointer to a DER encoded X509 certificate, as specified + * in https://tools.ietf.org/html/rfc5280#section-4.1. In ASN.1 + * notation, the certificate has the following structure: + * + * Certificate ::= SEQUENCE { + * tbsCertificate TBSCertificate, + * signatureAlgorithm AlgorithmIdentifier, + * signatureValue BIT STRING } + * + * TBSCertificate ::= SEQUENCE { } + * AlgorithmIdentifier ::= SEQUENCE { } + * + * where signatureValue = SIGN(HASH(tbsCertificate)), with SIGN and + * HASH specified by signatureAlgorithm. + */ +int DCRYPTO_x509_verify(const uint8_t *cert, size_t len, + const struct RSA *ca_pub_key) +{ + const uint8_t *p = cert; + const uint8_t *tbs; + size_t tbs_len; + const uint8_t *sig; + size_t sig_len; + + uint8_t digest[SHA256_DIGEST_SIZE]; + + /* Read Certificate SEQUENCE. */ + if (!asn1_parse_certificate(&p, &len)) + return 0; + + /* Read tbsCertificate SEQUENCE. */ + tbs = p; + if (!asn1_parse_tbs(&p, &len, &tbs_len)) + return 0; + + /* Read signatureAlgorithm SEQUENCE. */ + if (!asn1_parse_signature_algorithm(&p, &len)) + return 0; + + /* Read signatureValue BIT STRING. */ + if (!asn1_parse_signature_value(&p, &len, &sig, &sig_len)) + return 0; + + /* Check that the signature length corresponds to the issuer's + * public key size. */ + if (sig_len != bn_size(&ca_pub_key->N) && + sig_len != bn_size(&ca_pub_key->N) + 1) + return 0; + /* Check that leading signature bytes (if any) are zero. */ + if (sig_len == bn_size(&ca_pub_key->N) + 1) { + if (sig[0] != 0) + return 0; + sig++; + sig_len--; + } + + DCRYPTO_SHA256_hash(tbs, tbs_len, digest); + return DCRYPTO_rsa_verify(ca_pub_key, digest, sizeof(digest), + sig, sig_len, PADDING_MODE_PKCS1, HASH_SHA256); +} + +/* ---- Certificate generation ---- */ + +static void add_common_name(struct asn1 *ctx, const char *cname) +{ + SEQ_START(*ctx, V_SEQ, SEQ_SMALL) { + SEQ_START(*ctx, V_SET, SEQ_SMALL) { + SEQ_START(*ctx, V_SEQ, SEQ_SMALL) { + asn1_object(ctx, OID(commonName)); + asn1_string(ctx, V_ASN1_ASCII, cname); + } + SEQ_END(*ctx); + } + SEQ_END(*ctx); + } + SEQ_END(*ctx); +} + +int DCRYPTO_x509_gen_u2f_cert_name(const p256_int *d, const p256_int *pk_x, + const p256_int *pk_y, const p256_int *serial, + const char *name, uint8_t *cert, const int n) +{ + struct asn1 ctx = {cert, 0}; + HASH_CTX sha; + p256_int h, r, s; + struct drbg_ctx drbg; + + SEQ_START(ctx, V_SEQ, SEQ_LARGE) { /* outer seq */ + /* + * Grab current pointer to data to hash later. + * Note this will fail if cert body + cert sign is less + * than 256 bytes (SEQ_MEDIUM) -- not likely. + */ + uint8_t *body = ctx.p + ctx.n; + + /* Cert body seq */ + SEQ_START(ctx, V_SEQ, SEQ_MEDIUM) { + /* X509 v3 */ + SEQ_START(ctx, 0xa0, SEQ_SMALL) { + asn1_int(&ctx, 2); + } + SEQ_END(ctx); + + /* Serial number */ + if (serial) + asn1_p256_int(&ctx, serial); + else + asn1_int(&ctx, 1); + + /* Signature algo */ + SEQ_START(ctx, V_SEQ, SEQ_SMALL) { + asn1_object(&ctx, OID(ecdsa_with_SHA256)); + } + SEQ_END(ctx); + + /* Issuer */ + add_common_name(&ctx, name); + + /* Expiry */ + SEQ_START(ctx, V_SEQ, SEQ_SMALL) { + asn1_string(&ctx, V_ASN1_TIME, "20000101000000Z"); + asn1_string(&ctx, V_ASN1_TIME, "20991231235959Z"); + } + SEQ_END(ctx); + + /* Subject */ + add_common_name(&ctx, name); + + /* Subject pk */ + SEQ_START(ctx, V_SEQ, SEQ_SMALL) { + /* pk parameters */ + SEQ_START(ctx, V_SEQ, SEQ_SMALL) { + asn1_object(&ctx, OID(id_ecPublicKey)); + asn1_object(&ctx, OID(prime256v1)); + } + SEQ_END(ctx); + /* pk bits */ + SEQ_START(ctx, V_BITS, SEQ_SMALL) { + /* No unused bit at the end */ + asn1_tag(&ctx, 0); + asn1_pub(&ctx, pk_x, pk_y); + } + SEQ_END(ctx); + } + SEQ_END(ctx); + + /* U2F transports indicator extension */ + SEQ_START(ctx, 0xa3, SEQ_SMALL) { + SEQ_START(ctx, V_SEQ, SEQ_SMALL) { + SEQ_START(ctx, V_SEQ, SEQ_SMALL) { + asn1_object(&ctx, OID(fido_u2f)); + SEQ_START(ctx, V_ASN1_BYTES, SEQ_SMALL) { + SEQ_START(ctx, V_BITS, SEQ_SMALL) { + /* 3 zero bits */ + asn1_tag(&ctx, 3); + /* usb-internal transport */ + asn1_tag(&ctx, 0x08); + } + SEQ_END(ctx); + } + SEQ_END(ctx); + } + SEQ_END(ctx); + } + SEQ_END(ctx); + } + SEQ_END(ctx); + } + SEQ_END(ctx); /* Cert body */ + + /* Sign all of cert body */ + DCRYPTO_SHA256_init(&sha, 0); + HASH_update(&sha, body, (ctx.p + ctx.n) - body); + p256_from_bin(HASH_final(&sha), &h); + hmac_drbg_init_rfc6979(&drbg, d, &h); + if (!dcrypto_p256_ecdsa_sign(&drbg, d, &h, &r, &s)) + return 0; + + /* Append X509 signature */ + SEQ_START(ctx, V_SEQ, SEQ_SMALL); + asn1_object(&ctx, OID(ecdsa_with_SHA256)); + SEQ_END(ctx); + SEQ_START(ctx, V_BITS, SEQ_SMALL) { + /* no unused/zero bit at the end */ + asn1_tag(&ctx, 0); + asn1_sig(&ctx, &r, &s); + } SEQ_END(ctx); + + } SEQ_END(ctx); /* end of outer seq */ + + return ctx.n; +} + +int DCRYPTO_x509_gen_u2f_cert(const p256_int *d, const p256_int *pk_x, + const p256_int *pk_y, const p256_int *serial, + uint8_t *cert, const int n) +{ + return DCRYPTO_x509_gen_u2f_cert_name(d, pk_x, pk_y, serial, + serial ? STRINGIFY(BOARD) : "U2F", + cert, n); +} |