1 /*=========================================================================
2 
3   Program: GDCM (Grassroots DICOM). A DICOM library
4 
5   Copyright (c) 2006-2011 Mathieu Malaterre
6   All rights reserved.
7   See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.
8 
9      This software is distributed WITHOUT ANY WARRANTY; without even
10      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
11      PURPOSE.  See the above copyright notice for more information.
12 
13 =========================================================================*/
14 #include "gdcmCAPICryptographicMessageSyntax.h"
15 
16 #include <stdio.h> // fseek
17 
18 namespace gdcm
19 {
20 
CAPICryptographicMessageSyntax()21 CAPICryptographicMessageSyntax::CAPICryptographicMessageSyntax() : hProv(0), hRsaPrivK(0), cipherType(AES128_CIPHER)
22 {
23   initialized = Initialize();
24 }
25 
~CAPICryptographicMessageSyntax()26 CAPICryptographicMessageSyntax::~CAPICryptographicMessageSyntax()
27 {
28   for (std::vector<PCCERT_CONTEXT>::iterator it = certifList.begin(); it != certifList.end(); ++it)
29     {
30     CertFreeCertificateContext(*it);
31     /*if (! CertFreeCertificateContext(*it))
32       {
33       gdcmWarningMacro( "Error at releasing certificate context: " << std::hex << GetLastError() );
34       }*/
35     }
36 
37   if (hRsaPrivK) CryptDestroyKey(hRsaPrivK);
38 
39   if (!CryptReleaseContext(hProv, 0))
40     {
41     gdcmWarningMacro("Error when releasing context: 0x" << std::hex << GetLastError());
42     }
43 }
44 
45 // http://stackoverflow.com/questions/11709500/capi-does-not-support-password-based-encryption-pbe-encryption
SetPassword(const char *,size_t)46 bool CAPICryptographicMessageSyntax::SetPassword(const char * , size_t )
47 {
48   gdcmWarningMacro( "CAPI does not support Password Based Encryption." );
49   return false;
50 }
51 
ParseCertificateFile(const char * filename)52 bool CAPICryptographicMessageSyntax::ParseCertificateFile( const char *filename )
53 {
54   bool ret = false;
55   unsigned char *certHexBuf = NULL, *certBin = NULL;
56   DWORD certHexBufLen, certBinLen;
57 
58   if ( !LoadFile(filename, certHexBuf, certHexBufLen) )
59     goto err;
60 
61   // Call to get the needed amount of space
62   if ( !CryptStringToBinaryA( (LPCSTR)certHexBuf, 0, CRYPT_STRING_BASE64_ANY, NULL, &certBinLen, NULL, NULL ) )
63     {
64     gdcmErrorMacro( "CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
65     goto err;
66     }
67   certBin = new unsigned char[certBinLen];
68   // Convert from PEM format to DER format - removes header and footer and decodes from base64
69   if ( !CryptStringToBinaryA( (LPCSTR)certHexBuf, 0, CRYPT_STRING_BASE64_ANY, certBin, &certBinLen, NULL, NULL ) )
70     {
71     gdcmErrorMacro( "CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
72     goto err;
73     }
74 
75   PCCERT_CONTEXT certContext;
76   certContext = CertCreateCertificateContext(X509_ASN_ENCODING, certBin, certBinLen);
77   if (certContext == NULL)
78     {
79     gdcmErrorMacro( "CertCreateCertificateContext failed with error 0x" << std::hex << GetLastError() );
80     goto err;
81     }
82   certifList.push_back(certContext);
83 
84   ret = true;
85 
86 err:
87   if (certBin) delete[] certBin;
88   if (certHexBuf) delete[] certHexBuf;
89 
90   return ret;
91 }
92 
ParseKeyFile(const char * filename)93 bool CAPICryptographicMessageSyntax::ParseKeyFile( const char *filename ) {
94   bool ret = false;
95   unsigned char *keyHexBuffer = NULL, *keyBinBuffer = NULL, *keyBlob = NULL;
96   DWORD keyHexBufferLen, keyBinBufferLen, keyBlobLen;
97   HCRYPTKEY hKey = 0;
98 
99   if (!LoadFile(filename, keyHexBuffer, keyHexBufferLen))
100     goto err;
101 
102   if ( !CryptStringToBinaryA((LPCSTR)keyHexBuffer, 0, CRYPT_STRING_BASE64_ANY, NULL, &keyBinBufferLen, NULL, NULL) )
103     {
104     gdcmErrorMacro( "Failed to convert from BASE64. CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
105     goto err;
106     }
107   keyBinBuffer = new unsigned char[keyBinBufferLen];
108   if ( !CryptStringToBinaryA((LPCSTR)keyHexBuffer, 0, CRYPT_STRING_BASE64_ANY, keyBinBuffer, &keyBinBufferLen, NULL, NULL) )
109     {
110     gdcmErrorMacro( "Failed to convert from BASE64. CryptStringToBinary failed with error 0x" << std::hex << GetLastError() );
111     goto err;
112     }
113 
114   if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, keyBinBuffer, keyBinBufferLen, 0, NULL, NULL, &keyBlobLen))
115     {
116     gdcmErrorMacro( "Failed to parse private key. CryptDecodeObjectEx failed with error 0x" << std::hex << GetLastError() );
117     goto err;
118     }
119   keyBlob = new unsigned char[keyBlobLen];
120   if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, keyBinBuffer, keyBinBufferLen, 0, NULL, keyBlob, &keyBlobLen))
121     {
122     gdcmErrorMacro( "Failed to parse private key. CryptDecodeObjectEx failed with error 0x" << std::hex << GetLastError() );
123     goto err;
124     }
125 
126   if (!CryptImportKey(hProv, keyBlob, keyBlobLen, 0, 0, &hKey))
127     {
128     gdcmErrorMacro( "CryptImportKey failed with error 0x" << std::hex << GetLastError() );
129     goto err;
130     }
131   if (hRsaPrivK) CryptDestroyKey(hRsaPrivK);
132   hRsaPrivK = hKey;
133 
134   ret = true;
135 
136 err:
137   if (keyHexBuffer) delete[] keyHexBuffer;
138   if (keyBinBuffer) delete[] keyBinBuffer;
139   if (keyBlob) delete[] keyBlob;
140   return ret;
141 }
142 
SetCipherType(CryptographicMessageSyntax::CipherTypes type)143 void CAPICryptographicMessageSyntax::SetCipherType(CryptographicMessageSyntax::CipherTypes type)
144 {
145   cipherType = type;
146 }
147 
GetCipherType() const148 CryptographicMessageSyntax::CipherTypes CAPICryptographicMessageSyntax::GetCipherType() const
149 {
150   return cipherType;
151 }
152 
Encrypt(char * output,size_t & outlen,const char * array,size_t len) const153 bool CAPICryptographicMessageSyntax::Encrypt(char *output, size_t &outlen, const char *array, size_t len) const
154 {
155   CRYPT_ALGORITHM_IDENTIFIER EncryptAlgorithm = {0};
156   const char *objid = GetCipherObjId();
157   if( !objid )
158     {
159     gdcmErrorMacro( "Could not GetCipherObjId" );
160     return false;
161     }
162   EncryptAlgorithm.pszObjId = (char*)objid;
163 
164   CRYPT_ENCRYPT_MESSAGE_PARA EncryptParams = {0};
165   EncryptParams.cbSize = sizeof(EncryptParams);
166   EncryptParams.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
167   EncryptParams.hCryptProv = hProv;
168   EncryptParams.ContentEncryptionAlgorithm = EncryptAlgorithm;
169 
170   if (certifList.size() == 0)
171     {
172     gdcmErrorMacro("No recipients certificates loaded.");
173     return false;
174     }
175 
176   if(! CryptEncryptMessage(&EncryptParams, (DWORD)certifList.size(), (PCCERT_CONTEXT *)&certifList[0], (unsigned char *)array, (DWORD)len, (unsigned char *)output, (DWORD *)&outlen) )
177     {
178     DWORD dwResult = GetLastError();
179     gdcmErrorMacro( "Couldn't encrypt message. CryptEncryptMessage failed with error 0x" << std::hex << dwResult );
180     if (dwResult == CRYPT_E_UNKNOWN_ALGO)
181       {
182       gdcmErrorMacro("Unknown encryption algorithm. If on Windows XP please use only 3DES.");
183       }
184     return false;
185     }
186   return true;
187 }
188 
Decrypt(char * output,size_t & outlen,const char * array,size_t len) const189 bool CAPICryptographicMessageSyntax::Decrypt(char *output, size_t &outlen, const char *array, size_t len) const
190 {
191   bool ret = false;
192   unsigned char* cek = NULL;
193   HCRYPTMSG hMsg = NULL;
194   PCMSG_CMS_RECIPIENT_INFO recipientInfo = NULL;
195   DWORD dwMessageType, cbMessageTypeLen = sizeof(DWORD);
196   PCRYPT_ALGORITHM_IDENTIFIER cekAlg = NULL;
197   ALG_ID kekAlg;
198   DWORD kekAlgLen = sizeof(ALG_ID);
199   DWORD nrOfRecipeints, nrOfRecipientsLen = sizeof(DWORD);
200   unsigned char* bareContent = NULL;
201   struct {
202     BLOBHEADER header;
203     DWORD cbKeySize;
204     unsigned char rgbKeyData[32]; //the maximum is 256 bit for aes
205   } keyBlob = {{0}};
206 
207   if (hRsaPrivK == 0)
208     {
209     gdcmErrorMacro("No private key loaded loaded.");
210     return false;
211     }
212 
213   if (! (hMsg = CryptMsgOpenToDecode(CRYPT_ASN_ENCODING | X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CMSG_ENVELOPED_DATA_PKCS_1_5_VERSION, 0, NULL, NULL)) )
214     {
215     gdcmErrorMacro( "MsgOpenToDecode failed with error 0x" << std::hex << GetLastError() );
216     goto err;
217     }
218 
219   if(! CryptMsgUpdate(hMsg, (unsigned char*)array, (DWORD)len, TRUE))
220     {
221     gdcmErrorMacro( "MsgUpdate failed with error 0x" << std::hex << GetLastError() );
222     goto err;
223     }
224 
225   if(! CryptMsgGetParam(hMsg, CMSG_TYPE_PARAM, 0, &dwMessageType, &cbMessageTypeLen))
226     {
227     gdcmErrorMacro( "CryptMsgGetParam CMSG_TYPE_PARAM failed with error 0x" << std::hex << GetLastError() );
228     goto err;
229     }
230 
231   if(dwMessageType != CMSG_ENVELOPED)
232     {
233     gdcmErrorMacro("Wrong message type ( != CMSG_ENVELOPED )");
234     goto err;
235     }
236 
237   if(! CryptGetKeyParam(hRsaPrivK, KP_ALGID, (unsigned char*)&kekAlg, &kekAlgLen, 0))
238     {
239     gdcmErrorMacro( "MsgGetParam KP_ALGID failed with error 0x" << std::hex << GetLastError() );
240     goto err;
241     }
242   if (kekAlg != CALG_RSA_KEYX)
243     {
244     gdcmErrorMacro( "Key encryption algorithm is not RSA." );
245     goto err;
246     }
247 
248   if(! CryptMsgGetParam(hMsg, CMSG_RECIPIENT_COUNT_PARAM, 0, &nrOfRecipeints, &nrOfRecipientsLen))
249     {
250     gdcmErrorMacro( "Decode CMSG_RECIPIENT_COUNT_PARAM failed with error 0x" << std::hex << GetLastError() );
251     goto err;
252     }
253 
254   DWORD cekLen;
255 {
256   BOOL foundRecipient = FALSE;
257   for (DWORD i=0; i < nrOfRecipeints; i++)
258     {
259     if (recipientInfo) delete[] recipientInfo;
260 
261     DWORD cbRecipientInfoLen;
262     if(! CryptMsgGetParam(hMsg, CMSG_CMS_RECIPIENT_INFO_PARAM, i, NULL, &cbRecipientInfoLen))
263       {
264       gdcmErrorMacro( "MsgGetParam CMSG_CMS_RECIPIENT_INFO_PARAM size failed with error 0x" << std::hex << GetLastError() );
265       goto err;
266       }
267     recipientInfo = (PCMSG_CMS_RECIPIENT_INFO) new unsigned char[cbRecipientInfoLen];
268     if(! CryptMsgGetParam(hMsg, CMSG_CMS_RECIPIENT_INFO_PARAM, i, recipientInfo, &cbRecipientInfoLen))
269       {
270       gdcmErrorMacro( "MsgGetParam CMSG_CMS_RECIPIENT_INFO_PARAM failed with error 0x" << std::hex << GetLastError() );
271       goto err;
272       }
273 
274     DWORD rsaPadding = 0;
275     if (strcmp(recipientInfo->pKeyTrans->KeyEncryptionAlgorithm.pszObjId, szOID_RSAES_OAEP) == 0)
276       {
277       rsaPadding = CRYPT_OAEP;
278       }
279 
280     //cek - content encryption key
281     cekLen = recipientInfo->pKeyTrans->EncryptedKey.cbData;
282     cek = recipientInfo->pKeyTrans->EncryptedKey.pbData;
283     ReverseBytes(cek, cekLen);
284 
285     if ( (foundRecipient =
286       CryptDecrypt(hRsaPrivK, 0, TRUE, rsaPadding, cek, &cekLen)) )
287       break;
288     } // end loop recipients
289 
290   if (!foundRecipient)
291     {
292       gdcmErrorMacro( "No recipient found with the specified private key." );
293       goto err;
294     }
295 }
296 
297   DWORD cekAlgLen;
298   if(! CryptMsgGetParam(hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, 0, NULL, &cekAlgLen))
299     {
300     gdcmErrorMacro( "MsgGetParam CMSG_ENVELOPE_ALGORITHM_PARAM failed with error 0x" << std::hex << GetLastError() );
301     goto err;
302     }
303   cekAlg = (PCRYPT_ALGORITHM_IDENTIFIER) new unsigned char[cekAlgLen];
304   if(! CryptMsgGetParam(hMsg, CMSG_ENVELOPE_ALGORITHM_PARAM, 0, cekAlg, &cekAlgLen))
305     {
306     gdcmErrorMacro( "MsgGetParam CMSG_ENVELOPE_ALGORITHM_PARAM failed with error 0x" << std::hex << GetLastError() );
307     goto err;
308     }
309 
310   HCRYPTKEY hCEK;
311   keyBlob.header.bType = PLAINTEXTKEYBLOB;
312   keyBlob.header.bVersion = CUR_BLOB_VERSION;
313   keyBlob.header.reserved = 0;
314   keyBlob.header.aiKeyAlg = GetAlgIdByObjId(cekAlg->pszObjId);
315   keyBlob.cbKeySize = cekLen;
316   assert(cekLen <= 32);
317   memcpy(keyBlob.rgbKeyData, cek, cekLen);
318 
319   if (!CryptImportKey(hProv, (unsigned char*)&keyBlob, sizeof(keyBlob), 0, 0, &hCEK))
320     {
321     gdcmErrorMacro( "CryptImportKey failed with error 0x" << std::hex << GetLastError() );
322     goto err;
323     }
324 
325   if(! CryptSetKeyParam(hCEK, KP_IV, (unsigned char *) cekAlg->Parameters.pbData+2, 0)) //+2 for ASN header ???
326     {
327     gdcmErrorMacro( "SetKeyParam KP_IV failed with error 0x" << std::hex << GetLastError() );
328     goto err;
329     }
330 
331 {
332   DWORD dwMode = CRYPT_MODE_CBC;
333   if(! CryptSetKeyParam(hCEK, KP_MODE, (unsigned char*) &dwMode, 0))
334     {
335     gdcmErrorMacro( "SetKeyParam KP_MODE failed with error 0x" << std::hex << GetLastError() );
336     goto err;
337     }
338 }
339 
340   DWORD bareContentLen;
341   if(! CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, NULL, &bareContentLen))
342     {
343     gdcmErrorMacro( "MsgGetParam CMSG_BARE_CONTENT_PARAM size failed with error 0x" << std::hex << GetLastError() );
344     goto err;
345     }
346   bareContent = new unsigned char[bareContentLen];
347   if(! CryptMsgGetParam(hMsg, CMSG_CONTENT_PARAM, 0, bareContent, &bareContentLen))
348     {
349     gdcmErrorMacro( "MsgGetParam CMSG_BARE_CONTENT_PARAM failed with error 0x" << std::hex << GetLastError() );
350     goto err;
351     }
352 
353   if (! CryptDecrypt(hCEK, 0, TRUE, 0, bareContent, &bareContentLen))
354     {
355     gdcmErrorMacro( "CryptDecrypt failed with error 0x" << std::hex << GetLastError() );
356     goto err;
357     }
358 
359   if (bareContentLen > outlen)
360     {
361     gdcmErrorMacro( "Supplied output buffer too small: " << bareContentLen << " bytes needed." );
362     goto err;
363     }
364 
365     memcpy(output, bareContent, bareContentLen);
366     outlen = bareContentLen;
367 
368     ret = true;
369 err:
370     if (hMsg) CryptMsgClose(hMsg);
371     if (recipientInfo) delete[] recipientInfo;
372     if (bareContent) delete[] bareContent;
373     if (cekAlg) delete[] cekAlg;
374 
375   return ret;
376 }
377 
GetAlgIdByObjId(const char * pszObjId)378 ALG_ID CAPICryptographicMessageSyntax::GetAlgIdByObjId(const char * pszObjId)
379 {
380   // HACK: fix compilation on mingw64:
381   // See: http://sourceforge.net/tracker/?func=detail&aid=3561209&group_id=202880&atid=983354
382 #ifndef szOID_NIST_AES128_CBC
383 #define szOID_NIST_AES128_CBC        "2.16.840.1.101.3.4.1.2"
384 #define szOID_NIST_AES192_CBC        "2.16.840.1.101.3.4.1.22"
385 #define szOID_NIST_AES256_CBC        "2.16.840.1.101.3.4.1.42"
386 #endif
387 
388   if (strcmp(pszObjId, szOID_NIST_AES128_CBC) == 0)
389     {
390     return CALG_AES_128;
391     }
392   else if (strcmp(pszObjId, szOID_NIST_AES192_CBC) == 0)
393     {
394     return CALG_AES_192;
395     }
396   else if (strcmp(pszObjId, szOID_NIST_AES256_CBC) == 0)
397     {
398     return CALG_AES_256;
399     }
400   else if (strcmp(pszObjId, szOID_RSA_DES_EDE3_CBC) == 0)
401     {
402     return CALG_3DES;
403     }
404   return 0;
405 }
406 
GetCipherObjId() const407 const char *CAPICryptographicMessageSyntax::GetCipherObjId() const
408 {
409   switch( cipherType )
410     {
411   case AES128_CIPHER:
412     return szOID_NIST_AES128_CBC;
413   case AES192_CIPHER:
414     return szOID_NIST_AES192_CBC;
415   case AES256_CIPHER:
416     return szOID_NIST_AES256_CBC;
417   case DES3_CIPHER:
418     return szOID_RSA_DES_EDE3_CBC;
419     }
420   return 0;
421 }
422 
Initialize()423 bool CAPICryptographicMessageSyntax::Initialize()
424 {
425   DWORD dwResult;
426   if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) //CRYPT_VERIFYCONTEXT aes decr in cryptmsgcontrol not working
427     {
428     dwResult = GetLastError();
429     if (dwResult == NTE_BAD_KEYSET)
430       {
431       if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV, PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT))
432         {
433         dwResult = GetLastError();
434         gdcmErrorMacro(  "CryptAcquireContext() failed:" << std::hex << dwResult);
435         return false;
436         }
437       }
438     else if (dwResult == NTE_KEYSET_NOT_DEF)
439       {
440       //Probably WinXP
441       gdcmWarningMacro( "Certificate based encryption is supported on Windows XP only using 3DES." );
442       if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A" (Prototype)" /*"Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"*/, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) //CRYPT_VERIFYCONTEXT aes decr in cryptmsgcontrol not working
443         {
444         dwResult = GetLastError();
445         if (dwResult == NTE_BAD_KEYSET)
446           {
447           if (!CryptAcquireContextA(&hProv, NULL, MS_ENH_RSA_AES_PROV_A" (Prototype)" /*"Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"*/, PROV_RSA_AES, CRYPT_NEWKEYSET | CRYPT_VERIFYCONTEXT))
448             {
449             dwResult = GetLastError();
450             gdcmErrorMacro( "CryptAcquireContext() failed: " << std::hex << dwResult );
451             return false;
452             }
453           }
454         else
455           {
456           dwResult = GetLastError();
457           return false;
458           }
459         }
460       }
461     else
462       {
463       dwResult = GetLastError();
464       return false;
465       }
466     }
467   return true;
468 }
469 
ReverseBytes(unsigned char * data,DWORD len)470 void CAPICryptographicMessageSyntax::ReverseBytes(unsigned char* data, DWORD len)
471 {
472   unsigned char temp;
473   for (DWORD i = 0; i < len/2; i++)
474     {
475     temp = data[len-i-1];
476     data[len-i-1] = data[i];
477     data[i] = temp;
478     }
479 }
480 
LoadFile(const char * filename,unsigned char * & buffer,DWORD & bufLen)481 bool CAPICryptographicMessageSyntax::LoadFile(const char * filename, unsigned char* & buffer, DWORD & bufLen)
482 {
483   assert( !buffer );
484   FILE * f = fopen(filename, "rb");
485   if (f == NULL)
486     {
487     gdcmErrorMacro("Couldn't open the file: " << filename);
488     fclose(f);
489     return false;
490     }
491   fseek(f, 0L, SEEK_END);
492   long sz = ftell(f);
493   rewind(f);
494 
495   buffer = new unsigned char[sz];
496   if( !buffer )
497   {
498     fclose(f);
499     return false;
500   }
501   bufLen = sz;
502 
503   while (sz)
504     {
505     size_t l = fread(buffer + bufLen - sz, sizeof(unsigned char), sz, f);
506     sz -= (long)l;
507     }
508 
509   fclose(f);
510   return true;
511 }
512 
513 } // end namespace gdcm
514