mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-25 22:35:42 +00:00

Trusted Board Boot supports multiple hash algorithms, including SHA-256, SHA-384, and SHA-512. These algorithms produce hashes of different lengths, so the resulting DER-encoded hash objects are also of different lengths. However, the common Trusted Board Boot code only stores the contents of the object, not its length. Before commitf47547b354
, this was harmless: ASN.1 objects are self-delimiting, and any excess padding was ignored.f47547b354
changed the code to reject excess padding. However, this breaks using a shorter hash in a build that supports longer hashes: the shorter hash will have padding after it, and verify_hash() will reject it. This was found by an Arm customer: TF-A v2.9 refused to boot, even though TF-A v2.6 (which did not havef47547b354
) worked just fine. Storing the length of the hash turns out to be quite difficult. However, it turns out that hashes verified by verify_hash() always come from the ROTPK or an X.509 certificate extension. Furthermore, _all_ X.509 certificate extensions used by Trusted Board Boot are ASN.1 DER encoded, so it is possible to reject padding in get_ext(). Padding after the ROTPK is harmless, and it is better to ignore that padding than to refuse to boot the system. Change-Id: I28a19d7783e6036b65e86426d78c8e5b2ed6f542 Fixes:f47547b354
("fix(auth): reject invalid padding in digests") Signed-off-by: Demi Marie Obenour <demiobenour@gmail.com>
508 lines
13 KiB
C
508 lines
13 KiB
C
/*
|
|
* Copyright (c) 2015-2023, ARM Limited and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
/*
|
|
* X509 parser based on mbed TLS
|
|
*
|
|
* This module implements functions to check the integrity of a X509v3
|
|
* certificate ASN.1 structure and extract authentication parameters from the
|
|
* extensions field, such as an image hash or a public key.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
/* mbed TLS headers */
|
|
#include <mbedtls/asn1.h>
|
|
#include <mbedtls/oid.h>
|
|
#include <mbedtls/platform.h>
|
|
|
|
#include <arch_helpers.h>
|
|
#include <drivers/auth/img_parser_mod.h>
|
|
#include <drivers/auth/mbedtls/mbedtls_common.h>
|
|
#include <lib/utils.h>
|
|
|
|
/* Maximum OID string length ("a.b.c.d.e.f ...") */
|
|
#define MAX_OID_STR_LEN 64
|
|
|
|
#define LIB_NAME "mbed TLS X509v3"
|
|
|
|
/* Temporary variables to speed up the authentication parameters search. These
|
|
* variables are assigned once during the integrity check and used any time an
|
|
* authentication parameter is requested, so we do not have to parse the image
|
|
* again */
|
|
static mbedtls_asn1_buf tbs;
|
|
static mbedtls_asn1_buf v3_ext;
|
|
static mbedtls_asn1_buf pk;
|
|
static mbedtls_asn1_buf sig_alg;
|
|
static mbedtls_asn1_buf signature;
|
|
|
|
/*
|
|
* Clear all static temporary variables.
|
|
*/
|
|
static void clear_temp_vars(void)
|
|
{
|
|
#define ZERO_AND_CLEAN(x) \
|
|
do { \
|
|
zeromem(&x, sizeof(x)); \
|
|
clean_dcache_range((uintptr_t)&x, sizeof(x)); \
|
|
} while (0);
|
|
|
|
ZERO_AND_CLEAN(tbs)
|
|
ZERO_AND_CLEAN(v3_ext);
|
|
ZERO_AND_CLEAN(pk);
|
|
ZERO_AND_CLEAN(sig_alg);
|
|
ZERO_AND_CLEAN(signature);
|
|
|
|
#undef ZERO_AND_CLEAN
|
|
}
|
|
|
|
/*
|
|
* Get X509v3 extension
|
|
*
|
|
* Global variable 'v3_ext' must point to the extensions region
|
|
* in the certificate. OID may be NULL to request that get_ext()
|
|
* is only being called for integrity checking.
|
|
*/
|
|
static int get_ext(const char *oid, void **ext, unsigned int *ext_len)
|
|
{
|
|
int oid_len, ret, is_critical;
|
|
size_t len;
|
|
unsigned char *p;
|
|
const unsigned char *end;
|
|
char oid_str[MAX_OID_STR_LEN];
|
|
mbedtls_asn1_buf extn_oid;
|
|
|
|
p = v3_ext.p;
|
|
end = v3_ext.p + v3_ext.len;
|
|
|
|
/*
|
|
* Check extensions integrity. At least one extension is
|
|
* required: the ASN.1 specifies a minimum size of 1, and at
|
|
* least one extension is needed to authenticate the next stage
|
|
* in the boot chain.
|
|
*/
|
|
do {
|
|
unsigned char *end_ext_data;
|
|
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len,
|
|
MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
end_ext_data = p + len;
|
|
|
|
/* Get extension ID */
|
|
ret = mbedtls_asn1_get_tag(&p, end_ext_data, &extn_oid.len,
|
|
MBEDTLS_ASN1_OID);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
extn_oid.tag = MBEDTLS_ASN1_OID;
|
|
extn_oid.p = p;
|
|
p += extn_oid.len;
|
|
|
|
/* Get optional critical */
|
|
ret = mbedtls_asn1_get_bool(&p, end_ext_data, &is_critical);
|
|
if ((ret != 0) && (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG)) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
/*
|
|
* Data should be octet string type and must use all bytes in
|
|
* the Extension.
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end_ext_data, &len,
|
|
MBEDTLS_ASN1_OCTET_STRING);
|
|
if ((ret != 0) || ((p + len) != end_ext_data)) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
/* Detect requested extension */
|
|
oid_len = mbedtls_oid_get_numeric_string(oid_str,
|
|
MAX_OID_STR_LEN,
|
|
&extn_oid);
|
|
if ((oid_len == MBEDTLS_ERR_OID_BUF_TOO_SMALL) || (oid_len < 0)) {
|
|
return IMG_PARSER_ERR;
|
|
}
|
|
|
|
if ((oid != NULL) &&
|
|
((size_t)oid_len == strlen(oid_str)) &&
|
|
(strcmp(oid, oid_str) == 0)) {
|
|
/* Extension must be ASN.1 DER */
|
|
if (len < 2) {
|
|
/* too short */
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
if ((p[0] & 0x1F) == 0x1F) {
|
|
/* multi-byte ASN.1 DER tag, not allowed */
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
if ((p[0] & 0xDF) == 0) {
|
|
/* UNIVERSAL 0 tag, not allowed */
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
*ext = (void *)p;
|
|
*ext_len = (unsigned int)len;
|
|
|
|
/* Advance past the tag byte */
|
|
p++;
|
|
|
|
if (mbedtls_asn1_get_len(&p, end_ext_data, &len)) {
|
|
/* not valid DER */
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
if (p + len != end_ext_data) {
|
|
/* junk after ASN.1 object */
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
return IMG_PARSER_OK;
|
|
}
|
|
|
|
/* Next */
|
|
p = end_ext_data;
|
|
} while (p < end);
|
|
|
|
return (oid == NULL) ? IMG_PARSER_OK : IMG_PARSER_ERR_NOT_FOUND;
|
|
}
|
|
|
|
|
|
/*
|
|
* Check the integrity of the certificate ASN.1 structure.
|
|
*
|
|
* Extract the relevant data that will be used later during authentication.
|
|
*
|
|
* This function doesn't clear the static variables located on the top of this
|
|
* file in case of an error. It is only called from check_integrity(), which
|
|
* performs the cleanup if necessary.
|
|
*/
|
|
static int cert_parse(void *img, unsigned int img_len)
|
|
{
|
|
int ret;
|
|
size_t len;
|
|
unsigned char *p, *end, *crt_end, *pk_end;
|
|
mbedtls_asn1_buf sig_alg1;
|
|
/*
|
|
* The unique ASN.1 DER encoding of [0] EXPLICIT INTEGER { v3(2} }.
|
|
*/
|
|
static const char v3[] = {
|
|
/* The outer CONTEXT SPECIFIC 0 tag */
|
|
MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_CONTEXT_SPECIFIC | 0,
|
|
/* The number bytes used to encode the inner INTEGER */
|
|
3,
|
|
/* The tag of the inner INTEGER */
|
|
MBEDTLS_ASN1_INTEGER,
|
|
/* The number of bytes needed to represent 2 */
|
|
1,
|
|
/* The actual value 2 */
|
|
2,
|
|
};
|
|
|
|
p = (unsigned char *)img;
|
|
len = img_len;
|
|
crt_end = p + len;
|
|
end = crt_end;
|
|
|
|
/*
|
|
* Certificate ::= SEQUENCE {
|
|
* tbsCertificate TBSCertificate,
|
|
* signatureAlgorithm AlgorithmIdentifier,
|
|
* signatureValue BIT STRING }
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if ((ret != 0) || ((p + len) != end)) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
/*
|
|
* TBSCertificate ::= SEQUENCE {
|
|
*/
|
|
tbs.p = p;
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
end = p + len;
|
|
tbs.len = end - tbs.p;
|
|
|
|
/*
|
|
* Version ::= [0] EXPLICIT INTEGER { v1(0), v2(1), v3(2) }
|
|
* -- only v3 accepted
|
|
*/
|
|
if (((end - p) <= (ptrdiff_t)sizeof(v3)) ||
|
|
(memcmp(p, v3, sizeof(v3)) != 0)) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += sizeof(v3);
|
|
|
|
/*
|
|
* CertificateSerialNumber ::= INTEGER
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_INTEGER);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += len;
|
|
|
|
/*
|
|
* signature AlgorithmIdentifier
|
|
*/
|
|
sig_alg1.p = p;
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
sig_alg1.len = (p + len) - sig_alg1.p;
|
|
p += len;
|
|
|
|
/*
|
|
* issuer Name
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += len;
|
|
|
|
/*
|
|
* Validity ::= SEQUENCE {
|
|
* notBefore Time,
|
|
* notAfter Time }
|
|
*
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += len;
|
|
|
|
/*
|
|
* subject Name
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += len;
|
|
|
|
/*
|
|
* SubjectPublicKeyInfo
|
|
*/
|
|
pk.p = p;
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
pk_end = p + len;
|
|
pk.len = pk_end - pk.p;
|
|
|
|
/* algorithm */
|
|
ret = mbedtls_asn1_get_tag(&p, pk_end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += len;
|
|
|
|
/* Key is a BIT STRING and must use all bytes in SubjectPublicKeyInfo */
|
|
ret = mbedtls_asn1_get_bitstring_null(&p, pk_end, &len);
|
|
if ((ret != 0) || (p + len != pk_end)) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p = pk_end;
|
|
|
|
/*
|
|
* issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
* subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
|
|
* -- technically these contain BIT STRINGs but that is not worth
|
|
* -- validating
|
|
*/
|
|
for (int i = 1; i < 3; i++) {
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len,
|
|
MBEDTLS_ASN1_CONTEXT_SPECIFIC |
|
|
MBEDTLS_ASN1_CONSTRUCTED | i);
|
|
/*
|
|
* Unique IDs are obsolete, so MBEDTLS_ERR_ASN1_UNEXPECTED_TAG
|
|
* is the common case.
|
|
*/
|
|
if (ret != MBEDTLS_ERR_ASN1_UNEXPECTED_TAG) {
|
|
if (ret != 0) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += len;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* extensions [3] EXPLICIT Extensions OPTIONAL
|
|
* }
|
|
*
|
|
* X.509 and RFC5280 allow omitting the extensions entirely.
|
|
* However, in TF-A, a certificate with no extensions would
|
|
* always fail later on, as the extensions contain the
|
|
* information needed to authenticate the next stage in the
|
|
* boot chain. Furthermore, get_ext() assumes that the
|
|
* extensions have been parsed into v3_ext, and allowing
|
|
* there to be no extensions would pointlessly complicate
|
|
* the code. Therefore, just reject certificates without
|
|
* extensions. This is also why version 1 and 2 certificates
|
|
* are rejected above.
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len,
|
|
MBEDTLS_ASN1_CONTEXT_SPECIFIC |
|
|
MBEDTLS_ASN1_CONSTRUCTED | 3);
|
|
if ((ret != 0) || (len != (size_t)(end - p))) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
|
|
/*
|
|
* Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
|
* -- must use all remaining bytes in TBSCertificate
|
|
*/
|
|
ret = mbedtls_asn1_get_tag(&p, end, &len, MBEDTLS_ASN1_CONSTRUCTED |
|
|
MBEDTLS_ASN1_SEQUENCE);
|
|
if ((ret != 0) || (len != (size_t)(end - p))) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
v3_ext.p = p;
|
|
v3_ext.len = len;
|
|
p += len;
|
|
|
|
/* Check extensions integrity */
|
|
ret = get_ext(NULL, NULL, NULL);
|
|
if (ret != IMG_PARSER_OK) {
|
|
return ret;
|
|
}
|
|
|
|
end = crt_end;
|
|
|
|
/*
|
|
* }
|
|
* -- end of TBSCertificate
|
|
*
|
|
* signatureAlgorithm AlgorithmIdentifier
|
|
* -- Does not need to be parsed. Ensuring it is bitwise
|
|
* -- identical (including the tag!) with the first signature
|
|
* -- algorithm is sufficient.
|
|
*/
|
|
if ((sig_alg1.len >= (size_t)(end - p)) ||
|
|
(0 != memcmp(sig_alg1.p, p, sig_alg1.len))) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
p += sig_alg1.len;
|
|
memcpy(&sig_alg, &sig_alg1, sizeof(sig_alg));
|
|
|
|
/*
|
|
* signatureValue BIT STRING
|
|
* } -- must consume all bytes
|
|
*/
|
|
signature.p = p;
|
|
ret = mbedtls_asn1_get_bitstring_null(&p, end, &len);
|
|
if ((ret != 0) || ((p + len) != end)) {
|
|
return IMG_PARSER_ERR_FORMAT;
|
|
}
|
|
signature.len = end - signature.p;
|
|
|
|
return IMG_PARSER_OK;
|
|
}
|
|
|
|
|
|
/* Exported functions */
|
|
|
|
static void init(void)
|
|
{
|
|
mbedtls_init();
|
|
}
|
|
|
|
/*
|
|
* Wrapper for cert_parse() that clears the static variables used by it in case
|
|
* of an error.
|
|
*/
|
|
static int check_integrity(void *img, unsigned int img_len)
|
|
{
|
|
int rc = cert_parse(img, img_len);
|
|
|
|
if (rc != IMG_PARSER_OK)
|
|
clear_temp_vars();
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Extract an authentication parameter from an X509v3 certificate
|
|
*
|
|
* This function returns a pointer to the extracted data and its length.
|
|
* Depending on the type of parameter, a pointer to the data stored in the
|
|
* certificate may be returned (i.e. an octet string containing a hash). Other
|
|
* data may need to be copied and formatted (i.e. integers). In the later case,
|
|
* a buffer of the correct type needs to be statically allocated, filled and
|
|
* returned.
|
|
*/
|
|
static int get_auth_param(const auth_param_type_desc_t *type_desc,
|
|
void *img, unsigned int img_len,
|
|
void **param, unsigned int *param_len)
|
|
{
|
|
int rc = IMG_PARSER_OK;
|
|
|
|
/* We do not use img because the check_integrity function has already
|
|
* extracted the relevant data (v3_ext, pk, sig_alg, etc) */
|
|
|
|
switch (type_desc->type) {
|
|
case AUTH_PARAM_RAW_DATA:
|
|
/* Data to be signed */
|
|
*param = (void *)tbs.p;
|
|
*param_len = (unsigned int)tbs.len;
|
|
break;
|
|
case AUTH_PARAM_HASH:
|
|
case AUTH_PARAM_NV_CTR:
|
|
/* All these parameters are included as X509v3 extensions */
|
|
rc = get_ext(type_desc->cookie, param, param_len);
|
|
break;
|
|
case AUTH_PARAM_PUB_KEY:
|
|
if (type_desc->cookie != NULL) {
|
|
/* Get public key from extension */
|
|
rc = get_ext(type_desc->cookie, param, param_len);
|
|
} else {
|
|
/* Get the subject public key */
|
|
*param = (void *)pk.p;
|
|
*param_len = (unsigned int)pk.len;
|
|
}
|
|
break;
|
|
case AUTH_PARAM_SIG_ALG:
|
|
/* Get the certificate signature algorithm */
|
|
*param = (void *)sig_alg.p;
|
|
*param_len = (unsigned int)sig_alg.len;
|
|
break;
|
|
case AUTH_PARAM_SIG:
|
|
/* Get the certificate signature */
|
|
*param = (void *)signature.p;
|
|
*param_len = (unsigned int)signature.len;
|
|
break;
|
|
default:
|
|
rc = IMG_PARSER_ERR_NOT_FOUND;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
REGISTER_IMG_PARSER_LIB(IMG_CERT, LIB_NAME, init,
|
|
check_integrity, get_auth_param);
|