From patchwork Tue May 7 17:51:00 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Raymond Mao X-Patchwork-Id: 1932585 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=yfoYfIf+; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org) Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VYmJD6Syjz1xnT for ; Wed, 8 May 2024 03:59:40 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 6915F887D8; Tue, 7 May 2024 19:59:38 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=linaro.org header.i=@linaro.org header.b="yfoYfIf+"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 7C201883E8; Tue, 7 May 2024 19:59:37 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-oi1-x232.google.com (mail-oi1-x232.google.com [IPv6:2607:f8b0:4864:20::232]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id C2818887DA for ; Tue, 7 May 2024 19:59:34 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=raymond.mao@linaro.org Received: by mail-oi1-x232.google.com with SMTP id 5614622812f47-3c963880aecso1291635b6e.3 for ; Tue, 07 May 2024 10:59:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1715104773; x=1715709573; darn=lists.denx.de; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=+aOCMhEbOfr7nIXqYrtDD7Pa6CDYsevfphDD4StEDmE=; b=yfoYfIf+pZXZV72HveXrnFKDoGflyBl5m+t5t2OnNTWLV6BRhHSftZWlqBURcwV/pt yIhj3fbYNWch8es1WK41E81pD34WfigU6/cX3APOe9ALjj30zqX4cNUyRARl9+tloQKP fseVGC6z5gj0Mg/wGzc0K5aFqRpRJWSc6RFMLJ83X4NRfo0U8r4PikVXbBDYdkcHBFOO ZtL690fvWE/oFU1tG8INF5WtxKcQfLiD9+PwcOwhziAt8uQ0eP138V7YabLC8g6/9AxY k1c0Y95EbpVbPDUQG1reosLcy1+nesSK3K/2N8tZvS9GqQCDbx5tmf5M1klPilU6qMIj x3vg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1715104773; x=1715709573; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=+aOCMhEbOfr7nIXqYrtDD7Pa6CDYsevfphDD4StEDmE=; b=MFqn8DtIcAOe6+Dupyi6sgUpTVw7QHJH9KFdU4e3zSA98WY2ElfZb6Ghwg2QAaNk+o wCaalW4kjLzapH0cyV/raH3675lalyeQLJdqnKaNNSdUaKw2rgQocILATt5xmbn6Y0v5 nY2sazykIa7pONp1fHinnwEbxQfM6t7FcCz2Nk4pC69fXo1H6eevfs4FjkFSG+wX0HlR LJjMOy3+oHN75yijpztIy7V1z5V42WYc9+PbPCYdXZQXCuWivjHryfUwSuaDlEaAo/sw OL84neSwt8qNUTs0VyzXRX/exaBZ7veVafLHIJB0e+5wPZ5hsK7Fy57o5vtYlnpqIOzB X2OA== X-Gm-Message-State: AOJu0YwLUhDUjItu8rIlrQDmYbK9sxMYGROrm3v5DsNbNUEZRawX5aoW urm/Kd+JyY2XihhEl4ufakd7SX7tTf4tTK1kDGozqhcsQBXag9sskaYjSL4bASPb/DuXQt6BDTA u X-Google-Smtp-Source: AGHT+IHlrrdbJggyWbewQ+q2DuGDnA31NtiEa65wJQbpc32LTTTZO5zSAi2yO+UeLcgl++mLXFozsA== X-Received: by 2002:a05:6808:b2f:b0:3c9:69b3:8f6a with SMTP id 5614622812f47-3c9852bd81amr311937b6e.19.1715104773177; Tue, 07 May 2024 10:59:33 -0700 (PDT) Received: from ubuntu.localdomain (pool-174-115-4-214.cpe.net.cable.rogers.com. [174.115.4.214]) by smtp.gmail.com with ESMTPSA id o1-20020a0cecc1000000b006a0f126a033sm4801834qvq.28.2024.05.07.10.59.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 07 May 2024 10:59:32 -0700 (PDT) From: Raymond Mao To: u-boot@lists.denx.de Cc: Raymond Mao , Tom Rini , Heinrich Schuchardt , Ilias Apalodimas , Tuomas Tynkkynen , Simon Glass , Leo Yu-Chi Liang , Andrejs Cainikovs , Marek Vasut , Sean Anderson , Andrew Davis , Sumit Garg , Christophe Leroy , Jesse Taube , Bryan Brattlof , "Leon M. Busch-George" , Ralph Siemsen , Igor Opaniuk , Ilya Lukin <4.shket@gmail.com>, Sergei Antonov , Bin Meng , Alper Nebi Yasak , Abdellatif El Khlifi , AKASHI Takahiro , Alexander Gendin , Eddie James , =?utf-8?q?Vincent_Stehl=C3=A9?= , Oleksandr Suvorov , Masahisa Kojima Subject: [PATCH v2 18/28] mbedtls: add X509 cert parser porting layer Date: Tue, 7 May 2024 10:51:00 -0700 Message-Id: <20240507175132.1456512-19-raymond.mao@linaro.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20240507175132.1456512-1-raymond.mao@linaro.org> References: <20240507175132.1456512-1-raymond.mao@linaro.org> MIME-Version: 1.0 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.39 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.103.8 at phobos.denx.de X-Virus-Status: Clean Add porting layer for X509 cert parser on top of MbedTLS X509 library. Signed-off-by: Raymond Mao --- Changes in v2 - Move the porting layer to MbedTLS dir. 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 + */ + +#include +#include +#include + +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); +}