[PATCH v3 15/25] mbedtls: add X509 cert parser porting layer
Raymond Mao
raymond.mao at linaro.org
Tue May 28 16:09:26 CEST 2024
Add porting layer for X509 cert parser on top of MbedTLS X509
library.
Signed-off-by: Raymond Mao <raymond.mao at linaro.org>
---
Changes in v2
- Move the porting layer to MbedTLS dir.
Changes in v3
- None.
lib/mbedtls/Makefile | 1 +
lib/mbedtls/x509_cert_parser.c | 497 +++++++++++++++++++++++++++++++++
2 files changed, 498 insertions(+)
create mode 100644 lib/mbedtls/x509_cert_parser.c
diff --git a/lib/mbedtls/Makefile b/lib/mbedtls/Makefile
index cd0144eac1c..e7cba1ad17c 100644
--- a/lib/mbedtls/Makefile
+++ b/lib/mbedtls/Makefile
@@ -24,6 +24,7 @@ hash_mbedtls-$(CONFIG_$(SPL_)SHA512) += sha512.o
# x509 libraries
obj-$(CONFIG_MBEDTLS_LIB_X509) += x509_mbedtls.o
x509_mbedtls-$(CONFIG_$(SPL_)ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o
+x509_mbedtls-$(CONFIG_$(SPL_)X509_CERTIFICATE_PARSER) += x509_cert_parser.o
obj-$(CONFIG_MBEDTLS_LIB_CRYPTO) += mbedtls_lib_crypto.o
mbedtls_lib_crypto-y := \
diff --git a/lib/mbedtls/x509_cert_parser.c b/lib/mbedtls/x509_cert_parser.c
new file mode 100644
index 00000000000..b0867d31047
--- /dev/null
+++ b/lib/mbedtls/x509_cert_parser.c
@@ -0,0 +1,497 @@
+// 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) {
+ 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);
+}
+
+/*
+ * Check for self-signedness in an X.509 cert and if found, check the signature
+ * immediately if we can.
+ */
+int x509_check_for_self_signed(struct x509_certificate *cert)
+{
+ int ret = 0;
+
+ if (cert->raw_subject_size != cert->raw_issuer_size ||
+ memcmp(cert->raw_subject, cert->raw_issuer,
+ cert->raw_issuer_size) != 0)
+ goto not_self_signed;
+
+ if (cert->sig->auth_ids[0] || cert->sig->auth_ids[1]) {
+ /* If the AKID is present it may have one or two parts. If
+ * both are supplied, both must match.
+ */
+ bool a = asymmetric_key_id_same(cert->skid, cert->sig->auth_ids[1]);
+ bool b = asymmetric_key_id_same(cert->id, cert->sig->auth_ids[0]);
+
+ if (!a && !b)
+ goto not_self_signed;
+
+ ret = -EKEYREJECTED;
+ if (((a && !b) || (b && !a)) &&
+ cert->sig->auth_ids[0] && cert->sig->auth_ids[1])
+ goto out;
+ }
+
+ ret = -EKEYREJECTED;
+ if (strcmp(cert->pub->pkey_algo, cert->sig->pkey_algo) != 0)
+ goto out;
+
+ ret = public_key_verify_signature(cert->pub, cert->sig);
+ if (ret < 0) {
+ if (ret == -ENOPKG) {
+ cert->unsupported_sig = true;
+ ret = 0;
+ }
+ goto out;
+ }
+
+ pr_devel("Cert Self-signature verified");
+ cert->self_signed = true;
+
+out:
+ return ret;
+
+not_self_signed:
+ return 0;
+}
+
+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 RedDSA) from Red Hat 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
More information about the U-Boot
mailing list