static int s_cert_context_import_ecc_private_key()

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;
}