Search code examples
c++windowswinapirsacryptoapi

How to import a PKCS#8 with CryptoApi


I Have a PKCS#8 Key and I desperately try to import in CryptoAPI without any success. I have :

-----BEGIN PRIVATE KEY-----
<privatekey>
-----END PRIVATE KEY-----

That contain this :

Private Key algo RSA
 Private Format  PKCS#8
 ASN1 Dump
RSA Private CRT Key [.....]
            modulus: .....
    public exponent: .....

I try to import the key like this :

 if not CryptStringToBinaryA(
           PansiChar(aBase64PrivateKey), // pszString: LPCSTR;
           length(aBase64PrivateKey), // cchString: DWORD;
           CRYPT_STRING_BASE64HEADER, // dwFlags: DWORD;
           nil, // pbBinary: pByte;
           @cbPrivKey, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD
  setlength(pPrivKey, cbPrivKey);
  if not CryptStringToBinaryA(
           PansiChar(aBase64PrivateKey), // pszString: LPCSTR;
           length(aBase64PrivateKey), // cchString: DWORD;
           CRYPT_STRING_BASE64HEADER, // dwFlags: DWORD;
           @pPrivKey[0], // pbBinary: pByte;
           @cbPrivKey, // pcbBinary: PDWORD;
           nil, // pdwSkip: PDWORD;
           nil) then raiseLastOsError; // pdwFlags: PDWORD

  //init pKeyBlob
  if not CryptDecodeObjectEx(
           X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, // dwCertEncodingType: DWORD;
           PKCS_PRIVATE_KEY_INFO, // lpszStructType: LPCSTR;
           @pPrivKey[0], // const pbEncoded: PBYTE;
           cbPrivKey, // cbEncoded: DWORD;
           0, // dwFlags: DWORD;
           nil, // pDecodePara: PCRYPT_DECODE_PARA;
           nil, // pvStructInfo: Pointer;
           @cbKeyBlob) then raiseLastOsError; // pcbStructInfo: PDWORD
  setlength(pKeyBlob, cbKeyBlob);
  if not CryptDecodeObjectEx(
           X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, // dwCertEncodingType: DWORD;
           PKCS_PRIVATE_KEY_INFO, // lpszStructType: LPCSTR;
           @pPrivKey[0], // const pbEncoded: PBYTE;
           cbPrivKey, // cbEncoded: DWORD;
           0, // dwFlags: DWORD;
           nil, // pDecodePara: PCRYPT_DECODE_PARA;
           @pKeyBlob[0], // pvStructInfo: Pointer;
           @cbKeyBlob) then raiseLastOsError; // pcbStructInfo: PDWORD

  //acquire a handle to a particular key container
  if (not CryptAcquireContextA(@hProv, // phProv: PHCRYPTPROV;
                               nil, // pszContainer: PAnsiChar;
                               nil, // pszProvider: PAnsiChar;
                               PROV_RSA_AES, // dwProvType: DWORD;
                               CRYPT_VERIFYCONTEXT)) then raiselastOsError; // dwFlags: DWORD
  try

    // Now import the key.
    if not CryptImportKey(hProv, // hProv: HCRYPTPROV;
                          @pKeyBlob[0], // const pbData: PBYTE;
                          cbKeyBlob, // dwDataLen: DWORD;
                          0, // hPubKey: HCRYPTKEY;
                          0, // dwFlags: DWORD;
                          @hRSAKey) then raiseLastOsError; // phKey: PHCRYPTKEY

but CryptImportKey fail with "Bad Version of provider", I guess because it's was waiting for a PKCS#1 Key. How can I import a PKCS#8 key?


Solution

  • for convert PKCS #8 format to legacy or CNG crypto api need several steps.

    first need convert string to binary via CryptStringToBinaryA with CRYPT_STRING_BASE64HEADER

    than call CryptDecodeObjectEx with PKCS_PRIVATE_KEY_INFO (if private key encrypted need use PKCS_ENCRYPTED_PRIVATE_KEY_INFO )

    than need again call CryptDecodeObjectEx with PKCS_RSA_PRIVATE_KEY for legacy crypto api and CNG_RSA_PUBLIC_KEY_BLOB for CNG.

    now we can call BCryptImportKeyPair or CryptImportKey

    for import public key need use X509_PUBLIC_KEY_INFO, for public key from cert - X509_CERT_TO_BE_SIGNED (+ CNG_RSA_PUBLIC_KEY_BLOB for CNG, for legacy can use CryptImportPublicKeyInfo)

    say code for legacy

    inline ULONG BOOL_TO_ERROR(BOOL f)
    {
        return f ? NOERROR : GetLastError();
    }
    
    ULONG CryptImportPublicKey(_Out_ HCRYPTKEY *phKey, 
                               _In_ HCRYPTPROV hProv,
                               _In_ PCUCHAR pbKeyOrCert, 
                               _In_ ULONG cbKeyOrCert, 
                               _In_ bool bCert)
    {
        ULONG cb;
    
        union {
            PVOID pvStructInfo;
            PCERT_INFO pCertInfo;
            PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
        };
    
        ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            bCert ? X509_CERT_TO_BE_SIGNED : X509_PUBLIC_KEY_INFO, 
            pbKeyOrCert, cbKeyOrCert, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, &pvStructInfo, &cb));
    
        if (dwError == NOERROR)
        {
            PVOID pv = pvStructInfo;
    
            if (bCert)
            {
                PublicKeyInfo = &pCertInfo->SubjectPublicKeyInfo;
            }
    
            dwError = BOOL_TO_ERROR(CryptImportPublicKeyInfo(hProv, 
                X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PublicKeyInfo, phKey));
    
            LocalFree(pv);
        }
    
        return dwError;
    }
    
    ULONG CryptImportPrivateKey(_Out_ HCRYPTKEY* phKey, 
                                _In_ HCRYPTPROV hProv, 
                                _In_ PCUCHAR pbKey, 
                                _In_ ULONG cbKey)
    {
        ULONG cb;
        PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;
    
        ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, 
            pbKey, cbKey, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, (void**)&PrivateKeyInfo, &cb));
    
        if (dwError == NOERROR)
        {
            PUBLICKEYSTRUC* ppks;  
    
            dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
                PKCS_RSA_PRIVATE_KEY, PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData, 
                CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&ppks, &cb));
    
            LocalFree(PrivateKeyInfo);
    
            if (dwError == NOERROR)
            {
                dwError = BOOL_TO_ERROR(CryptImportKey(hProv, (PUCHAR)ppks, cb, 0, CRYPT_EXPORTABLE, phKey));
                LocalFree(ppks);
            }
        }
    
        return dwError;
    }
    
    enum BLOB_TYPE { bt_priv, bt_pub, bt_cert };
    
    ULONG CryptImportKey(_Out_ HCRYPTKEY *phKey, 
                         _In_ HCRYPTPROV hProv,
                         _In_ BLOB_TYPE bt, 
                         _In_ PCSTR szKey, 
                         _In_ ULONG cchKey)
    {
        PUCHAR pbKey = 0;
        ULONG cbKey = 0;
        ULONG dwError;
    
        while (CryptStringToBinaryA(szKey, cchKey, CRYPT_STRING_BASE64HEADER, pbKey, &cbKey, 0, 0))
        {
            if (pbKey)
            {
                switch (bt)
                {
                case bt_priv:
                    dwError = CryptImportPrivateKey(phKey, hProv, pbKey, cbKey);
                    break;
                case bt_pub:
                    dwError = CryptImportPublicKey(phKey, hProv, pbKey, cbKey, false);
                    break;
                case bt_cert:
                    dwError = CryptImportPublicKey(phKey, hProv, pbKey, cbKey, true);
                    break;
                default: dwError = ERROR_INVALID_PARAMETER;
                }
    
                _freea(pbKey);
    
                return dwError;
            }
    
            if (!(pbKey = (PUCHAR)_malloca(cbKey)))
            {
                break;
            }
        }
    
        dwError = GetLastError();
    
        if (pbKey) _freea(pbKey);
    
        return dwError;
    }
    
    void DoLegacyTest(_In_ PCSTR szToBeSigned, 
                      _In_ PCSTR szPrivateKey, 
                      _In_ ULONG cchPrivateKey,
                      _In_ PCSTR szPublicKeyOrCert, 
                      _In_ ULONG cchPublicKeyOrCert,
                      _In_ bool bCert)
    {
        HCRYPTPROV hProv;
        if (CryptAcquireContextW(&hProv, 0, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
        {
            HCRYPTKEY hKey;
            HCRYPTHASH hHash;
    
            if (CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
            {
                if (CryptHashData(hHash, (PUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), 0))
                {
                    PUCHAR pbSignature = 0;
                    ULONG cbSignature = 0;
                    BOOL fOk = false;
    
                    if (NOERROR == CryptImportKey(&hKey, hProv, bt_priv, szPrivateKey, cchPrivateKey))
                    {
                        ULONG dwKeySpec, cb; 
    
                        if (CryptGetKeyParam(hKey, KP_ALGID, (PUCHAR)&dwKeySpec, &(cb = sizeof(dwKeySpec)), 0))
                        {
                            switch (dwKeySpec)
                            {
                            case CALG_RSA_KEYX:
                                dwKeySpec = AT_KEYEXCHANGE;
                                break;
                            case CALG_RSA_SIGN:
                                dwKeySpec = AT_SIGNATURE;
                                break;
                            default: dwKeySpec = 0;
                            }
    
                            if (CryptGetKeyParam(hKey, KP_BLOCKLEN, (PUCHAR)&cbSignature, &(cb = sizeof(cbSignature)), 0))
                            {
                                pbSignature = (PUCHAR)alloca(cbSignature >>= 3);
    
                                fOk = CryptSignHashW(hHash, dwKeySpec, 0, 0, pbSignature, &cbSignature);
                            }
                        }
    
                        CryptDestroyKey(hKey);
                    }
    
                    if (fOk)
                    {
                        if (NOERROR == CryptImportKey(&hKey, hProv, bCert ? bt_cert : bt_pub, szPublicKeyOrCert, cchPublicKeyOrCert))
                        {
                            if (!CryptVerifySignatureW(hHash, pbSignature, cbSignature, hKey, 0, 0))
                            {
                                __debugbreak();
                            }
                            CryptDestroyKey(hKey);
                        }
                    }
                }
    
                CryptDestroyHash(hHash);
            }
    
            CryptReleaseContext(hProv, 0);
        }
    }
    

    code for CNG

    inline ULONG BOOL_TO_ERROR(BOOL f)
    {
        return f ? NOERROR : GetLastError();
    }
    
    NTSTATUS openssl_verify(_In_ BCRYPT_KEY_HANDLE hKey,
                            _In_ PCUCHAR pbToBeSigned, 
                            _In_ ULONG cbToBeSigned,
                            _In_ PCUCHAR pbSignature, 
                            _In_ ULONG cbSignature,
                            _In_ PCWSTR pszAlgId)
    {
        BCRYPT_ALG_HANDLE hAlgorithm;
    
        NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);
    
        if (0 <= status)
        {
            BCRYPT_HASH_HANDLE hHash = 0;
    
            ULONG HashBlockLength, cb;
    
            0 <= (status = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashBlockLength, sizeof(ULONG), &cb, 0)) &&
                0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0));
    
            BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    
            if (0 <= status)
            {
                PUCHAR pbHash = (PUCHAR)alloca(HashBlockLength);
    
                0 <= (status = BCryptHashData(hHash, const_cast<PUCHAR>(pbToBeSigned), cbToBeSigned, 0)) &&
                    0 <= (status = BCryptFinishHash(hHash, pbHash, HashBlockLength, 0));
    
                BCryptDestroyHash(hHash);
    
                if (0 <= status)
                {
                    BCRYPT_PKCS1_PADDING_INFO pi = { pszAlgId };
    
                    status = BCryptVerifySignature(hKey, &pi, pbHash, HashBlockLength, 
                        const_cast<PUCHAR>(pbSignature), cbSignature, BCRYPT_PAD_PKCS1);
                }
            }
        }
    
        return status;
    }
    
    inline NTSTATUS openssl_verify(_In_ BCRYPT_KEY_HANDLE hKey,
                                   _In_ PCSTR szToBeSigned,
                                   _In_ PCUCHAR pbSignature, 
                                   _In_ ULONG cbSignature,
                                   _In_ PCWSTR pszAlgId)
    {
        return openssl_verify(hKey, (PCUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), pbSignature, cbSignature, pszAlgId);
    }
    
    NTSTATUS openssl_sign(_In_ BCRYPT_KEY_HANDLE hKey,
                          _In_ PCUCHAR pbToBeSigned, 
                          _In_ ULONG cbToBeSigned,
                          _Out_ PUCHAR pbSignature, 
                          _Inout_ PULONG pcbSignature,
                          _In_ PCWSTR pszAlgId)
    {
        BCRYPT_ALG_HANDLE hAlgorithm;
    
        NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);
    
        if (0 <= status)
        {
            BCRYPT_HASH_HANDLE hHash = 0;
    
            ULONG HashBlockLength, cb;
    
            0 <= (status = BCryptGetProperty(hAlgorithm, BCRYPT_HASH_LENGTH, (PUCHAR)&HashBlockLength, sizeof(ULONG), &cb, 0)) &&
                0 <= (status = BCryptCreateHash(hAlgorithm, &hHash, 0, 0, 0, 0, 0));
    
            BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    
            if (0 <= status)
            {
                PUCHAR pbHash = (PUCHAR)alloca(HashBlockLength);
    
                0 <= (status = BCryptHashData(hHash, const_cast<PUCHAR>(pbToBeSigned), cbToBeSigned, 0)) &&
                    0 <= (status = BCryptFinishHash(hHash, pbHash, HashBlockLength, 0));
    
                BCryptDestroyHash(hHash);
    
                if (0 <= status)
                {
                    BCRYPT_PKCS1_PADDING_INFO pi = { pszAlgId };
    
                    status = BCryptSignHash(hKey, &pi, pbHash, HashBlockLength, 
                        pbSignature, *pcbSignature, pcbSignature, BCRYPT_PAD_PKCS1);
                }
            }
        }
    
        return status;
    }
    
    inline NTSTATUS openssl_sign(_In_ BCRYPT_KEY_HANDLE hKey,
                                 _In_ PCSTR szToBeSigned,
                                 _Out_ PUCHAR pbSignature, 
                                 _Inout_ PULONG pcbSignature,
                                 _In_ PCWSTR pszAlgId)
    {
        return openssl_sign(hKey, (PCUCHAR)szToBeSigned, (ULONG)strlen(szToBeSigned), pbSignature, pcbSignature, pszAlgId);
    }
    
    NTSTATUS BCryptImportKey(_Out_ BCRYPT_KEY_HANDLE *phKey, 
                             _In_ PCWSTR pszBlobType, 
                             _In_ BCRYPT_RSAKEY_BLOB* prkb, 
                             _In_ ULONG cb)
    {
        BCRYPT_ALG_HANDLE hAlgorithm;
    
        NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RSA_ALGORITHM, 0, 0);
    
        if (0 <= status)
        {
            status = BCryptImportKeyPair(hAlgorithm, 0, pszBlobType, phKey, (PUCHAR)prkb, cb, 0);
    
            BCryptCloseAlgorithmProvider(hAlgorithm, 0);
        }
    
        return status;
    }
    
    HRESULT BCryptImportPrivateKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCUCHAR pbKey, _In_ ULONG cbKey)
    {
        ULONG cb;
        PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;
    
        ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, 
            pbKey, cbKey, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, (void**)&PrivateKeyInfo, &cb));
    
        if (dwError == NOERROR)
        {
            BCRYPT_RSAKEY_BLOB* prkb;
    
            dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
                CNG_RSA_PRIVATE_KEY_BLOB, PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData, 
                CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&prkb, &cb));
    
            LocalFree(PrivateKeyInfo);
    
            if (dwError == NOERROR)
            {
                NTSTATUS status = BCryptImportKey(phKey, BCRYPT_RSAPRIVATE_BLOB, prkb, cb);
                LocalFree(prkb);
                return HRESULT_FROM_NT(status);
            }
        }
    
        return HRESULT_FROM_WIN32(dwError);
    }
    
    HRESULT BCryptImportPublicKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ PCUCHAR pbKeyOrCert, _In_ ULONG cbKeyOrCert, _In_ bool bCert)
    {
        ULONG cb;
    
        union {
            PVOID pvStructInfo;
            PCERT_INFO pCertInfo;
            PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
        };
    
        ULONG dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
            bCert ? X509_CERT_TO_BE_SIGNED : X509_PUBLIC_KEY_INFO, 
            pbKeyOrCert, cbKeyOrCert, CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, &pvStructInfo, &cb));
    
        if (dwError == NOERROR)
        {
            BCRYPT_RSAKEY_BLOB* prkb;
    
            PVOID pv = pvStructInfo;
    
            if (bCert)
            {
                PublicKeyInfo = &pCertInfo->SubjectPublicKeyInfo;
            }
    
            dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
                CNG_RSA_PUBLIC_KEY_BLOB, 
                PublicKeyInfo->PublicKey.pbData, 
                PublicKeyInfo->PublicKey.cbData, 
                CRYPT_DECODE_ALLOC_FLAG, 0, (void**)&prkb, &cb));
    
            LocalFree(pv);
    
            if (dwError == NOERROR)
            {
                NTSTATUS status = BCryptImportKey(phKey, BCRYPT_RSAPUBLIC_BLOB, prkb, cb);
                LocalFree(prkb);
                return HRESULT_FROM_NT(status);
            }
        }
    
        return HRESULT_FROM_WIN32(dwError);
    }
    
    enum BLOB_TYPE { bt_priv, bt_pub, bt_cert };
    
    HRESULT BCryptImportKey(_Out_ BCRYPT_KEY_HANDLE *phKey, _In_ BLOB_TYPE bt, _In_ PCSTR szKey, _In_ ULONG cchKey)
    {
        PUCHAR pbKey = 0;
        ULONG cbKey = 0;
        HRESULT hr;
    
        while (CryptStringToBinaryA(szKey, cchKey, CRYPT_STRING_BASE64HEADER, pbKey, &cbKey, 0, 0))
        {
            if (pbKey)
            {
                switch (bt)
                {
                case bt_priv:
                    hr = BCryptImportPrivateKey(phKey, pbKey, cbKey);
                    break;
                case bt_pub:
                    hr = BCryptImportPublicKey(phKey, pbKey, cbKey, false);
                    break;
                case bt_cert:
                    hr = BCryptImportPublicKey(phKey, pbKey, cbKey, true);
                    break;
                default: hr = E_INVALIDARG;
                }
    
                _freea(pbKey);
    
                return hr;
            }
    
            if (!(pbKey = (PUCHAR)_malloca(cbKey)))
            {
                break;
            }
        }
    
        hr = HRESULT_FROM_WIN32(GetLastError());
    
        if (pbKey) _freea(pbKey);
    
        return hr;
    }
    
    void DoCNGTest(_In_ PCSTR szToBeSigned, 
                   _In_ PCSTR szPrivateKey, 
                   _In_ ULONG cchPrivateKey,
                   _In_ PCSTR szPublicKeyOrCert, 
                   _In_ ULONG cchPublicKeyOrCert,
                   _In_ bool bCert)
    {
        HRESULT hr;
        BCRYPT_KEY_HANDLE hKey;
    
        PUCHAR pbSignature = 0;
        ULONG cbSignature = 0, cb;
    
        if (0 <= (hr = BCryptImportKey(&hKey, bt_priv, szPrivateKey, cchPrivateKey)))
        {
            if (0 <= (hr = BCryptGetProperty(hKey, BCRYPT_SIGNATURE_LENGTH, (PUCHAR)&cbSignature, sizeof(ULONG), &cb, 0)))
            {
                pbSignature = (PUCHAR)alloca(cbSignature);
    
                hr = HRESULT_FROM_NT(openssl_sign(hKey, szToBeSigned, pbSignature, &cbSignature, BCRYPT_SHA256_ALGORITHM));
            }
    
            BCryptDestroyKey(hKey);
        }
    
        if (0 <= hr)
        {
            if (0 <= (hr = BCryptImportKey(&hKey, bCert ? bt_cert : bt_pub, szPublicKeyOrCert, cchPublicKeyOrCert)))
            {
                hr = HRESULT_FROM_NT(openssl_verify(hKey, szToBeSigned, pbSignature, cbSignature, BCRYPT_SHA256_ALGORITHM));
    
                if (0 > hr)
                {
                    __debugbreak();
                }
    
                BCryptDestroyKey(hKey);
            }
        }
    }