| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * X509 cert parser using MbedTLS X509 library |
| * |
| * Copyright (c) 2024 Linaro Limited |
| * Author: Raymond Mao <raymond.mao@linaro.org> |
| */ |
| |
| #include <linux/err.h> |
| #include <crypto/public_key.h> |
| #include <crypto/x509_parser.h> |
| |
| static void x509_free_mbedtls_ctx(struct x509_cert_mbedtls_ctx *ctx) |
| { |
| if (!ctx) |
| return; |
| |
| kfree(ctx->tbs); |
| kfree(ctx->raw_serial); |
| kfree(ctx->raw_issuer); |
| kfree(ctx->raw_subject); |
| kfree(ctx->raw_skid); |
| kfree(ctx); |
| } |
| |
| static int x509_set_cert_flags(struct x509_certificate *cert) |
| { |
| struct public_key_signature *sig = cert->sig; |
| |
| if (!sig || !cert->pub) { |
| pr_err("Signature or public key is not initialized\n"); |
| return -ENOPKG; |
| } |
| |
| if (!cert->pub->pkey_algo) |
| cert->unsupported_key = true; |
| |
| if (!sig->pkey_algo) |
| cert->unsupported_sig = true; |
| |
| if (!sig->hash_algo) |
| cert->unsupported_sig = true; |
| |
| /* TODO: is_hash_blacklisted()? */ |
| |
| /* Detect self-signed certificates and set self_signed flag */ |
| return x509_check_for_self_signed(cert); |
| } |
| |
| time64_t x509_get_timestamp(const mbedtls_x509_time *x509_time) |
| { |
| unsigned int year, mon, day, hour, min, sec; |
| |
| /* Adjust for year since 1900 */ |
| year = x509_time->year - 1900; |
| /* Adjust for 0-based month */ |
| mon = x509_time->mon - 1; |
| day = x509_time->day; |
| hour = x509_time->hour; |
| min = x509_time->min; |
| sec = x509_time->sec; |
| |
| return (time64_t)mktime64(year, mon, day, hour, min, sec); |
| } |
| |
| static char *x509_populate_dn_name_string(const mbedtls_x509_name *name) |
| { |
| size_t len = 256; |
| int wb; |
| char *name_str; |
| |
| do { |
| name_str = kzalloc(len, GFP_KERNEL); |
| if (!name_str) |
| return NULL; |
| |
| wb = mbedtls_x509_dn_gets(name_str, len, name); |
| if (wb < 0) { |
| pr_err("Get DN string failed, ret:-0x%04x\n", |
| (unsigned int)-wb); |
| kfree(name_str); |
| len = len * 2; /* Try with a bigger buffer */ |
| } |
| } while (wb < 0); |
| |
| name_str[wb] = '\0'; /* add the terminator */ |
| |
| return name_str; |
| } |
| |
| static int x509_populate_signature_params(const mbedtls_x509_crt *cert, |
| struct public_key_signature **sig) |
| { |
| struct public_key_signature *s; |
| struct image_region region; |
| size_t akid_len; |
| unsigned char *akid_data; |
| int ret; |
| |
| /* Check if signed data exist */ |
| if (!cert->tbs.p || !cert->tbs.len) |
| return -EINVAL; |
| |
| region.data = cert->tbs.p; |
| region.size = cert->tbs.len; |
| |
| s = kzalloc(sizeof(*s), GFP_KERNEL); |
| if (!s) |
| return -ENOMEM; |
| |
| /* |
| * Get the public key algorithm. |
| * Note: |
| * ECRDSA (Elliptic Curve Russian Digital Signature Algorithm) is not |
| * supported by MbedTLS. |
| */ |
| switch (cert->sig_pk) { |
| case MBEDTLS_PK_RSA: |
| s->pkey_algo = "rsa"; |
| break; |
| default: |
| ret = -EINVAL; |
| goto error_sig; |
| } |
| |
| /* Get the hash algorithm */ |
| switch (cert->sig_md) { |
| case MBEDTLS_MD_SHA1: |
| s->hash_algo = "sha1"; |
| s->digest_size = SHA1_SUM_LEN; |
| break; |
| case MBEDTLS_MD_SHA256: |
| s->hash_algo = "sha256"; |
| s->digest_size = SHA256_SUM_LEN; |
| break; |
| case MBEDTLS_MD_SHA384: |
| s->hash_algo = "sha384"; |
| s->digest_size = SHA384_SUM_LEN; |
| break; |
| case MBEDTLS_MD_SHA512: |
| s->hash_algo = "sha512"; |
| s->digest_size = SHA512_SUM_LEN; |
| break; |
| /* Unsupported algo */ |
| case MBEDTLS_MD_MD5: |
| case MBEDTLS_MD_SHA224: |
| default: |
| ret = -EINVAL; |
| goto error_sig; |
| } |
| |
| /* |
| * Optional attributes: |
| * auth_ids holds AuthorityKeyIdentifier (information of issuer), |
| * aka akid, which is used to match with a cert's id or skid to |
| * indicate that is the issuer when we lookup a cert chain. |
| * |
| * auth_ids[0]: |
| * [PKCS#7 or CMS ver 1] - generated from "Issuer + Serial number" |
| * [CMS ver 3] - generated from skid (subjectKeyId) |
| * auth_ids[1]: generated from skid (subjectKeyId) |
| * |
| * Assume that we are using PKCS#7 (msg->version=1), |
| * not CMS ver 3 (msg->version=3). |
| */ |
| akid_len = cert->authority_key_id.authorityCertSerialNumber.len; |
| akid_data = cert->authority_key_id.authorityCertSerialNumber.p; |
| |
| /* Check if serial number exists */ |
| if (akid_len && akid_data) { |
| s->auth_ids[0] = asymmetric_key_generate_id(akid_data, |
| akid_len, |
| cert->issuer_raw.p, |
| cert->issuer_raw.len); |
| if (!s->auth_ids[0]) { |
| ret = -ENOMEM; |
| goto error_sig; |
| } |
| } |
| |
| akid_len = cert->authority_key_id.keyIdentifier.len; |
| akid_data = cert->authority_key_id.keyIdentifier.p; |
| |
| /* Check if subjectKeyId exists */ |
| if (akid_len && akid_data) { |
| s->auth_ids[1] = asymmetric_key_generate_id(akid_data, |
| akid_len, |
| "", 0); |
| if (!s->auth_ids[1]) { |
| ret = -ENOMEM; |
| goto error_sig; |
| } |
| } |
| |
| /* |
| * Encoding can be pkcs1 or raw, but only pkcs1 is supported. |
| * Set the encoding explicitly to pkcs1. |
| */ |
| s->encoding = "pkcs1"; |
| |
| /* Copy the signature data */ |
| s->s = kmemdup(cert->sig.p, cert->sig.len, GFP_KERNEL); |
| if (!s->s) { |
| ret = -ENOMEM; |
| goto error_sig; |
| } |
| s->s_size = cert->sig.len; |
| |
| /* Calculate the digest of signed data (tbs) */ |
| s->digest = kzalloc(s->digest_size, GFP_KERNEL); |
| if (!s->digest) { |
| ret = -ENOMEM; |
| goto error_sig; |
| } |
| |
| ret = hash_calculate(s->hash_algo, ®ion, 1, s->digest); |
| if (!ret) |
| *sig = s; |
| |
| return ret; |
| |
| error_sig: |
| public_key_signature_free(s); |
| return ret; |
| } |
| |
| static int x509_save_mbedtls_ctx(const mbedtls_x509_crt *cert, |
| struct x509_cert_mbedtls_ctx **pctx) |
| { |
| struct x509_cert_mbedtls_ctx *ctx; |
| |
| ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| if (!ctx) |
| return -ENOMEM; |
| |
| /* Signed data (tbs - The part that is To Be Signed)*/ |
| ctx->tbs = kmemdup(cert->tbs.p, cert->tbs.len, |
| GFP_KERNEL); |
| if (!ctx->tbs) |
| goto error_ctx; |
| |
| /* Raw serial number */ |
| ctx->raw_serial = kmemdup(cert->serial.p, |
| cert->serial.len, GFP_KERNEL); |
| if (!ctx->raw_serial) |
| goto error_ctx; |
| |
| /* Raw issuer */ |
| ctx->raw_issuer = kmemdup(cert->issuer_raw.p, |
| cert->issuer_raw.len, GFP_KERNEL); |
| if (!ctx->raw_issuer) |
| goto error_ctx; |
| |
| /* Raw subject */ |
| ctx->raw_subject = kmemdup(cert->subject_raw.p, |
| cert->subject_raw.len, GFP_KERNEL); |
| if (!ctx->raw_subject) |
| goto error_ctx; |
| |
| /* Raw subjectKeyId */ |
| ctx->raw_skid = kmemdup(cert->subject_key_id.p, |
| cert->subject_key_id.len, GFP_KERNEL); |
| if (!ctx->raw_skid) |
| goto error_ctx; |
| |
| *pctx = ctx; |
| |
| return 0; |
| |
| error_ctx: |
| x509_free_mbedtls_ctx(ctx); |
| return -ENOMEM; |
| } |
| |
| /* |
| * Free an X.509 certificate |
| */ |
| void x509_free_certificate(struct x509_certificate *cert) |
| { |
| if (cert) { |
| public_key_free(cert->pub); |
| public_key_signature_free(cert->sig); |
| kfree(cert->issuer); |
| kfree(cert->subject); |
| kfree(cert->id); |
| kfree(cert->skid); |
| x509_free_mbedtls_ctx(cert->mbedtls_ctx); |
| kfree(cert); |
| } |
| } |
| |
| int x509_populate_pubkey(mbedtls_x509_crt *cert, struct public_key **pub_key) |
| { |
| struct public_key *pk; |
| |
| pk = kzalloc(sizeof(*pk), GFP_KERNEL); |
| if (!pk) |
| return -ENOMEM; |
| |
| pk->key = kzalloc(cert->pk_raw.len, GFP_KERNEL); |
| if (!pk->key) { |
| kfree(pk); |
| return -ENOMEM; |
| } |
| memcpy(pk->key, cert->pk_raw.p, cert->pk_raw.len); |
| pk->keylen = cert->pk_raw.len; |
| |
| /* |
| * For ECC keys, params field might include information about the curve used, |
| * the generator point, or other algorithm-specific parameters. |
| * For RSA keys, it's common for the params field to be NULL. |
| * FIXME: Assume that we just support RSA keys with id_type X509. |
| */ |
| pk->params = NULL; |
| pk->paramlen = 0; |
| |
| pk->key_is_private = false; |
| pk->id_type = "X509"; |
| pk->pkey_algo = "rsa"; |
| pk->algo = OID_rsaEncryption; |
| |
| *pub_key = pk; |
| |
| return 0; |
| } |
| |
| int x509_populate_cert(mbedtls_x509_crt *mbedtls_cert, |
| struct x509_certificate **pcert) |
| { |
| struct x509_certificate *cert; |
| struct asymmetric_key_id *kid; |
| struct asymmetric_key_id *skid; |
| int ret; |
| |
| cert = kzalloc(sizeof(*cert), GFP_KERNEL); |
| if (!cert) |
| return -ENOMEM; |
| |
| /* Public key details */ |
| ret = x509_populate_pubkey(mbedtls_cert, &cert->pub); |
| if (ret) |
| goto error_cert_pop; |
| |
| /* Signature parameters */ |
| ret = x509_populate_signature_params(mbedtls_cert, &cert->sig); |
| if (ret) |
| goto error_cert_pop; |
| |
| ret = -ENOMEM; |
| |
| /* Name of certificate issuer */ |
| cert->issuer = x509_populate_dn_name_string(&mbedtls_cert->issuer); |
| if (!cert->issuer) |
| goto error_cert_pop; |
| |
| /* Name of certificate subject */ |
| cert->subject = x509_populate_dn_name_string(&mbedtls_cert->subject); |
| if (!cert->subject) |
| goto error_cert_pop; |
| |
| /* Certificate validity */ |
| cert->valid_from = x509_get_timestamp(&mbedtls_cert->valid_from); |
| cert->valid_to = x509_get_timestamp(&mbedtls_cert->valid_to); |
| |
| /* Save mbedtls context we need */ |
| ret = x509_save_mbedtls_ctx(mbedtls_cert, &cert->mbedtls_ctx); |
| if (ret) |
| goto error_cert_pop; |
| |
| /* Signed data (tbs - The part that is To Be Signed)*/ |
| cert->tbs = cert->mbedtls_ctx->tbs; |
| cert->tbs_size = mbedtls_cert->tbs.len; |
| |
| /* Raw serial number */ |
| cert->raw_serial = cert->mbedtls_ctx->raw_serial; |
| cert->raw_serial_size = mbedtls_cert->serial.len; |
| |
| /* Raw issuer */ |
| cert->raw_issuer = cert->mbedtls_ctx->raw_issuer; |
| cert->raw_issuer_size = mbedtls_cert->issuer_raw.len; |
| |
| /* Raw subject */ |
| cert->raw_subject = cert->mbedtls_ctx->raw_subject; |
| cert->raw_subject_size = mbedtls_cert->subject_raw.len; |
| |
| /* Raw subjectKeyId */ |
| cert->raw_skid = cert->mbedtls_ctx->raw_skid; |
| cert->raw_skid_size = mbedtls_cert->subject_key_id.len; |
| |
| /* Generate cert issuer + serial number key ID */ |
| kid = asymmetric_key_generate_id(cert->raw_serial, |
| cert->raw_serial_size, |
| cert->raw_issuer, |
| cert->raw_issuer_size); |
| if (IS_ERR(kid)) { |
| ret = PTR_ERR(kid); |
| goto error_cert_pop; |
| } |
| cert->id = kid; |
| |
| /* Generate subject + subjectKeyId */ |
| skid = asymmetric_key_generate_id(cert->raw_skid, cert->raw_skid_size, "", 0); |
| if (IS_ERR(skid)) { |
| ret = PTR_ERR(skid); |
| goto error_cert_pop; |
| } |
| cert->skid = skid; |
| |
| /* |
| * Set the certificate flags: |
| * self_signed, unsupported_key, unsupported_sig, blacklisted |
| */ |
| ret = x509_set_cert_flags(cert); |
| if (!ret) { |
| *pcert = cert; |
| return 0; |
| } |
| |
| error_cert_pop: |
| x509_free_certificate(cert); |
| return ret; |
| } |
| |
| struct x509_certificate *x509_cert_parse(const void *data, size_t datalen) |
| { |
| mbedtls_x509_crt mbedtls_cert; |
| struct x509_certificate *cert = NULL; |
| long ret; |
| |
| /* Parse DER encoded certificate */ |
| mbedtls_x509_crt_init(&mbedtls_cert); |
| ret = mbedtls_x509_crt_parse_der(&mbedtls_cert, data, datalen); |
| if (ret) |
| goto clean_up_ctx; |
| |
| /* Populate x509_certificate from mbedtls_x509_crt */ |
| ret = x509_populate_cert(&mbedtls_cert, &cert); |
| if (ret) |
| goto clean_up_ctx; |
| |
| clean_up_ctx: |
| mbedtls_x509_crt_free(&mbedtls_cert); |
| if (!ret) |
| return cert; |
| |
| return ERR_PTR(ret); |
| } |