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