[PATCH v8 17/27] mbedtls: add X509 cert parser porting layer
Ilias Apalodimas
ilias.apalodimas at linaro.org
Wed Oct 9 11:16:47 CEST 2024
On Fri, 4 Oct 2024 at 01:00, Raymond Mao <raymond.mao at linaro.org> wrote:
>
> Add porting layer for X509 cert parser on top of MbedTLS X509
> library.
> Introduce _LEGACY and _MBEDTLS kconfigs for X509 cert parser legacy
> and MbedTLS implementations respectively.
>
> Signed-off-by: Raymond Mao <raymond.mao at linaro.org>
> ---
> Changes in v2
> - Move the porting layer to MbedTLS dir.
> Changes in v3
> - None.
> Changes in v4
> - Introduce _LEGACY and _MBEDTLS kconfigs for X509 cert parser legacy
> and MbedTLS implementations respectively.
> - Move common functions to helper.
> Changes in v5
> - Kconfig rename.
> - Adjust a few inline comments.
> Changes in v6
> - None.
> Changes in v7
> - None.
> Changes in v8
> - None
>
> lib/mbedtls/Kconfig | 18 ++
> lib/mbedtls/Makefile | 4 +-
> lib/mbedtls/x509_cert_parser.c | 447 +++++++++++++++++++++++++++++++++
> 3 files changed, 468 insertions(+), 1 deletion(-)
> create mode 100644 lib/mbedtls/x509_cert_parser.c
>
> diff --git a/lib/mbedtls/Kconfig b/lib/mbedtls/Kconfig
> index e81d14505ff..abdafd04e89 100644
> --- a/lib/mbedtls/Kconfig
> +++ b/lib/mbedtls/Kconfig
> @@ -118,6 +118,7 @@ config LEGACY_CRYPTO_CERT
> bool "legacy certificate libraries"
> select ASYMMETRIC_PUBLIC_KEY_LEGACY if \
> ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> + select X509_CERTIFICATE_PARSER_LEGACY if X509_CERTIFICATE_PARSER
> select SPL_ASYMMETRIC_PUBLIC_KEY_LEGACY if \
> SPL_ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> help
> @@ -132,6 +133,14 @@ config ASYMMETRIC_PUBLIC_KEY_LEGACY
> This option chooses legacy certificate library for asymmetric public
> key crypto algorithm.
>
> +config X509_CERTIFICATE_PARSER_LEGACY
> + bool "X.509 certificate parser with legacy certificate library"
> + depends on ASYMMETRIC_PUBLIC_KEY_LEGACY
> + select ASN1_DECODER_LEGACY
> + help
> + This option chooses legacy certificate library for X509 certificate
> + parser.
> +
> if SPL
>
> config SPL_ASYMMETRIC_PUBLIC_KEY_LEGACY
> @@ -283,6 +292,7 @@ config MBEDTLS_LIB_X509
> bool "MbedTLS certificate libraries"
> select ASYMMETRIC_PUBLIC_KEY_MBEDTLS if \
> ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> + select X509_CERTIFICATE_PARSER_MBEDTLS if X509_CERTIFICATE_PARSER
> select SPL_ASYMMETRIC_PUBLIC_KEY_MBEDTLS if \
> SPL_ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> help
> @@ -297,6 +307,14 @@ config ASYMMETRIC_PUBLIC_KEY_MBEDTLS
> This option chooses MbedTLS certificate library for asymmetric public
> key crypto algorithm.
>
> +config X509_CERTIFICATE_PARSER_MBEDTLS
> + bool "X.509 certificate parser with MbedTLS certificate library"
> + depends on ASYMMETRIC_PUBLIC_KEY_MBEDTLS
> + select ASN1_DECODER_MBEDTLS
> + help
> + This option chooses MbedTLS certificate library for X509 certificate
> + parser.
> +
> if SPL
>
> config SPL_ASYMMETRIC_PUBLIC_KEY_MBEDTLS
> diff --git a/lib/mbedtls/Makefile b/lib/mbedtls/Makefile
> index d3f566d0c91..29653323279 100644
> --- a/lib/mbedtls/Makefile
> +++ b/lib/mbedtls/Makefile
> @@ -14,6 +14,8 @@ obj-$(CONFIG_$(SPL_)SHA512_MBEDTLS) += sha512.o
> # x509 libraries
> obj-$(CONFIG_$(SPL_)ASYMMETRIC_PUBLIC_KEY_MBEDTLS) += \
> public_key.o
> +obj-$(CONFIG_$(SPL_)X509_CERTIFICATE_PARSER_MBEDTLS) += \
> + x509_cert_parser.o
>
> # MbedTLS crypto library
> obj-$(CONFIG_MBEDTLS_LIB) += mbedtls_lib_crypto.o
> @@ -44,7 +46,7 @@ mbedtls_lib_x509-$(CONFIG_$(SPL_)ASYMMETRIC_PUBLIC_KEY_MBEDTLS) += \
> $(MBEDTLS_LIB_DIR)/pk.o \
> $(MBEDTLS_LIB_DIR)/pk_wrap.o \
> $(MBEDTLS_LIB_DIR)/pkparse.o
> -mbedtls_lib_x509-$(CONFIG_$(SPL_)X509_CERTIFICATE_PARSER) += \
> +mbedtls_lib_x509-$(CONFIG_$(SPL_)X509_CERTIFICATE_PARSER_MBEDTLS) += \
> $(MBEDTLS_LIB_DIR)/x509_crl.o \
> $(MBEDTLS_LIB_DIR)/x509_crt.o
> mbedtls_lib_x509-$(CONFIG_$(SPL_)PKCS7_MESSAGE_PARSER) += \
> diff --git a/lib/mbedtls/x509_cert_parser.c b/lib/mbedtls/x509_cert_parser.c
> new file mode 100644
> index 00000000000..cb42018695c
> --- /dev/null
> +++ b/lib/mbedtls/x509_cert_parser.c
> @@ -0,0 +1,447 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * X509 cert parser using MbedTLS X509 library
> + *
> + * Copyright (c) 2024 Linaro Limited
> + * Author: Raymond Mao <raymond.mao at 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;
> + size_t 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);
> +}
> --
> 2.25.1
>
Acked-by: Ilias Apalodimas <ilias.apalodimas at linaro.org>
More information about the U-Boot
mailing list