#include "gdcmCAPICryptographicMessageSyntax.h"
#include <stdio.h>
namespace gdcm
{
CAPICryptographicMessageSyntax::CAPICryptographicMessageSyntax() : hProv(0), hRsaPrivK(0), cipherType(AES128_CIPHER)
{
initialized = Initialize();
}
CAPICryptographicMessageSyntax::~CAPICryptographicMessageSyntax()
{
for (std::vector<PCCERT_CONTEXT>::iterator it = certifList.begin(); it != certifList.end(); ++it)
{
CertFreeCertificateContext(*it);
}
if (hRsaPrivK) CryptDestroyKey(hRsaPrivK);
if (!CryptReleaseContext(hProv, 0))
{
gdcmWarningMacro("Error when releasing context: 0x" << std::hex << GetLastError());
}
}
bool CAPICryptographicMessageSyntax::SetPassword(const char * , size_t )
{
gdcmWarningMacro( "CAPI does not support Password Based Encryption." );
return false;
}
bool CAPICryptographicMessageSyntax::ParseCertificateFile( const char *filename )
{
bool ret = false;
unsigned char *certHexBuf = NULL, *certBin = NULL;
DWORD certHexBufLen, certBinLen;
if ( !LoadFile(filename, certHexBuf, certHexBufLen) )
goto err;
if ( !CryptStringToBinaryA( (LPCSTR)certHexBuf, 0, CRYPT_STRING_BASE64_ANY, NULL, &certBinLen, NULL, NULL ) )
{
gdcmErrorMacro( "CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
goto err;
}
certBin = new unsigned char[certBinLen];
if ( !CryptStringToBinaryA( (LPCSTR)certHexBuf, 0, CRYPT_STRING_BASE64_ANY, certBin, &certBinLen, NULL, NULL ) )
{
gdcmErrorMacro( "CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
goto err;
}
PCCERT_CONTEXT certContext;
certContext = CertCreateCertificateContext(X509_ASN_ENCODING, certBin, certBinLen);
if (certContext == NULL)
{
gdcmErrorMacro( "CertCreateCertificateContext failed with error 0x" << std::hex << GetLastError() );
goto err;
}
certifList.push_back(certContext);
ret = true;
err:
if (certBin) delete[] certBin;
if (certHexBuf) delete[] certHexBuf;
return ret;
}
bool CAPICryptographicMessageSyntax::ParseKeyFile( const char *filename ) {
bool ret = false;
unsigned char *keyHexBuffer = NULL, *keyBinBuffer = NULL, *keyBlob = NULL;
DWORD keyHexBufferLen, keyBinBufferLen, keyBlobLen;
HCRYPTKEY hKey = 0;
if (!LoadFile(filename, keyHexBuffer, keyHexBufferLen))
goto err;
if ( !CryptStringToBinaryA((LPCSTR)keyHexBuffer, 0, CRYPT_STRING_BASE64_ANY, NULL, &keyBinBufferLen, NULL, NULL) )
{
gdcmErrorMacro( "Failed to convert from BASE64. CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
goto err;
}
keyBinBuffer = new unsigned char[keyBinBufferLen];
if ( !CryptStringToBinaryA((LPCSTR)keyHexBuffer, 0, CRYPT_STRING_BASE64_ANY, keyBinBuffer, &keyBinBufferLen, NULL, NULL) )
{
gdcmErrorMacro( "Failed to convert from BASE64. CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, keyBinBuffer, keyBinBufferLen, 0, NULL, NULL, &keyBlobLen))
{
gdcmErrorMacro( "Failed to parse private key. CryptDecodeObjectEx failed with error 0x" << std::hex << GetLastError() );
goto err;
}
keyBlob = new unsigned char[keyBlobLen];
if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, keyBinBuffer, keyBinBufferLen, 0, NULL, keyBlob, &keyBlobLen))
{
gdcmErrorMacro( "Failed to parse private key. CryptDecodeObjectEx failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if (!CryptImportKey(hProv, keyBlob, keyBlobLen, 0, 0, &hKey))
{
gdcmErrorMacro( "CryptImportKey failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if (hRsaPrivK) CryptDestroyKey(hRsaPrivK);
hRsaPrivK = hKey;
ret = true;
err:
if (keyHexBuffer) delete[] keyHexBuffer;
if (keyBinBuffer) delete[] keyBinBuffer;
if (keyBlob) delete[] keyBlob;
return ret;
}
void CAPICryptographicMessageSyntax::SetCipherType(CryptographicMessageSyntax::CipherTypes type)
{
cipherType = type;
}
CryptographicMessageSyntax::CipherTypes CAPICryptographicMessageSyntax::GetCipherType() const
{
return cipherType;
}
bool CAPICryptographicMessageSyntax::Encrypt(char *output, size_t &outlen, const char *array, size_t len) const
{
CRYPT_ALGORITHM_IDENTIFIER EncryptAlgorithm = {0};
const char *objid = GetCipherObjId();
if( !objid )
{
gdcmErrorMacro( "Could not GetCipherObjId" );
return false;
}
EncryptAlgorithm.pszObjId = (char*)objid;
CRYPT_ENCRYPT_MESSAGE_PARA EncryptParams = {0};
EncryptParams.cbSize = sizeof(EncryptParams);
EncryptParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
EncryptParams.hCryptProv = hProv;
EncryptParams.ContentEncryptionAlgorithm = EncryptAlgorithm;
if (certifList.size() == 0)
{
gdcmErrorMacro("No recipients certificates loaded.");
return false;
}
if(! CryptEncryptMessage(&EncryptParams, (DWORD)certifList.size(), (PCCERT_CONTEXT *)&certifList[0], (unsigned char *)array, (DWORD)len, (unsigned char *)output, (DWORD *)&outlen) )
{
DWORD dwResult = GetLastError();
gdcmErrorMacro( "Couldn't encrypt message. CryptEncryptMessage failed with error 0x" << std::hex << dwResult );
if (dwResult == CRYPT_E_UNKNOWN_ALGO)
{
gdcmErrorMacro("Unknown encryption algorithm. If on Windows XP please use only 3DES.");
}
return false;
}
return true;
}
bool CAPICryptographicMessageSyntax::Decrypt(char *output, size_t &outlen, const char *array, size_t len) const
{
bool ret = false;
unsigned char* cek = NULL;
HCRYPTMSG hMsg = NULL;
PCMSG_CMS_RECIPIENT_INFO recipientInfo = NULL;
DWORD dwMessageType, cbMessageTypeLen = sizeof(DWORD);
PCRYPT_ALGORITHM_IDENTIFIER cekAlg = NULL;
ALG_ID kekAlg;
DWORD kekAlgLen = sizeof(ALG_ID);
DWORD nrOfRecipeints, nrOfRecipientsLen = sizeof(DWORD);
unsigned char* bareContent = NULL;
struct {
BLOBHEADER header;
DWORD cbKeySize;
unsigned char rgbKeyData[32]; } keyBlob = {{0}};
if (hRsaPrivK == 0)
{
gdcmErrorMacro("No private key loaded loaded.");
return false;
}
if (! (hMsg = CryptMsgOpenToDecode(CRYPT_ASN_ENCODING | X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CMSG_ENVELOPED_DATA_PKCS_1_5_VERSION, 0, NULL, NULL)) )
{
gdcmErrorMacro( "MsgOpenToDecode failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if(! CryptMsgUpdate(hMsg, (unsigned char*)array, (DWORD)len, TRUE))
{
gdcmErrorMacro( "MsgUpdate failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if(! CryptMsgGetParam(hMsg, CMSG_TYPE_PARAM, 0, &dwMessageType, &cbMessageTypeLen))
{
gdcmErrorMacro( "CryptMsgGetParam CMSG_TYPE_PARAM failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if(dwMessageType != CMSG_ENVELOPED)
{
gdcmErrorMacro("Wrong message type ( != CMSG_ENVELOPED )");
goto err;
}
if(! CryptGetKeyParam(hRsaPrivK, KP_ALGID, (unsigned char*)&kekAlg, &kekAlgLen, 0))
{
gdcmErrorMacro( "MsgGetParam KP_ALGID failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if (kekAlg != CALG_RSA_KEYX)
{
gdcmErrorMacro( "Key encryption algorithm is not RSA." );
goto err;
}
if(! CryptMsgGetParam(hMsg, CMSG_RECIPIENT_COUNT_PARAM, 0, &nrOfRecipeints, &nrOfRecipientsLen))
{
gdcmErrorMacro( "Decode CMSG_RECIPIENT_COUNT_PARAM failed with error 0x" << std::hex << GetLastError() );
goto err;
}
DWORD cekLen;
{
BOOL foundRecipient = FALSE;
for (DWORD i=0; i < nrOfRecipeints; i++)
{
if (recipientInfo) delete[] recipientInfo;
DWORD cbRecipientInfoLen;
if(! CryptMsgGetParam(hMsg, CMSG_CMS_RECIPIENT_INFO_PARAM, i, NULL, &cbRecipientInfoLen))
{
gdcmErrorMacro( "MsgGetParam CMSG_CMS_RECIPIENT_INFO_PARAM size failed with error 0x" << std::hex << GetLastError() );
goto err;
}
recipientInfo = (PCMSG_CMS_RECIPIENT_INFO) new unsigned char[cbRecipientInfoLen];
if(! CryptMsgGetParam(hMsg, CMSG_CMS_RECIPIENT_INFO_PARAM, i, recipientInfo, &cbRecipientInfoLen))
{
gdcmErrorMacro( "MsgGetParam CMSG_CMS_RECIPIENT_INFO_PARAM failed with error 0x" << std::hex << GetLastError() );
goto err;
}
DWORD rsaPadding = 0;
if (strcmp(recipientInfo->pKeyTrans->KeyEncryptionAlgorithm.pszObjId, szOID_RSAES_OAEP) == 0)
{
rsaPadding = CRYPT_OAEP;
}
cekLen = recipientInfo->pKeyTrans->EncryptedKey.cbData;
cek = recipientInfo->pKeyTrans->EncryptedKey.pbData;
ReverseBytes(cek, cekLen);
if ( (foundRecipient =
CryptDecrypt(hRsaPrivK, 0, TRUE, rsaPadding, cek, &cekLen)) )
break;
}
if (!foundRecipient)
{
gdcmErrorMacro( "No recipient found with the specified private key." );
goto err;
}
}
DWORD cekAlgLen;
if(! CryptMsgGetParam(hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, 0, NULL, &cekAlgLen))
{
gdcmErrorMacro( "MsgGetParam CMSG_ENVELOPE_ALGORITHM_PARAM failed with error 0x" << std::hex << GetLastError() );
goto err;
}
cekAlg = (PCRYPT_ALGORITHM_IDENTIFIER) new unsigned char[cekAlgLen];
if(! CryptMsgGetParam(hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, 0, cekAlg, &cekAlgLen))
{
gdcmErrorMacro( "MsgGetParam CMSG_ENVELOPE_ALGORITHM_PARAM failed with error 0x" << std::hex << GetLastError() );
goto err;
}
HCRYPTKEY hCEK;
keyBlob.header.bType = PLAINTEXTKEYBLOB;
keyBlob.header.bVersion = CUR_BLOB_VERSION;
keyBlob.header.reserved = 0;
keyBlob.header.aiKeyAlg = GetAlgIdByObjId(cekAlg->pszObjId);
keyBlob.cbKeySize = cekLen;
gdcm_assert(cekLen <= 32);
memcpy(keyBlob.rgbKeyData, cek, cekLen);
if (!CryptImportKey(hProv, (unsigned char*)&keyBlob, sizeof(keyBlob), 0, 0, &hCEK))
{
gdcmErrorMacro( "CryptImportKey failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if(! CryptSetKeyParam(hCEK, KP_IV, (unsigned char *) cekAlg->Parameters.pbData+2, 0)) {
gdcmErrorMacro( "SetKeyParam KP_IV failed with error 0x" << std::hex << GetLastError() );
goto err;
}
{
DWORD dwMode = CRYPT_MODE_CBC;
if(! CryptSetKeyParam(hCEK, KP_MODE, (unsigned char*) &dwMode, 0))
{
gdcmErrorMacro( "SetKeyParam KP_MODE failed with error 0x" << std::hex << GetLastError() );
goto err;
}
}
DWORD bareContentLen;
if(! CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, NULL, &bareContentLen))
{
gdcmErrorMacro( "MsgGetParam CMSG_BARE_CONTENT_PARAM size failed with error 0x" << std::hex << GetLastError() );
goto err;
}
bareContent = new unsigned char[bareContentLen];
if(! CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, bareContent, &bareContentLen))
{
gdcmErrorMacro( "MsgGetParam CMSG_BARE_CONTENT_PARAM failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if (! CryptDecrypt(hCEK, 0, TRUE, 0, bareContent, &bareContentLen))
{
gdcmErrorMacro( "CryptDecrypt failed with error 0x" << std::hex << GetLastError() );
goto err;
}
if (bareContentLen > outlen)
{
gdcmErrorMacro( "Supplied output buffer too small: " << bareContentLen << " bytes needed." );
goto err;
}
memcpy(output, bareContent, bareContentLen);
outlen = bareContentLen;
ret = true;
err:
if (hMsg) CryptMsgClose(hMsg);
if (recipientInfo) delete[] recipientInfo;
if (bareContent) delete[] bareContent;
if (cekAlg) delete[] cekAlg;
return ret;
}
ALG_ID CAPICryptographicMessageSyntax::GetAlgIdByObjId(const char * pszObjId)
{
#ifndef szOID_NIST_AES128_CBC
#define szOID_NIST_AES128_CBC "2.16.840.1.101.3.4.1.2"
#define szOID_NIST_AES192_CBC "2.16.840.1.101.3.4.1.22"
#define szOID_NIST_AES256_CBC "2.16.840.1.101.3.4.1.42"
#endif
if (strcmp(pszObjId, szOID_NIST_AES128_CBC) == 0)
{
return CALG_AES_128;
}
else if (strcmp(pszObjId, szOID_NIST_AES192_CBC) == 0)
{
return CALG_AES_192;
}
else if (strcmp(pszObjId, szOID_NIST_AES256_CBC) == 0)
{
return CALG_AES_256;
}
else if (strcmp(pszObjId, szOID_RSA_DES_EDE3_CBC) == 0)
{
return CALG_3DES;
}
return 0;
}
const char *CAPICryptographicMessageSyntax::GetCipherObjId() const
{
switch( cipherType )
{
case AES128_CIPHER:
return szOID_NIST_AES128_CBC;
case AES192_CIPHER:
return szOID_NIST_AES192_CBC;
case AES256_CIPHER:
return szOID_NIST_AES256_CBC;
case DES3_CIPHER:
return szOID_RSA_DES_EDE3_CBC;
}
return 0;
}
bool CAPICryptographicMessageSyntax::Initialize()
{
DWORD dwResult;
if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
dwResult = GetLastError();
if (dwResult == NTE_BAD_KEYSET)
{
if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT))
{
dwResult = GetLastError();
gdcmErrorMacro( "CryptAcquireContext() failed:" << std::hex << dwResult);
return false;
}
}
else if (dwResult == NTE_KEYSET_NOT_DEF)
{
gdcmWarningMacro( "Certificate based encryption is supported on Windows XP only using 3DES." );
if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A" (Prototype)" , PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
dwResult = GetLastError();
if (dwResult == NTE_BAD_KEYSET)
{
if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A" (Prototype)" , PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT))
{
dwResult = GetLastError();
gdcmErrorMacro( "CryptAcquireContext() failed: " << std::hex << dwResult );
return false;
}
}
else
{
dwResult = GetLastError();
return false;
}
}
}
else
{
dwResult = GetLastError();
return false;
}
}
return true;
}
void CAPICryptographicMessageSyntax::ReverseBytes(unsigned char* data, DWORD len)
{
unsigned char temp;
for (DWORD i = 0; i < len/2; i++)
{
temp = data[len-i-1];
data[len-i-1] = data[i];
data[i] = temp;
}
}
bool CAPICryptographicMessageSyntax::LoadFile(const char * filename, unsigned char* & buffer, DWORD & bufLen)
{
gdcm_assert( !buffer );
FILE * f = fopen(filename, "rb");
if (f == NULL)
{
gdcmErrorMacro("Couldn't open the file: " << filename);
fclose(f);
return false;
}
fseek(f, 0L, SEEK_END);
long sz = ftell(f);
rewind(f);
buffer = new unsigned char[sz];
if( !buffer )
{
fclose(f);
return false;
}
bufLen = sz;
while (sz)
{
size_t l = fread(buffer + bufLen - sz, sizeof(unsigned char), sz, f);
sz -= (long)l;
}
fclose(f);
return true;
}
}