diff --git a/sdk/bolos_syscalls_unified_sdk.h b/sdk/bolos_syscalls_unified_sdk.h index 75201203..055cdcf1 100644 --- a/sdk/bolos_syscalls_unified_sdk.h +++ b/sdk/bolos_syscalls_unified_sdk.h @@ -183,3 +183,7 @@ #define SYSCALL_cx_hash_to_field_ID_IN 0x06000104 #define SYSCALL_cx_bls12381_aggregate_ID_IN 0x05000105 #define SYSCALL_cx_bls12381_key_gen_ID_IN 0x03000108 + +#define SYSCALL_os_pki_load_certificate_ID_IN 0x060000aa +#define SYSCALL_os_pki_verify_ID_IN 0x040000ab +#define SYSCALL_os_pki_get_info_ID_IN 0x040000ac diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 34dc0f10..577318e1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,8 @@ add_library(emu bolos/cx_hash.c bolos/cx_math.c bolos/default.c + bolos/os_pki.c + bolos/os_signature.c emulate.c emulate_1.2.c emulate_1.5.c diff --git a/src/bolos/cx_hash.c b/src/bolos/cx_hash.c index c98b34bc..9372252e 100644 --- a/src/bolos/cx_hash.c +++ b/src/bolos/cx_hash.c @@ -151,6 +151,7 @@ const cx_hash_info_t *spec_cx_hash_get_info(cx_md_t md_type) case CX_SHA512: return &cx_sha512_info; case CX_SHA3: + case CX_SHA3_256: return &cx_sha3_info; case CX_KECCAK: return &cx_keccak_info; diff --git a/src/bolos/cx_hash.h b/src/bolos/cx_hash.h index 70a985a7..b56dde09 100644 --- a/src/bolos/cx_hash.h +++ b/src/bolos/cx_hash.h @@ -8,6 +8,7 @@ #define CX_SHA256_SIZE 32 #define CX_SHA384_SIZE 48 #define CX_SHA512_SIZE 64 +#define CX_SHA3_256_SIZE 32 #define RIPEMD_BLOCK_SIZE 64 #define SHA256_BLOCK_SIZE 64 @@ -44,6 +45,8 @@ enum cx_md_e { CX_SHAKE128, // any bytes /** SHAKE-128 Digest */ CX_SHAKE256, // any bytes + /** SHA3 256*/ + CX_SHA3_256, }; /** Convenience type. See #cx_md_e. */ typedef enum cx_md_e cx_md_t; diff --git a/src/bolos/cx_utils.c b/src/bolos/cx_utils.c index 52398e3c..ce3a0307 100644 --- a/src/bolos/cx_utils.c +++ b/src/bolos/cx_utils.c @@ -305,6 +305,17 @@ void U4BE_ENCODE(uint8_t *buf, size_t off, uint32_t value) buf[off + 3] = value & 0xFF; } +uint16_t U2BE(const uint8_t *buf, size_t off) +{ + return (buf[off] << 8) | buf[off + 1]; +} + +uint32_t U4BE(const uint8_t *buf, size_t off) +{ + return (((uint32_t)buf[off]) << 24) | (buf[off + 1] << 16) | + (buf[off + 2] << 8) | buf[off + 3]; +} + void cx_memxor(uint8_t *buf1, const uint8_t *buf2, size_t len) { size_t i; diff --git a/src/bolos/cx_utils.h b/src/bolos/cx_utils.h index c8e3b629..23a85c93 100644 --- a/src/bolos/cx_utils.h +++ b/src/bolos/cx_utils.h @@ -110,4 +110,6 @@ int get_path(const char *str_, unsigned int *path, int max_path_len); void U2BE_ENCODE(uint8_t *buf, size_t off, uint32_t value); void U4BE_ENCODE(uint8_t *buf, size_t off, uint32_t value); +uint16_t U2BE(const uint8_t *buf, size_t off); +uint32_t U4BE(const uint8_t *buf, size_t off); void cx_memxor(uint8_t *buf1, const uint8_t *buf2, size_t len); diff --git a/src/bolos/os_pki.c b/src/bolos/os_pki.c new file mode 100644 index 00000000..f45cefa8 --- /dev/null +++ b/src/bolos/os_pki.c @@ -0,0 +1,342 @@ +#define _SDK_2_0_ +#include +#include +#include +#include + +#include "cx.h" +#include "cx_utils.h" +#include "os_pki.h" +#include "os_signature.h" + +#define OS_PKI_DIGEST_MAX_LEN (64) +#define OS_PKI_TLV_TAG_OFFSET (0) +#define OS_PKI_TLV_LENGTH_OFFSET (1) +#define OS_PKI_TLV_VALUE_OFFSET (2) +#define OS_PKI_SEMVER_VERSION_LEN (6) + +typedef struct os_pki_s { + uint8_t key_usage; + uint8_t signer_sign_algo; + uint8_t pk_sign_algo; + uint8_t trusted_name[CERTIFICATE_TRUSTED_NAME_MAXLEN]; + size_t trusted_name_len; + uint16_t signer_id; + cx_ecfp_384_public_key_t public_key; +} os_pki_t; + +static os_pki_t os_pki; + +uint32_t os_pki_check_value(uint8_t *certificate_value, + cx_ecfp_384_public_key_t *cert_public_key) +{ + size_t domain_len; + cx_ecpoint_t point; + uint32_t swo_error = 0x9000; + cx_err_t error = CX_OK; + char cert_validity[OS_PKI_SEMVER_VERSION_LEN] = { 0 }; + uint8_t tag = certificate_value[OS_PKI_TLV_TAG_OFFSET]; + size_t version_nchars = 0; + + switch (tag) { + case CERTIFICATE_TAG_STRUCTURE_TYPE: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] != + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x422F; + } + break; + case CERTIFICATE_TAG_VERSION: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4230; + } + break; + case CERTIFICATE_TAG_VALIDITY: + if (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len) { + return 0x4231; + } + cert_validity[version_nchars] = + '0' + certificate_value[OS_PKI_TLV_VALUE_OFFSET]; + cert_validity[++version_nchars] = '.'; + cert_validity[++version_nchars] = + '0' + certificate_value[OS_PKI_TLV_VALUE_OFFSET + 1]; + cert_validity[++version_nchars] = '.'; + cert_validity[++version_nchars] = + '0' + certificate_value[OS_PKI_TLV_VALUE_OFFSET + 2]; + // Handle patch version greater than 10 + if (certificate_value[OS_PKI_TLV_VALUE_OFFSET + 2] >= 10) { + cert_validity[version_nchars] = + '0' + certificate_value[OS_PKI_TLV_VALUE_OFFSET + 2] / 10; + cert_validity[++version_nchars] = + '0' + certificate_value[OS_PKI_TLV_VALUE_OFFSET + 2] % 10; + } + if (strncmp(VERSION, cert_validity, version_nchars + 1) > 0) { + return 0x4231; + } + break; + case CERTIFICATE_TAG_VALIDITY_INDEX: + if ((U4BE(certificate_value, OS_PKI_TLV_VALUE_OFFSET) <= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4232; + } + break; + case CERTIFICATE_TAG_CHALLENGE: + break; + case CERTIFICATE_TAG_SIGNER_KEY_ID: + if ((U2BE(certificate_value, OS_PKI_TLV_VALUE_OFFSET) >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4233; + } + os_pki.signer_id = U2BE(certificate_value, OS_PKI_TLV_VALUE_OFFSET); + break; + case CERTIFICATE_TAG_SIGN_ALGO_ID: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4234; + } + os_pki.signer_sign_algo = certificate_value[OS_PKI_TLV_VALUE_OFFSET]; + break; + case CERTIFICATE_TAG_TIME_VALIDITY: + if (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len) { + return 0x423B; + } + break; + case CERTIFICATE_TAG_PUBLIC_KEY_ID: + if ((U2BE(certificate_value, OS_PKI_TLV_VALUE_OFFSET) >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4235; + } + break; + case CERTIFICATE_TAG_TRUSTED_NAME: + if (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] > + CERTIFICATE_TRUSTED_NAME_MAXLEN) { + return 0x423A; + } + memcpy(os_pki.trusted_name, certificate_value + OS_PKI_TLV_VALUE_OFFSET, + certificate_value[OS_PKI_TLV_LENGTH_OFFSET]); + os_pki.trusted_name_len = certificate_value[OS_PKI_TLV_LENGTH_OFFSET]; + break; + case CERTIFICATE_TAG_PUBLIC_KEY_USAGE: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4236; + } + os_pki.key_usage = certificate_value[OS_PKI_TLV_VALUE_OFFSET]; + break; + case CERTIFICATE_TAG_PUBLIC_KEY_CURVE_ID: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4237; + } + cert_public_key->curve = certificate_value[OS_PKI_TLV_VALUE_OFFSET]; + break; + case CERTIFICATE_TAG_COMPRESSED_PUBLIC_KEY: + /* Decompress key and initialize os_pki.public_key */ + CX_CHECK( + sys_cx_ecdomain_parameters_length(cert_public_key->curve, &domain_len)); + CX_CHECK(sys_cx_bn_lock(domain_len, 0)); + CX_CHECK(sys_cx_ecpoint_alloc(&point, cert_public_key->curve)); + /* If the prefix is 02, the y-coordinate is even otherwise if the prefix is + * 03 the y-coordinate is odd */ + CX_CHECK(sys_cx_ecpoint_decompress( + &point, certificate_value + OS_PKI_TLV_VALUE_OFFSET + 1, + certificate_value[OS_PKI_TLV_LENGTH_OFFSET] - 1, + certificate_value[OS_PKI_TLV_VALUE_OFFSET] & 0x1)); + cert_public_key->W[0] = 0x04; + CX_CHECK(sys_cx_ecpoint_export(&point, cert_public_key->W + 1, domain_len, + cert_public_key->W + 1 + domain_len, + domain_len)); + cert_public_key->W_len = 2 * domain_len + 1; + sys_cx_bn_unlock(); + break; + case CERTIFICATE_TAG_PK_SIGN_ALGO_ID: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4238; + } + os_pki.pk_sign_algo = certificate_value[OS_PKI_TLV_VALUE_OFFSET]; + break; + case CERTIFICATE_TAG_TARGET_DEVICE: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x4239; + } + break; + case CERTIFICATE_TAG_DEPTH: + if ((certificate_value[OS_PKI_TLV_VALUE_OFFSET] >= + C_os_pki_certificate_tag_info[tag].value) || + (certificate_value[OS_PKI_TLV_LENGTH_OFFSET] != + C_os_pki_certificate_tag_info[tag].field_len)) { + return 0x423C; + } + break; + default: + return 0x422D; + } + +end: + sys_cx_bn_unlock(); + if (error) { + swo_error = 0x3302; + } + return swo_error; +} + +static void os_pki_get_next_tag(uint8_t *certificate, uint32_t *offset) +{ + uint8_t tag = certificate[*offset]; + (*offset)++; + if (C_os_pki_certificate_tag_info[tag].field_len == + CERTIFICATE_FIELD_VAR_LEN) { + *offset += 1 + certificate[*offset]; + } else { + *offset += 1 + C_os_pki_certificate_tag_info[tag].field_len; + } +} + +static cx_sign_algo_t os_pki_get_signature_algorithm(uint8_t cert_sign_algo_id) +{ + switch (cert_sign_algo_id) { + case CERTIFICATE_SIGN_ALGO_ID_ECDSA_SHA256: + case CERTIFICATE_SIGN_ALGO_ID_ECDSA_SHA3_256: + case CERTIFICATE_SIGN_ALGO_ID_ECDSA_KECCAK_256: + case CERTIFICATE_SIGN_ALGO_ID_ECDSA_RIPEMD160: + return CX_SIGN_ALGO_ECDSA_RND; + case CERTIFICATE_SIGN_ALGO_ID_EDDSA_SHA512: + return CX_SIGN_ALGO_EDDSA_SHA512; + default: + return CX_SIGN_ALGO_UNKNOWN; + } + + return CX_SIGN_ALGO_UNKNOWN; +} + +uint32_t sys_os_pki_load_certificate(uint8_t expected_key_usage, + uint8_t *certificate, + size_t certificate_len, + uint8_t *trusted_name, + size_t *trusted_name_len, + cx_ecfp_384_public_key_t *public_key) +{ + uint32_t offset; + uint8_t cert_hash[OS_PKI_DIGEST_MAX_LEN] = { 0 }; + bool is_verified = false; + cx_hash_ctx hash_ctx; + size_t digest_len; + cx_sign_algo_t sign_algo; + uint32_t swo_error = 0x9000; + + for (offset = 0; offset < certificate_len;) { + if (CERTIFICATE_TAG_SIGNATURE == certificate[offset]) { + break; + } + if ((swo_error = os_pki_check_value(certificate + offset, public_key)) != + 0x9000) { + explicit_bzero(&os_pki, sizeof(os_pki)); + return swo_error; + } + os_pki_get_next_tag(certificate, &offset); + } + + if (expected_key_usage != os_pki.key_usage) { + explicit_bzero(&os_pki, sizeof(os_pki)); + return 0x422E; + } + + if ((CX_SHA3_256 == C_os_sign_algo_hash_info[os_pki.signer_sign_algo]) || + (CX_KECCAK == C_os_sign_algo_hash_info[os_pki.signer_sign_algo])) { + spec_cx_hash_init_ex(&hash_ctx, + C_os_sign_algo_hash_info[os_pki.signer_sign_algo], + CX_SHA3_256_SIZE); + } else { + spec_cx_hash_init(&hash_ctx, + C_os_sign_algo_hash_info[os_pki.signer_sign_algo]); + } + + spec_cx_hash_update(&hash_ctx, certificate, offset); + + spec_cx_hash_final(&hash_ctx, cert_hash); + digest_len = spec_cx_hash_get_size(&hash_ctx); + explicit_bzero(&hash_ctx, sizeof(hash_ctx)); + + // Skip signature tag + offset++; + switch (os_pki.signer_id) { + case CERTIFICATE_KEY_ID_LEDGER_ROOT_V3: + is_verified = os_ecdsa_verify_with_root_ca( + ROOT_CA_V3_KEY_ID, cert_hash, digest_len, + certificate + offset + OS_PKI_TLV_LENGTH_OFFSET, certificate[offset]); + break; + default: + sign_algo = os_pki_get_signature_algorithm(os_pki.signer_sign_algo); + is_verified = cx_verify( + sign_algo, (cx_ecfp_public_key_t *)&os_pki.public_key, cert_hash, + digest_len, certificate + offset + OS_PKI_TLV_LENGTH_OFFSET, + certificate[offset]); + break; + } + + if (!is_verified) { + explicit_bzero(&os_pki, sizeof(os_pki)); + return 0x5720; + } + + cx_ecdsa_internal_init_public_key(public_key->curve, public_key->W, + public_key->W_len, + (cx_ecfp_public_key_t *)&os_pki.public_key); + + if ((trusted_name != NULL) && (trusted_name_len != NULL)) { + memcpy(trusted_name, os_pki.trusted_name, os_pki.trusted_name_len); + *trusted_name_len = os_pki.trusted_name_len; + } + + return swo_error; +} + +bool sys_os_pki_verify(uint8_t *descriptor_hash, size_t descriptor_hash_len, + uint8_t *signature, size_t signature_len) +{ + cx_sign_algo_t sign_algo = + os_pki_get_signature_algorithm(os_pki.pk_sign_algo); + + return cx_verify(sign_algo, (cx_ecfp_public_key_t *)&os_pki.public_key, + descriptor_hash, descriptor_hash_len, signature, + signature_len); +} + +uint32_t sys_os_pki_get_info(uint8_t *key_usage, uint8_t *trusted_name, + size_t *trusted_name_len, + cx_ecfp_384_public_key_t *public_key) +{ + *key_usage = os_pki.key_usage; + memcpy(trusted_name, os_pki.trusted_name, os_pki.trusted_name_len); + *trusted_name_len = os_pki.trusted_name_len; + if (0 == cx_ecdsa_internal_init_public_key( + os_pki.public_key.curve, os_pki.public_key.W, + os_pki.public_key.W_len, (cx_ecfp_public_key_t *)public_key)) { + return 0x3202; + } + return 0x9000; +} diff --git a/src/bolos/os_pki.h b/src/bolos/os_pki.h new file mode 100644 index 00000000..f1a3a044 --- /dev/null +++ b/src/bolos/os_pki.h @@ -0,0 +1,148 @@ +#ifndef OS_PKI_H +#define OS_PKI_H + +#include "cxlib.h" +#include +#include + +/** Certificate field with a variable length */ +#define CERTIFICATE_FIELD_VAR_LEN (0xFF) +/** Certificate field with a non predefined value */ +#define CERTIFICATE_FIELD_UNKNOWN_VALUE (0xFFFFFFFF) +/** Certificate validity index minimum value */ +#define CERTIFICATE_VALIDITY_INDEX (0x00000001) +/** Certificate structure type */ +#define CERTIFICATE_STRUCTURE_TYPE_CERTIFICATE (0x01) +/** Maximum certificate trusted name length */ +#define CERTIFICATE_TRUSTED_NAME_MAXLEN (32) + +// Fake OS version +#define VERSION "1.0.0" + +/** Certificate tags associated to each certificate field */ +// clang-format off +typedef enum { + CERTIFICATE_TAG_STRUCTURE_TYPE = 0x01, ///< Structure type + CERTIFICATE_TAG_VERSION = 0x02, ///< Certificate version + CERTIFICATE_TAG_VALIDITY = 0x10, ///< Certificate validity + CERTIFICATE_TAG_VALIDITY_INDEX = 0x11, ///< Certificate validity index + CERTIFICATE_TAG_CHALLENGE = 0x12, ///< Challenge value + CERTIFICATE_TAG_SIGNER_KEY_ID = 0x13, ///< Signer key ID + CERTIFICATE_TAG_SIGN_ALGO_ID = 0x14, ///< Signature algorithm with the signer key + CERTIFICATE_TAG_SIGNATURE = 0x15, ///< Signature + CERTIFICATE_TAG_TIME_VALIDITY = 0x16, ///< Time validity + CERTIFICATE_TAG_TRUSTED_NAME = 0x20, ///< Trusted name + CERTIFICATE_TAG_PUBLIC_KEY_ID = 0x30, ///< Public key ID + CERTIFICATE_TAG_PUBLIC_KEY_USAGE = 0x31, ///< Public key usage + CERTIFICATE_TAG_PUBLIC_KEY_CURVE_ID = 0x32, ///< Curve ID on which the public key is defined + CERTIFICATE_TAG_COMPRESSED_PUBLIC_KEY = 0x33, ///< Public key in compressed form + CERTIFICATE_TAG_PK_SIGN_ALGO_ID = 0x34, ///< Signature algorithm with the public key + CERTIFICATE_TAG_TARGET_DEVICE = 0x35, ///< Target device + CERTIFICATE_TAG_DEPTH = 0x36 ///< Certificate depth +} os_pki_tag_t; +// clang-format on + +/** Certificate version possible values */ +enum { + CERTIFICATE_VERSION_02 = 0x02, ///< Certificate version 2 + CERTIFICATE_VERSION_UNKNOWN +}; + +/** Certificate key ID possible values */ +enum { + CERTIFICATE_KEY_ID_TEST = 0x0000, + CERTIFICATE_KEY_ID_PERSOV2, + CERTIFICATE_KEY_ID_LEDGER_ROOT_V3, + CERTIFICATE_KEY_ID_PLUGIN_SELECTOR, + CERTIFICATE_KEY_ID_NFT_METADATA, + CERTIFICATE_KEY_ID_PARTNER_METADATA, + CERTIFICATE_KEY_ID_ERC20_METADATA, + CERTIFICATE_KEY_ID_DOMAIN_METADATA, + CERTIFICATE_KEY_ID_UNKNOWN +}; + +/** Signature algorithm possible values */ +enum { + CERTIFICATE_SIGN_ALGO_ID_ECDSA_SHA256 = 0x01, + CERTIFICATE_SIGN_ALGO_ID_ECDSA_SHA3_256 = 0x02, + CERTIFICATE_SIGN_ALGO_ID_ECDSA_KECCAK_256 = 0x03, + CERTIFICATE_SIGN_ALGO_ID_ECDSA_RIPEMD160 = 0x04, + CERTIFICATE_SIGN_ALGO_ID_EDDSA_SHA512 = 0x10, + CERTIFICATE_SIGN_ALGO_ID_UNKNOWN +}; + +/** Public key usages possible values */ +enum { + CERTIFICATE_PUBLIC_KEY_USAGE_GENUINE_CHECK = 0x01, + CERTIFICATE_PUBLIC_KEY_USAGE_EXCHANGE_PAYLOAD, + CERTIFICATE_PUBLIC_KEY_USAGE_NFT_METADATA, + CERTIFICATE_PUBLIC_KEY_USAGE_TRUSTED_NAME, + CERTIFICATE_PUBLIC_KEY_USAGE_BACKUP_PROVIDER, + CERTIFICATE_PUBLIC_KEY_USAGE_RECOVER_ORCHESTRATOR, + CERTIFICATE_PUBLIC_KEY_USAGE_PLUGIN_METADATA, + CERTIFICATE_PUBLIC_KEY_USAGE_COIN_META, + CERTIFICATE_PUBLIC_KEY_USAGE_SEED_ID_AUTH, + CERTIFICATE_PUBLIC_KEY_USAGE_UNKNOWN, +}; + +/** Target device possible values */ +enum { + CERTIFICATE_TARGET_DEVICE_NANOS = 0x01, + CERTIFICATE_TARGET_DEVICE_NANOX, + CERTIFICATE_TARGET_DEVICE_NANOSP, + CERTIFICATE_TARGET_DEVICE_STAX, + CERTIFICATE_TARGET_DEVICE_UNKNOWN +}; + +/** Structure to store field length and field maximum value */ +typedef struct { + uint32_t value; + uint8_t field_len; +} os_pki_certificate_tag_info_t; + +// clang-format off +/** Array of field length and field maximum value corresponding to each tag */ +static const os_pki_certificate_tag_info_t C_os_pki_certificate_tag_info[] = { + [CERTIFICATE_TAG_STRUCTURE_TYPE] = {CERTIFICATE_STRUCTURE_TYPE_CERTIFICATE, 0x01 }, + [CERTIFICATE_TAG_VERSION] = {CERTIFICATE_VERSION_UNKNOWN, 0x01 }, + [CERTIFICATE_TAG_VALIDITY] = {CERTIFICATE_FIELD_UNKNOWN_VALUE, 0x04 }, + [CERTIFICATE_TAG_VALIDITY_INDEX] = {CERTIFICATE_VALIDITY_INDEX, 0x04 }, + [CERTIFICATE_TAG_CHALLENGE] = {CERTIFICATE_FIELD_UNKNOWN_VALUE, CERTIFICATE_FIELD_VAR_LEN}, + [CERTIFICATE_TAG_SIGNER_KEY_ID] = {CERTIFICATE_KEY_ID_UNKNOWN, 0x02 }, + [CERTIFICATE_TAG_SIGN_ALGO_ID] = {CERTIFICATE_SIGN_ALGO_ID_UNKNOWN, 0x01 }, + [CERTIFICATE_TAG_TIME_VALIDITY] = {CERTIFICATE_FIELD_UNKNOWN_VALUE, 0x04 }, + [CERTIFICATE_TAG_TRUSTED_NAME] = {CERTIFICATE_FIELD_UNKNOWN_VALUE, CERTIFICATE_FIELD_VAR_LEN}, + [CERTIFICATE_TAG_PUBLIC_KEY_ID] = {CERTIFICATE_KEY_ID_UNKNOWN, 0x02 }, + [CERTIFICATE_TAG_PUBLIC_KEY_USAGE] = {CERTIFICATE_PUBLIC_KEY_USAGE_UNKNOWN, 0x01 }, + [CERTIFICATE_TAG_PUBLIC_KEY_CURVE_ID] = {CX_CURVE_TWISTED_EDWARDS_END, 0x01 }, + [CERTIFICATE_TAG_COMPRESSED_PUBLIC_KEY] = {CERTIFICATE_KEY_ID_UNKNOWN, CERTIFICATE_FIELD_VAR_LEN}, + [CERTIFICATE_TAG_PK_SIGN_ALGO_ID] = {CERTIFICATE_SIGN_ALGO_ID_UNKNOWN, 0x01 }, + [CERTIFICATE_TAG_TARGET_DEVICE] = {CERTIFICATE_TARGET_DEVICE_UNKNOWN, 0x01 }, + [CERTIFICATE_TAG_SIGNATURE] = {CERTIFICATE_FIELD_UNKNOWN_VALUE, CERTIFICATE_FIELD_VAR_LEN}, + [CERTIFICATE_TAG_DEPTH] = {CERTIFICATE_FIELD_UNKNOWN_VALUE, 0x01 }, +}; +// clang-format on + +static const cx_md_t C_os_sign_algo_hash_info[] = { + [CERTIFICATE_SIGN_ALGO_ID_ECDSA_SHA256] = CX_SHA256, + [CERTIFICATE_SIGN_ALGO_ID_ECDSA_SHA3_256] = CX_SHA3_256, + [CERTIFICATE_SIGN_ALGO_ID_ECDSA_KECCAK_256] = CX_KECCAK, + [CERTIFICATE_SIGN_ALGO_ID_ECDSA_RIPEMD160] = CX_RIPEMD160, + [CERTIFICATE_SIGN_ALGO_ID_EDDSA_SHA512] = CX_SHA512 +}; + +uint32_t sys_os_pki_load_certificate(uint8_t expected_key_usage, + uint8_t *certificate, + size_t certificate_len, + uint8_t *trusted_name, + size_t *trusted_name_len, + cx_ecfp_384_public_key_t *public_key); + +bool sys_os_pki_verify(uint8_t *descriptor_hash, size_t descriptor_hash_len, + uint8_t *signature, size_t signature_len); + +uint32_t sys_os_pki_get_info(uint8_t *key_usage, uint8_t *trusted_name, + size_t *trusted_name_len, + cx_ecfp_384_public_key_t *public_key); + +#endif /* OS_PKI_H */ diff --git a/src/bolos/os_signature.c b/src/bolos/os_signature.c new file mode 100644 index 00000000..2cb322ca --- /dev/null +++ b/src/bolos/os_signature.c @@ -0,0 +1,163 @@ +#include "os_signature.h" +#include "exception.h" +#include + +cx_ecfp_public_key_t const speculos_root_ca_public_key = { + .curve = CX_CURVE_SECP256K1, + .W_len = 65, + .W = { 0x04, 0x37, 0x70, 0x13, 0xa7, 0x8f, 0xde, 0xeb, 0xae, 0xb4, 0x4c, + 0x26, 0x25, 0x60, 0xc0, 0x39, 0xb4, 0x94, 0xbc, 0xfd, 0x89, 0xdc, + 0x59, 0xfd, 0x23, 0xcc, 0xca, 0xce, 0x1b, 0x29, 0x52, 0xca, 0xa6, + 0x61, 0x6a, 0x0c, 0xfe, 0x66, 0x83, 0xab, 0x96, 0x39, 0xc6, 0xfc, + 0xe4, 0x44, 0xcd, 0xcd, 0x7c, 0x69, 0x86, 0x8d, 0x27, 0xdc, 0x77, + 0xea, 0x66, 0xd8, 0x62, 0x28, 0xae, 0xe5, 0x93, 0x31, 0x72 } +}; + +cx_err_t cx_ecdsa_internal_init_public_key(cx_curve_t curve, + const unsigned char *rawkey, + unsigned int key_len, + cx_ecfp_public_key_t *key) +{ + const cx_curve_domain_t *domain; + unsigned int expected_key_len; + unsigned int size; + + domain = cx_ecdomain(curve); + size = domain->length; + + memset(key, 0, sizeof(cx_ecfp_public_key_t)); + + if (rawkey != NULL) { + expected_key_len = 0; + if (rawkey[0] == 0x04) { + expected_key_len = 1 + size * 2; + } + if (expected_key_len == 0 || key_len != expected_key_len) { + return INVALID_PARAMETER; + } + } else { + key_len = 0; + } + // init key + key->curve = curve; + key->W_len = key_len; + memcpy(key->W, rawkey, key_len); + + return key_len; +} + +bool cx_ecdsa_internal_verify(const cx_ecfp_public_key_t *key, + const uint8_t *hash, unsigned int hash_len, + const uint8_t *sig, unsigned int sig_len) +{ + const cx_curve_weierstrass_t *domain; + unsigned int size; + const uint8_t *r, *s; + size_t rlen, slen; + int nid = 0; + bool result = false; + + domain = (const cx_curve_weierstrass_t *)cx_ecdomain(key->curve); + size = domain->length; + + if (!spec_cx_ecfp_decode_sig_der(sig, sig_len, size, &r, &rlen, &s, &slen)) { + return 0; + } + // Load ECDSA signature + BIGNUM *sig_r = BN_new(); + BIGNUM *sig_s = BN_new(); + BN_bin2bn(r, rlen, sig_r); + BN_bin2bn(s, slen, sig_s); + ECDSA_SIG *ecdsa_sig = ECDSA_SIG_new(); + ECDSA_SIG_set0(ecdsa_sig, sig_r, sig_s); + + // Set public key + BIGNUM *x = BN_new(); + BIGNUM *y = BN_new(); + BN_bin2bn(key->W + 1, domain->length, x); + BN_bin2bn(key->W + domain->length + 1, domain->length, y); + + nid = cx_nid_from_curve(key->curve); + EC_KEY *ec_key = EC_KEY_new_by_curve_name(nid); + EC_KEY_set_public_key_affine_coordinates(ec_key, x, y); + + if (ECDSA_do_verify(hash, hash_len, ecdsa_sig, ec_key) == 1) { + result = true; + } + + ECDSA_SIG_free(ecdsa_sig); + + BN_free(y); + BN_free(x); + EC_KEY_free(ec_key); + return result; +} + +int ED25519_verify(const uint8_t *message, size_t message_len, + const uint8_t signature[64], const uint8_t public_key[32]); + +bool cx_eddsa_internal_verify(const cx_ecfp_public_key_t *pu_key, + cx_md_t hashID, const unsigned char *hash, + unsigned int hash_len, const unsigned char *sig, + unsigned int sig_len) +{ + cx_ecfp_public_key_t pub; + + /* Only SHA-512 is supported in Speculos, as it is the only supported hash + * in OpenSSL */ + if (hashID != CX_SHA512) { + return 0; + } + if (sig_len != 64) { + return 0; + } + if (pu_key->curve != CX_CURVE_Ed25519 || pu_key->W_len != 1 + 2 * 32) { + return 0; + } + + /* pass a compressed key to ED25519_verify */ + memcpy(&pub, pu_key, sizeof(pub)); + if (pub.W[0] == 0x04) { + if (sys_cx_edward_compress_point(pub.curve, pub.W, pub.W_len) != 0) { + return 0; + } + } + + return ED25519_verify(hash, hash_len, sig, pub.W + 1); +} + +bool cx_verify(cx_sign_algo_t sign_algo, cx_ecfp_public_key_t *public_key, + uint8_t *msg_hash, size_t msg_hash_len, uint8_t *signature, + size_t signature_len) +{ + switch (sign_algo) { + case CX_SIGN_ALGO_ECDSA_RND: + case CX_SIGN_ALGO_ECDSA_RFC6979_RIPEMD160: + case CX_SIGN_ALGO_ECDSA_RFC6979_SHA224: + case CX_SIGN_ALGO_ECDSA_RFC6979_SHA256: + case CX_SIGN_ALGO_ECDSA_RFC6979_SHA384: + case CX_SIGN_ALGO_ECDSA_RFC6979_SHA512: + case CX_SIGN_ALGO_ECDSA_RFC6979_KECCAK: + case CX_SIGN_ALGO_ECDSA_RFC6979_SHA3: + return cx_ecdsa_internal_verify(public_key, msg_hash, msg_hash_len, + signature, signature_len); + break; + case CX_SIGN_ALGO_EDDSA_SHA512: + return cx_eddsa_internal_verify(public_key, CX_SHA512, msg_hash, + msg_hash_len, signature, signature_len); + default: + return false; + } + return false; +} + +bool os_ecdsa_verify_with_root_ca(uint8_t key_id, uint8_t *hash, + size_t hash_len, uint8_t *sig, size_t sig_len) +{ + bool result = false; + if (ROOT_CA_V3_KEY_ID == key_id) { + result = cx_ecdsa_internal_verify(&speculos_root_ca_public_key, hash, + hash_len, sig, sig_len); + } + return result; +} diff --git a/src/bolos/os_signature.h b/src/bolos/os_signature.h new file mode 100644 index 00000000..1faca571 --- /dev/null +++ b/src/bolos/os_signature.h @@ -0,0 +1,43 @@ +#ifndef OS_SIGNATURE_H +#define OS_SIGNATURE_H + +#include "cxlib.h" + +#define ROOT_CA_V3_KEY_ID (4) + +typedef enum cx_sign_algo_e { + CX_SIGN_ALGO_UNKNOWN = 0x00, + CX_SIGN_ALGO_EDDSA_SHA512 = 0x05, + CX_SIGN_ALGO_ECDSA_RND = 0x20, + CX_SIGN_ALGO_ECDSA_RFC6979_RIPEMD160 = 0x31, + CX_SIGN_ALGO_ECDSA_RFC6979_SHA224 = 0x32, + CX_SIGN_ALGO_ECDSA_RFC6979_SHA256 = 0x33, + CX_SIGN_ALGO_ECDSA_RFC6979_SHA384 = 0x34, + CX_SIGN_ALGO_ECDSA_RFC6979_SHA512 = 0x35, + CX_SIGN_ALGO_ECDSA_RFC6979_KECCAK = 0x36, + CX_SIGN_ALGO_ECDSA_RFC6979_SHA3 = 0x37, +} cx_sign_algo_t; + +cx_err_t cx_ecdsa_internal_init_public_key(cx_curve_t curve, + const unsigned char *rawkey, + unsigned int key_len, + cx_ecfp_public_key_t *key); + +bool cx_ecdsa_internal_verify(const cx_ecfp_public_key_t *key, + const uint8_t *hash, unsigned int hash_len, + const uint8_t *sig, unsigned int sig_len); + +bool cx_eddsa_internal_verify(const cx_ecfp_public_key_t *pu_key, + cx_md_t hashID, const unsigned char *hash, + unsigned int hash_len, const unsigned char *sig, + unsigned int sig_len); + +bool cx_verify(cx_sign_algo_t sign_algo, cx_ecfp_public_key_t *public_key, + uint8_t *msg_hash, size_t msg_hash_len, uint8_t *signature, + size_t signature_len); + +bool os_ecdsa_verify_with_root_ca(uint8_t key_id, uint8_t *hash, + size_t hash_len, uint8_t *sig, + size_t sig_len); + +#endif /* OS_SIGNATURE_H */ diff --git a/src/emulate_unified_sdk.c b/src/emulate_unified_sdk.c index 9ac42faa..487cbfe6 100644 --- a/src/emulate_unified_sdk.c +++ b/src/emulate_unified_sdk.c @@ -8,6 +8,7 @@ #include "bolos/cxlib.h" #include "bolos/endorsement.h" #include "bolos/nbgl.h" +#include "bolos/os_pki.h" #include "bolos/touch.h" #include "emulate.h" @@ -648,6 +649,26 @@ int emulate_syscall_os(unsigned long syscall, unsigned long *parameters, unsigned char *, serial, unsigned int, maxlength); + SYSCALL6(os_pki_load_certificate, "(%u, %p, %u, %p, %p, %p)", + uint8_t, expected_key_usage, + uint8_t *, certificate, + size_t, certificate_len, + uint8_t *, trusted_name, + size_t *, trusted_name_len, + cx_ecfp_384_public_key_t *, public_key); + + SYSCALL4(os_pki_verify, "(%p, %u, %p, %u)", + uint8_t *, descriptor_hash, + size_t, descriptor_hash_len, + uint8_t *, signature, + size_t, signature_len); + + SYSCALL4(os_pki_get_info, "(%p, %p, %p, %p)", + uint8_t *, key_usage, + uint8_t *, trusted_name, + size_t *, trusted_name_len, + cx_ecfp_384_public_key_t *, public_key); + /* clang-format on */ default: return SYSCALL_NOT_HANDLED;