Load an PEM encoded X.509 certificate into Windows CryptoAPI
KJKHyperion said in his answer:
I discovered the "magic" sequence of calls to import a RSA public key in PEM format. Here you go:
- decode the key into a binary blob with CryptStringToBinary; pass CRYPT_STRING_BASE64HEADER in dwFlags
- decode the binary key blob into a CERT_PUBLIC_KEY_INFO with CryptDecodeObjectEx; pass X509_ASN_ENCODING in dwCertEncodingType and X509_PUBLIC_KEY_INFO in lpszStructType
- decode the PublicKey blob from the CERT_PUBLIC_KEY_INFO into a RSA key blob with CryptDecodeObjectEx; pass X509_ASN_ENCODING in dwCertEncodingType and RSA_CSP_PUBLICKEYBLOB in lpszStructType
- import the RSA key blob with CryptImportKey
This sequence really helped me understand what's going on, but it didn't work for me as-is. The second call to CryptDecodeObjectEx
gave me an error:
"ASN.1 bad tag value met".
After many attempts at understanding Microsoft documentation, I finally realized that the output of the fist decode cannot be decoded as ASN again, and that it is actually ready for import. With this understanding I found the answer in the following link:
http://www.ms-news.net/f2748/problem-importing-public-key-4052577.html
Following is my own program that imports a public key from a .pem file to a CryptApi context:
int main()
{
char pemPubKey[2048];
int readLen;
char derPubKey[2048];
size_t derPubKeyLen = 2048;
CERT_PUBLIC_KEY_INFO *publicKeyInfo;
int publicKeyInfoLen;
HANDLE hFile;
HCRYPTPROV hProv = 0;
HCRYPTKEY hKey = 0;
/*
* Read the public key cert from the file
*/
hFile = CreateFileA( "c:\\pub.pem", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if ( hFile == INVALID_HANDLE_VALUE )
{
fprintf( stderr, "Failed to open file. error: %d\n", GetLastError() );
}
if ( !ReadFile( hFile, pemPubKey, 2048, &readLen, NULL ) )
{
fprintf( stderr, "Failed to read file. error: %d\n", GetLastError() );
}
/*
* Convert from PEM format to DER format - removes header and footer and decodes from base64
*/
if ( !CryptStringToBinaryA( pemPubKey, 0, CRYPT_STRING_BASE64HEADER, derPubKey, &derPubKeyLen, NULL, NULL ) )
{
fprintf( stderr, "CryptStringToBinary failed. Err: %d\n", GetLastError() );
}
/*
* Decode from DER format to CERT_PUBLIC_KEY_INFO
*/
if ( !CryptDecodeObjectEx( X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, derPubKey, derPubKeyLen,
CRYPT_ENCODE_ALLOC_FLAG, NULL, &publicKeyInfo, &publicKeyInfoLen ) )
{
fprintf( stderr, "CryptDecodeObjectEx 1 failed. Err: %p\n", GetLastError() );
return -1;
}
/*
* Acquire context
*/
if( !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) )
{
{
printf( "CryptAcquireContext failed - err=0x%x.\n", GetLastError() );
return -1;
}
}
/*
* Import the public key using the context
*/
if ( !CryptImportPublicKeyInfo( hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey ) )
{
fprintf( stderr, "CryptImportPublicKeyInfo failed. error: %d\n", GetLastError() );
return -1;
}
LocalFree( publicKeyInfo );
/*
* Now use hKey to encrypt whatever you need.
*/
return 0;
}
Import PEM encoded X.509 certificate into iOS KeyChain
The following calls were missing before creating the PKCS12:
OpenSSL_add_all_algorithms();
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
These solved the problems with the missing cipher and also a subsequent problem of a missing digest.
rsa encryption in windows crypto and decryption in openssl
CryptEncrypt
, for reasons that probably made sense to the author at the time, writes the bytes down backwards. Or, rather, it writes them in byte minor/little-endian order whereas almost every other cryptography library (including the Windows CNG BCryptEncrypt
and NCryptEncrypt
routines) writes them in byte major/big-endian order.
So you need to reverse data coming out of CryptEncrypt
, and reverse it going in to CryptDecrypt
.
For example, in .NET Core's RSACryptoServiceProvider.Encrypt it calls CapiHelper.EncryptKey which calls CryptEncrypt
then Array.Reverse
before returning.
The CryptEncrypt documentation has as the last sentence in the Remarks section
The ciphertext is returned in little-endian format.
Parsing and printing X.509 attributes from PEM
You want to use encoding.pem
to decode the pem file, which will give you the DER blocks you can decode with the crypto/x509
package.
For example:
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
log.Fatal(err)
}
var blocks [][]byte
for {
var certDERBlock *pem.Block
certDERBlock, certPEMBlock = pem.Decode(certPEMBlock)
if certDERBlock == nil {
break
}
if certDERBlock.Type == "CERTIFICATE" {
blocks = append(blocks, certDERBlock.Bytes)
}
}
for _, block := range blocks {
cert, err := x509.ParseCertificate(block)
if err != nil {
log.Println(err)
continue
}
fmt.Println("Certificate:")
fmt.Printf("\tSubject: %+v\n", cert.Subject)
fmt.Printf("\tDNS Names: %+v\n", cert.DNSNames)
fmt.Printf("\tEmailAddresses: %+v\n", cert.EmailAddresses)
fmt.Printf("\tIPAddresses: %+v\n", cert.IPAddresses)
}
Related Topics
Linking C++ Code with 'Gcc' (Without G++)
How to Typedef a Template Class
What Is the Right Approach When Using Stl Container for Median Calculation
Lru Implementation in Production Code
C++ Why the Assignment Operator Should Return a Const Ref in Order to Avoid (A=B)=C
What Does "Typedef Void (*Something)()" Mean
Is Gcc Std::Unordered_Map Implementation Slow? If So - Why
Openmp Nested Parallel for Loops VS Inner Parallel For
How to Find the Name of the Calling Function
How to Get the Icon, Mime Type, and Application Associated with a File in the Linux Desktop
What Is the Performance Impact of Using Int64_T Instead of Int32_T on 32-Bit Systems
What's Time Complexity of This Algorithm for Finding All Combinations
Virtual Dispatch Implementation Details