in source/windows/windows_pki_utils.c [357:505]
static int s_cert_context_import_ecc_private_key(
PCCERT_CONTEXT cert_context,
struct aws_allocator *allocator,
const BYTE *key,
DWORD decoded_len,
wchar_t uuid_wstr[AWS_UUID_STR_LEN]) {
(void)decoded_len;
AWS_FATAL_ASSERT(cert_context != NULL);
NCRYPT_PROV_HANDLE crypto_prov = 0;
NCRYPT_KEY_HANDLE h_key = 0;
BCRYPT_ECCKEY_BLOB *key_blob = NULL;
int result = AWS_OP_ERR;
SECURITY_STATUS status;
CRYPT_BIT_BLOB *public_key_blob = &cert_context->pCertInfo->SubjectPublicKeyInfo.PublicKey;
DWORD public_key_blob_length = public_key_blob->cbData;
if (public_key_blob_length == 0) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: invalid zero-length ecc key data");
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
goto done;
}
/*
* Per rfc5480#section-2.2, the public key section of the encoding consists of a single byte that tells whether or
* not the public key is compressed, followed by the raw key data itself. Windows doesn't seem to support importing
* compressed keys directly, so for now check and fail if it's a compressed key.
*
* Given that we're pulling the data from a windows internal structure generated by CryptQueryObject, it is
* not known whether it's even possible to see a compressed tag here or if Windows automatically uncompresses a
* compressed key for you. The win32 documentation is quite unhelpful here.
*
* We could test this by generating a certificate that contains a compressed public key and feeding it in.
* I cannot find a way to do it that doesn't involve raw hex editing a sub object in the DER encoding of the
* certificate. So figuring out the final expectation here is a TODO.
*/
if (*public_key_blob->pbData != AWS_EPKCT_UNCOMPRESSED) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: compressed ecc public keys not yet supported.");
aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
goto done;
}
/*
* Now we want everything but the first byte, so dec the length and bump the pointer. I was more comfortable doing
* it the manual way rather than with cursors because using cursors would force us to do multiple narrowing casts
* back when configuring win32 data.
*/
public_key_blob_length--;
struct aws_byte_cursor public_blob_cursor = {
.ptr = public_key_blob->pbData + 1,
.len = public_key_blob_length,
};
CRYPT_ECC_PRIVATE_KEY_INFO *private_key_info = (CRYPT_ECC_PRIVATE_KEY_INFO *)key;
ULONG private_key_length = private_key_info->PrivateKey.cbData;
struct aws_byte_cursor private_key_cursor = {
.ptr = private_key_info->PrivateKey.pbData,
.len = private_key_length,
};
DWORD key_blob_size = sizeof(BCRYPT_ECCKEY_BLOB) + public_key_blob_length + private_key_length;
key_blob = (BCRYPT_ECCKEY_BLOB *)aws_mem_calloc(allocator, 1, key_blob_size);
if (key_blob == NULL) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: could not allocate ecc key blob memory");
goto done;
}
key_blob->dwMagic = s_compute_ecc_key_type_from_private_key_size(private_key_cursor.len);
key_blob->cbKey = private_key_length;
struct aws_byte_buf key_blob_buffer = {
.buffer = (uint8_t *)key_blob,
.len = sizeof(BCRYPT_ECCKEY_BLOB),
.capacity = key_blob_size,
};
if (aws_byte_buf_append(&key_blob_buffer, &public_blob_cursor)) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: insufficient space to build ecc key blob");
goto done;
}
if (aws_byte_buf_append(&key_blob_buffer, &private_key_cursor)) {
AWS_LOGF_ERROR(AWS_LS_IO_PKI, "static: insufficient space to build ecc key blob");
goto done;
}
status = NCryptOpenStorageProvider(&crypto_prov, MS_KEY_STORAGE_PROVIDER, 0);
if (status != ERROR_SUCCESS) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI, "static: could not open ncrypt key storage provider, error %d", (int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
NCryptBuffer ncBuf = {AWS_UUID_STR_LEN * sizeof(wchar_t), NCRYPTBUFFER_PKCS_KEY_NAME, uuid_wstr};
NCryptBufferDesc ncBufDesc;
ncBufDesc.ulVersion = 0;
ncBufDesc.cBuffers = 1;
ncBufDesc.pBuffers = &ncBuf;
status = NCryptImportKey(
crypto_prov,
0,
BCRYPT_ECCPRIVATE_BLOB,
&ncBufDesc,
&h_key,
(BYTE *)key_blob,
key_blob_size,
NCRYPT_OVERWRITE_KEY_FLAG);
if (status != ERROR_SUCCESS) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI,
"static: failed to import ecc key with status %d, last error %d",
status,
(int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
CRYPT_KEY_PROV_INFO key_prov_info = {uuid_wstr, MS_KEY_STORAGE_PROVIDER, 0, 0, 0, NULL, 0};
if (!CertSetCertificateContextProperty(cert_context, CERT_KEY_PROV_INFO_PROP_ID, 0, &key_prov_info)) {
AWS_LOGF_ERROR(
AWS_LS_IO_PKI, "static: failed to set cert context key provider, with last error %d", (int)GetLastError());
aws_raise_error(AWS_ERROR_SYS_CALL_FAILURE);
goto done;
}
result = AWS_OP_SUCCESS;
done:
if (h_key != 0) {
NCryptFreeObject(h_key);
}
if (crypto_prov != 0) {
NCryptFreeObject(crypto_prov);
}
if (key_blob != NULL) {
aws_mem_release(allocator, key_blob);
}
return result;
}