From 08507069bd59e89761952bb29b76bc3d365b31e1 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 14 May 2026 00:36:40 +0200 Subject: [PATCH] Derive secp256k1 from SE. Signed-off-by: Pol Henarejos --- src/otp/otp_macos.c | 202 +++++++++++++++++++++++++++++--------------- 1 file changed, 133 insertions(+), 69 deletions(-) diff --git a/src/otp/otp_macos.c b/src/otp/otp_macos.c index 63890da..854db83 100644 --- a/src/otp/otp_macos.c +++ b/src/otp/otp_macos.c @@ -24,53 +24,98 @@ #include #include +#include "mbedtls/bignum.h" +#include "mbedtls/ecp.h" #include "mbedtls/sha256.h" #define CF_SAFE_RELEASE(x) do { if ((x) != NULL) CFRelease(x); } while (0) #define SE_KEY_TAG "com.picokeys.novus.se.key" +#define LOCAL_KEY_TAG "com.picokeys.novus.local.ecdh.key" static int sec_err(const char *ctx, OSStatus st) { fprintf(stderr, "[macos-se] %s failed: OSStatus=%d\n", ctx, (int)st); return -1; } -static int macos_se_vault_load_or_create_key2(uint8_t out_key32[32]) { +static int derive_secp256k1_privkey_from_secret(const uint8_t *secret, size_t secret_len, uint8_t out_key32[32]) { int rc = -1; + uint8_t digest[32]; + const uint8_t label[] = "pico-novus/se-ecdh-to-k1-v1"; + mbedtls_ecp_group grp; + mbedtls_mpi x, n_minus_1; + + if (!secret || secret_len == 0 || !out_key32) { + return -1; + } + + mbedtls_sha256_context sha; + mbedtls_sha256_init(&sha); + mbedtls_sha256_starts(&sha, 0); + mbedtls_sha256_update(&sha, label, sizeof(label) - 1); + mbedtls_sha256_update(&sha, secret, secret_len); + mbedtls_sha256_finish(&sha, digest); + mbedtls_sha256_free(&sha); + + mbedtls_ecp_group_init(&grp); + mbedtls_mpi_init(&x); + mbedtls_mpi_init(&n_minus_1); + + if (mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256K1) != 0) { + goto cleanup; + } + if (mbedtls_mpi_read_binary(&x, digest, sizeof(digest)) != 0) { + goto cleanup; + } + if (mbedtls_mpi_copy(&n_minus_1, &grp.N) != 0) { + goto cleanup; + } + if (mbedtls_mpi_sub_int(&n_minus_1, &n_minus_1, 1) != 0) { + goto cleanup; + } + if (mbedtls_mpi_mod_mpi(&x, &x, &n_minus_1) != 0) { + goto cleanup; + } + if (mbedtls_mpi_add_int(&x, &x, 1) != 0) { + goto cleanup; + } + if (mbedtls_mpi_write_binary(&x, out_key32, 32) != 0) { + goto cleanup; + } + + rc = 0; + +cleanup: + mbedtls_mpi_free(&n_minus_1); + mbedtls_mpi_free(&x); + mbedtls_ecp_group_free(&grp); + return rc; +} + +static int load_or_create_p256_key(const char *tag_str, bool in_secure_enclave, SecKeyRef *out_private_key) { SecKeyRef private_key = NULL; - SecKeyRef public_key = NULL; CFDataRef tag = NULL; CFDictionaryRef find_query = NULL; CFDictionaryRef priv_attrs = NULL; CFNumberRef key_size_num = NULL; CFDictionaryRef attrs = NULL; - CFDataRef pub_data = NULL; CFErrorRef err = NULL; OSStatus st; + int rc = -1; - tag = CFDataCreate(NULL, (const UInt8 *)SE_KEY_TAG, (CFIndex)strlen(SE_KEY_TAG)); + if (!tag_str || !out_private_key) { + return -1; + } + + *out_private_key = NULL; + tag = CFDataCreate(NULL, (const UInt8 *)tag_str, (CFIndex)strlen(tag_str)); if (!tag) { fprintf(stderr, "[macos-se] CFDataCreate(tag) failed\n"); goto cleanup; } - const void *find_keys[] = { - kSecClass, - kSecAttrApplicationTag, - kSecAttrKeyType, - kSecReturnRef - }; - const void *find_vals[] = { - kSecClassKey, - tag, - kSecAttrKeyTypeECSECPrimeRandom, - kCFBooleanTrue - }; - find_query = CFDictionaryCreate(NULL, - find_keys, - find_vals, - 4, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); + const void *find_keys[] = { kSecClass, kSecAttrApplicationTag, kSecAttrKeyType, kSecReturnRef }; + const void *find_vals[] = { kSecClassKey, tag, kSecAttrKeyTypeECSECPrimeRandom, kCFBooleanTrue }; + find_query = CFDictionaryCreate(NULL, find_keys, find_vals, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!find_query) { fprintf(stderr, "[macos-se] CFDictionaryCreate(find_query) failed\n"); goto cleanup; @@ -80,12 +125,7 @@ static int macos_se_vault_load_or_create_key2(uint8_t out_key32[32]) { if (st == errSecItemNotFound) { const void *priv_keys[] = { kSecAttrIsPermanent, kSecAttrApplicationTag }; const void *priv_vals[] = { kCFBooleanTrue, tag }; - priv_attrs = CFDictionaryCreate(NULL, - priv_keys, - priv_vals, - 2, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); + priv_attrs = CFDictionaryCreate(NULL, priv_keys, priv_vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (!priv_attrs) { fprintf(stderr, "[macos-se] CFDictionaryCreate(priv_attrs) failed\n"); goto cleanup; @@ -98,24 +138,15 @@ static int macos_se_vault_load_or_create_key2(uint8_t out_key32[32]) { goto cleanup; } - const void *keys[] = { - kSecAttrKeyType, - kSecAttrKeySizeInBits, - kSecAttrTokenID, - kSecPrivateKeyAttrs - }; - const void *vals[] = { - kSecAttrKeyTypeECSECPrimeRandom, - key_size_num, - kSecAttrTokenIDSecureEnclave, - priv_attrs - }; - attrs = CFDictionaryCreate(NULL, - keys, - vals, - 4, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); + if (in_secure_enclave) { + const void *keys[] = { kSecAttrKeyType, kSecAttrKeySizeInBits, kSecAttrTokenID, kSecPrivateKeyAttrs }; + const void *vals[] = { kSecAttrKeyTypeECSECPrimeRandom, key_size_num, kSecAttrTokenIDSecureEnclave, priv_attrs }; + attrs = CFDictionaryCreate(NULL, keys, vals, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } else { + const void *keys[] = { kSecAttrKeyType, kSecAttrKeySizeInBits, kSecPrivateKeyAttrs }; + const void *vals[] = { kSecAttrKeyTypeECSECPrimeRandom, key_size_num, priv_attrs }; + attrs = CFDictionaryCreate(NULL, keys, vals, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } if (!attrs) { fprintf(stderr, "[macos-se] CFDictionaryCreate(attrs) failed\n"); goto cleanup; @@ -129,36 +160,71 @@ static int macos_se_vault_load_or_create_key2(uint8_t out_key32[32]) { } goto cleanup; } - printf("[macos-se] Created Secure Enclave key '%s'\n", SE_KEY_TAG); + printf("[macos-se] Created key '%s'%s\n", tag_str, in_secure_enclave ? " in Secure Enclave" : ""); } else if (st != errSecSuccess) { sec_err("SecItemCopyMatching", st); goto cleanup; } else { - printf("[macos-se] Using existing Secure Enclave key '%s'\n", SE_KEY_TAG); + printf("[macos-se] Using existing key '%s'\n", tag_str); } - public_key = SecKeyCopyPublicKey(private_key); - if (!public_key) { - fprintf(stderr, "[macos-se] SecKeyCopyPublicKey failed\n"); + *out_private_key = private_key; + private_key = NULL; + rc = 0; + +cleanup: + if (err) { + CFRelease(err); + } + CF_SAFE_RELEASE(attrs); + CF_SAFE_RELEASE(key_size_num); + CF_SAFE_RELEASE(priv_attrs); + CF_SAFE_RELEASE(find_query); + CF_SAFE_RELEASE(tag); + CF_SAFE_RELEASE(private_key); + return rc; +} + +static int macos_se_vault_load_or_create_key2(uint8_t out_key32[32]) { + int rc = -1; + SecKeyRef se_private_key = NULL; + SecKeyRef local_private_key = NULL; + SecKeyRef local_public_key = NULL; + CFDataRef shared_secret = NULL; + CFDictionaryRef ecdh_params = NULL; + CFErrorRef err = NULL; + + if (load_or_create_p256_key(SE_KEY_TAG, true, &se_private_key) != 0) { + goto cleanup; + } + if (load_or_create_p256_key(LOCAL_KEY_TAG, false, &local_private_key) != 0) { goto cleanup; } - pub_data = SecKeyCopyExternalRepresentation(public_key, &err); - if (!pub_data) { - fprintf(stderr, "[macos-se] SecKeyCopyExternalRepresentation failed\n"); + local_public_key = SecKeyCopyPublicKey(local_private_key); + if (!local_public_key) { + fprintf(stderr, "[macos-se] SecKeyCopyPublicKey(local_private_key) failed\n"); + goto cleanup; + } + + ecdh_params = CFDictionaryCreate(NULL, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (!ecdh_params) { + fprintf(stderr, "[macos-se] CFDictionaryCreate(ecdh_params) failed\n"); + goto cleanup; + } + + shared_secret = SecKeyCopyKeyExchangeResult(se_private_key, kSecKeyAlgorithmECDHKeyExchangeStandard, local_public_key, ecdh_params, &err); + if (!shared_secret) { + fprintf(stderr, "[macos-se] SecKeyCopyKeyExchangeResult failed\n"); if (err) { CFShow(err); } goto cleanup; } - mbedtls_sha256(CFDataGetBytePtr(pub_data), - (size_t)CFDataGetLength(pub_data), - out_key32, - 0); - if (!memcmp(out_key32, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 32)) { - out_key32[31] = 1; + if (derive_secp256k1_privkey_from_secret(CFDataGetBytePtr(shared_secret), (size_t)CFDataGetLength(shared_secret), out_key32) != 0) { + fprintf(stderr, "[macos-se] failed deriving secp256k1 private key from ECDH secret\n"); + goto cleanup; } rc = 0; @@ -166,14 +232,11 @@ cleanup: if (err) { CFRelease(err); } - CF_SAFE_RELEASE(pub_data); - CF_SAFE_RELEASE(public_key); - CF_SAFE_RELEASE(attrs); - CF_SAFE_RELEASE(key_size_num); - CF_SAFE_RELEASE(priv_attrs); - CF_SAFE_RELEASE(find_query); - CF_SAFE_RELEASE(tag); - CF_SAFE_RELEASE(private_key); + CF_SAFE_RELEASE(ecdh_params); + CF_SAFE_RELEASE(shared_secret); + CF_SAFE_RELEASE(local_public_key); + CF_SAFE_RELEASE(local_private_key); + CF_SAFE_RELEASE(se_private_key); return rc; } #endif @@ -204,6 +267,7 @@ void otp_platform_init(const uint8_t **otp_key_1_out, const uint8_t **otp_key_2_ if (macos_se_vault_load_or_create_key2(_otp2) != 0) { printf("Warning: failed to load MACOS_APP Secure Enclave key; using dummy otp_key_2\n"); } + DEBUG_DATA(_otp2, 32); #endif *otp_key_1_out = _otp1; *otp_key_2_out = _otp2;