Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

683 lines
22 KiB

  1. /*++
  2. Copyright (c) 1997 Microsoft Corporation
  3. Module Name:
  4. certupgr.cxx
  5. Abstract:
  6. Functions used in upgrading server certs from K2 [server cert in metabase] to
  7. Avalanche [server cert in CAPI store].
  8. Author:
  9. Alex Mallet (amallet) 07-Dec-1997
  10. Boyd Multerer (boydm) 20-Jan-1998 Converted to be useful in setup
  11. --*/
  12. #include "stdafx.h"
  13. #include <objbase.h>
  14. #ifndef _CHICAGO_
  15. #include "oidenc.h"
  16. // keyring include
  17. #include "intrlkey.h"
  18. //
  19. //Local includes
  20. //
  21. #include "certupgr.h"
  22. //#include "certtools.h"
  23. // The below define is in some interal schannel header file. John Banes
  24. // told me to just redefine it below as such........ - Boyd
  25. LPCSTR SGC_KEY_SALT = "SGCKEYSALT";
  26. // prototypes
  27. BOOL DecodeAndImportPrivateKey( PBYTE pbEncodedPrivateKey IN,
  28. DWORD cbEncodedPrivateKey IN,
  29. PCHAR pszPassword IN,
  30. PWCHAR pszKeyContainerIN,
  31. CRYPT_KEY_PROV_INFO *pCryptKeyProvInfo );
  32. BOOL UpdateCSPInfo( PCCERT_CONTEXT pcCertContext );
  33. BOOL FImportAndStoreRequest( PCCERT_CONTEXT pCert, PVOID pbPKCS10req, DWORD cbPKCS10req );
  34. //-------------------------------------------------------------------------
  35. PCCERT_CONTEXT CopyKRCertToCAPIStore_A( PVOID pbPrivateKey, DWORD cbPrivateKey,
  36. PVOID pbPublicKey, DWORD cbPublicKey,
  37. PVOID pbPKCS10req, DWORD cbPKCS10req,
  38. PCHAR pszPassword,
  39. PCHAR pszCAPIStore)
  40. {
  41. PCCERT_CONTEXT pCert = NULL;
  42. // prep the wide strings
  43. PWCHAR pszwCAPIStore = NULL;
  44. DWORD lenStore = (strlen(pszCAPIStore)+1) * sizeof(WCHAR);
  45. pszwCAPIStore = (PWCHAR)GlobalAlloc( GPTR, lenStore );
  46. if ( !pszwCAPIStore )
  47. goto cleanup;
  48. // convert the strings
  49. MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, pszCAPIStore, -1, pszwCAPIStore, lenStore );
  50. // do the real call
  51. pCert = CopyKRCertToCAPIStore_W(
  52. pbPrivateKey, cbPrivateKey,
  53. pbPublicKey, cbPublicKey,
  54. pbPKCS10req, cbPKCS10req,
  55. pszPassword,
  56. pszwCAPIStore );
  57. cleanup:
  58. // preserve the last error state
  59. DWORD err = GetLastError();
  60. // clean up the strings
  61. if ( pszwCAPIStore )
  62. GlobalFree( pszwCAPIStore );
  63. // reset the last error state
  64. SetLastError( err );
  65. // return the cert
  66. return pCert;
  67. }
  68. //--------------------------------------------------------------------------------------------
  69. // Copies an old Key-Ring style cert to the CAPI store. This cert comes in as two binaries and a password.
  70. PCCERT_CONTEXT CopyKRCertToCAPIStore_W( PVOID pbPrivateKey, DWORD cbPrivateKey,
  71. PVOID pbPublicKey, DWORD cbPublicKey,
  72. PVOID pbPKCS10req, DWORD cbPKCS10req,
  73. PCHAR pszPassword,
  74. PWCHAR pszCAPIStore)
  75. /*++
  76. Routine Description:
  77. Upgrades K2 server certs to Avalanche server certs - reads server cert out of K2
  78. metabase, creates cert context and stores it in CAPI2 "MY" store and writes
  79. relevant information back to metabase.
  80. Arguments:
  81. pMDObject - pointer to Metabase object
  82. pszOldMBPath - path to where server cert is stored in old MB, relative to SSL_W3_KEYS_MD_PATH
  83. pszNewMBPath - fully qualified path to where server cert info should be stored in new MB
  84. Returns:
  85. BOOL indicating success/failure
  86. --*/
  87. {
  88. BOOL fSuccess = FALSE;
  89. HCERTSTORE hStore = NULL;
  90. PCCERT_CONTEXT pcCertContext = NULL;
  91. LPOLESTR polestr = NULL;
  92. // start by opening the CAPI store that we will be saving the certificate into
  93. hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM,
  94. 0,
  95. NULL,
  96. CERT_SYSTEM_STORE_LOCAL_MACHINE,
  97. pszCAPIStore );
  98. if ( !hStore )
  99. {
  100. // iisDebugOut((_T("Error 0x%x calling CertOpenStore \n"), GetLastError());
  101. goto EndUpgradeServerCert;
  102. }
  103. // at this point we check to see if a certificate was passed in. If none was, then we need
  104. // to create a dummy-temporary certificate that markes the private key as incomplete. That
  105. // way, then the real certificate comes back from verisign the regular tools can be used
  106. // to complete the key.
  107. //CertCreateSelfSignCertificate()
  108. //
  109. //Create cert context to be stored in CAPI store
  110. //
  111. pbPublicKey = (PVOID)((PBYTE)pbPublicKey + CERT_DER_PREFIX);
  112. cbPublicKey -= CERT_DER_PREFIX;
  113. pcCertContext = CertCreateCertificateContext( X509_ASN_ENCODING, (PUCHAR)pbPublicKey, cbPublicKey);
  114. if ( pcCertContext )
  115. {
  116. // the private key gets stored in a seperate location from the certificate and gets referred to
  117. // by the certificate. We should try to pick a unique name so that some other cert won't step
  118. // on it by accident. There is no formal format for this name whatsoever. Some groups use a
  119. // human-readable string, some use a hash of the cert, and some use a GUID string. All are valid
  120. // although for generated certs the hash or the GUID are probably better.
  121. // get the 128 big md5 hash of the cert for the name
  122. DWORD dwHashSize;
  123. BOOL fHash;
  124. BYTE MD5Hash[16]; // give it some extra size
  125. dwHashSize = sizeof(MD5Hash);
  126. fHash = CertGetCertificateContextProperty( pcCertContext,
  127. CERT_MD5_HASH_PROP_ID,
  128. (VOID *) MD5Hash,
  129. &dwHashSize );
  130. // Since the MD5 hash is the same size as a guid, we can use the guid utilities to make a
  131. // nice string out of it.
  132. HRESULT hresult;
  133. hresult = StringFromCLSID( (REFCLSID)MD5Hash, &polestr );
  134. //
  135. // Now decode private key blob and import it into CAPI1 private key
  136. //
  137. CRYPT_KEY_PROV_INFO CryptKeyProvInfo;
  138. if ( DecodeAndImportPrivateKey( (PUCHAR)pbPrivateKey, cbPrivateKey, pszPassword,
  139. polestr, &CryptKeyProvInfo ) )
  140. {
  141. //
  142. // Add the private key to the cert context
  143. //
  144. BOOL f;
  145. f = CertSetCertificateContextProperty( pcCertContext, CERT_KEY_PROV_INFO_PROP_ID,
  146. 0, &CryptKeyProvInfo );
  147. f = UpdateCSPInfo( pcCertContext );
  148. if ( f )
  149. {
  150. //
  151. // Store it in the provided store
  152. //
  153. if ( CertAddCertificateContextToStore( hStore, pcCertContext,
  154. CERT_STORE_ADD_REPLACE_EXISTING, NULL ) )
  155. {
  156. fSuccess = TRUE;
  157. // Write out the original request as a property on the cert
  158. FImportAndStoreRequest( pcCertContext, pbPKCS10req, cbPKCS10req );
  159. }
  160. else
  161. {
  162. // iisDebugOut((_T("Error 0x%x calling CertAddCertificateContextToStore"), GetLastError());
  163. }
  164. }
  165. else
  166. {
  167. // iisDebugOut((_T("Error 0x%x calling CertSetCertificateContextProperty"), GetLastError());
  168. }
  169. }
  170. }
  171. else
  172. {
  173. // iisDebugOut((_T("Error 0x%x calling CertCreateCertificateContext"), GetLastError());
  174. }
  175. //
  176. //Cleanup that's done only on failure
  177. //
  178. if ( !fSuccess )
  179. {
  180. if ( pcCertContext )
  181. {
  182. CertFreeCertificateContext( pcCertContext );
  183. }
  184. pcCertContext = NULL;
  185. }
  186. EndUpgradeServerCert:
  187. // cleanup
  188. if ( hStore )
  189. CertCloseStore ( hStore, 0 );
  190. if ( polestr )
  191. CoTaskMemFree( polestr );
  192. // return the answer
  193. return pcCertContext;
  194. }
  195. //--------------------------------------------------------------------------------------------
  196. BOOL UpdateCSPInfo( PCCERT_CONTEXT pcCertContext )
  197. {
  198. BYTE cbData[1000];
  199. CRYPT_KEY_PROV_INFO* pProvInfo = (CRYPT_KEY_PROV_INFO *) cbData;
  200. DWORD dwFoo = 1000;
  201. BOOL fSuccess = TRUE;
  202. if ( ! CertGetCertificateContextProperty( pcCertContext,
  203. CERT_KEY_PROV_INFO_PROP_ID,
  204. pProvInfo,
  205. &dwFoo ) )
  206. {
  207. fSuccess = FALSE;
  208. // iisDebugOut((_T("Fudge. failed to get property : 0x%x"), GetLastError());
  209. }
  210. else
  211. {
  212. pProvInfo->dwProvType = PROV_RSA_SCHANNEL;
  213. pProvInfo->pwszProvName = NULL;
  214. if ( !CertSetCertificateContextProperty( pcCertContext,
  215. CERT_KEY_PROV_INFO_PROP_ID,
  216. 0,
  217. pProvInfo ) )
  218. {
  219. fSuccess = FALSE;
  220. // iisDebugOut((_T("Fudge. failed to set property : 0x%x"), GetLastError());
  221. }
  222. }
  223. // return success
  224. return fSuccess;
  225. }
  226. //--------------------------------------------------------------------------------------------
  227. BOOL DecodeAndImportPrivateKey( PBYTE pbEncodedPrivateKey IN,
  228. DWORD cbEncodedPrivateKey IN,
  229. PCHAR pszPassword IN,
  230. PWCHAR pszKeyContainer IN,
  231. CRYPT_KEY_PROV_INFO *pCryptKeyProvInfo )
  232. /*++
  233. Routine Description:
  234. Converts the private key stored in the metabase, in Schannel-internal format,
  235. into a key that can be imported via CryptImportKey() to create a CAP1 key blob.
  236. Arguments:
  237. pbEncodedPrivateKey - pointer to [encoded] private key
  238. cbEncodedPrivateKey - size of encoded private key blob
  239. pszPassword - password used to encode private key
  240. pszKeyContainer - container name for private key
  241. pCryptKeyProvInfo - pointer to CRYPT_KEY_PROV_INFO structure filled in on success
  242. Returns:
  243. BOOL indicating success/failure
  244. --*/
  245. {
  246. BOOL fSuccess = FALSE;
  247. DWORD cbPassword = strlen(pszPassword);
  248. PPRIVATE_KEY_FILE_ENCODE pPrivateFile = NULL;
  249. DWORD cbPrivateFile = 0;
  250. MD5_CTX md5Ctx;
  251. struct RC4_KEYSTRUCT rc4Key;
  252. DWORD i;
  253. HCRYPTPROV hProv = NULL;
  254. HCRYPTKEY hPrivateKey = NULL;
  255. DWORD cbDecodedPrivateKey = 0;
  256. PBYTE pbDecodedPrivateKey = NULL;
  257. DWORD err;
  258. //
  259. //HACK HACK HACK - need to make sure Schannel is initialized, so it registers
  260. //its custom decoders, which we make use of in the following code. So, make a
  261. //bogus call to an Schannel function
  262. // Note: on NT5, the AcquireCredentialsHandle operates in the lsass process and
  263. // thus will not properly initialize the stuff we need in our process. Thus we
  264. // call SslGenerateRandomBits instead.
  265. //
  266. DWORD dw;
  267. SslGenerateRandomBits( (PUCHAR)&dw, sizeof(dw) );
  268. // We have to do a little fixup here. Old versions of
  269. // schannel wrote the wrong header data into the ASN
  270. // for private key files, so we must fix the size data.
  271. pbEncodedPrivateKey[2] = (BYTE) (((cbEncodedPrivateKey - 4) & 0xFF00) >> 8); //Get MSB
  272. pbEncodedPrivateKey[3] = (BYTE) ((cbEncodedPrivateKey - 4) & 0xFF); //Get LSB
  273. //
  274. // ASN.1 decode the private key.
  275. //
  276. //
  277. // Figure out the size of the buffer needed
  278. //
  279. if( !CryptDecodeObject(X509_ASN_ENCODING,
  280. szPrivateKeyFileEncode,
  281. pbEncodedPrivateKey,
  282. cbEncodedPrivateKey,
  283. 0,
  284. NULL,
  285. &cbPrivateFile) )
  286. {
  287. err = GetLastError();
  288. // iisDebugOut((_T("Error 0x%x decoding the private key"), err);
  289. goto EndDecodeKey;
  290. }
  291. pPrivateFile = (PPRIVATE_KEY_FILE_ENCODE) LocalAlloc( LPTR, cbPrivateFile );
  292. if(pPrivateFile == NULL)
  293. {
  294. SetLastError( ERROR_OUTOFMEMORY );
  295. goto EndDecodeKey;
  296. }
  297. //
  298. // Actually fill in the buffer
  299. //
  300. if( !CryptDecodeObject( X509_ASN_ENCODING,
  301. szPrivateKeyFileEncode,
  302. pbEncodedPrivateKey,
  303. cbEncodedPrivateKey,
  304. 0,
  305. pPrivateFile,
  306. &cbPrivateFile ) )
  307. {
  308. err = GetLastError();
  309. // iisDebugOut((_T("Error 0x%x decoding the private key"), err);
  310. goto EndDecodeKey;
  311. }
  312. //
  313. // Decrypt the decoded private key using the password.
  314. //
  315. MD5Init(&md5Ctx);
  316. MD5Update(&md5Ctx, (PBYTE) pszPassword, cbPassword);
  317. MD5Final(&md5Ctx);
  318. rc4_key( &rc4Key, 16, md5Ctx.digest );
  319. // memset( &md5Ctx, 0, sizeof(md5Ctx) );
  320. rc4( &rc4Key,
  321. pPrivateFile->EncryptedBlob.cbData,
  322. pPrivateFile->EncryptedBlob.pbData );
  323. //
  324. // Build a PRIVATEKEYBLOB from the decrypted private key.
  325. //
  326. //
  327. // Figure out size of buffer needed
  328. //
  329. if( !CryptDecodeObject( X509_ASN_ENCODING,
  330. szPrivateKeyInfoEncode,
  331. pPrivateFile->EncryptedBlob.pbData,
  332. pPrivateFile->EncryptedBlob.cbData,
  333. 0,
  334. NULL,
  335. &cbDecodedPrivateKey ) )
  336. {
  337. // NOTE: This stuff is complicated!!! The following code came
  338. // from John Banes. Heck this whole routine pretty much came
  339. // from John Banes. -- Boyd
  340. // Maybe this was a SGC style key.
  341. // Re-encrypt it, and build the SGC decrypting
  342. // key, and re-decrypt it.
  343. BYTE md5Digest[MD5DIGESTLEN];
  344. rc4_key(&rc4Key, 16, md5Ctx.digest);
  345. rc4(&rc4Key,
  346. pPrivateFile->EncryptedBlob.cbData,
  347. pPrivateFile->EncryptedBlob.pbData);
  348. CopyMemory(md5Digest, md5Ctx.digest, MD5DIGESTLEN);
  349. MD5Init(&md5Ctx);
  350. MD5Update(&md5Ctx, md5Digest, MD5DIGESTLEN);
  351. MD5Update(&md5Ctx, (PUCHAR)SGC_KEY_SALT, strlen(SGC_KEY_SALT));
  352. MD5Final(&md5Ctx);
  353. rc4_key(&rc4Key, 16, md5Ctx.digest);
  354. rc4(&rc4Key,
  355. pPrivateFile->EncryptedBlob.cbData,
  356. pPrivateFile->EncryptedBlob.pbData);
  357. // Try again...
  358. if(!CryptDecodeObject(X509_ASN_ENCODING,
  359. szPrivateKeyInfoEncode,
  360. pPrivateFile->EncryptedBlob.pbData,
  361. pPrivateFile->EncryptedBlob.cbData,
  362. 0,
  363. NULL,
  364. &cbDecodedPrivateKey))
  365. {
  366. ZeroMemory(&md5Ctx, sizeof(md5Ctx));
  367. err = GetLastError();
  368. goto EndDecodeKey;
  369. }
  370. }
  371. pbDecodedPrivateKey = (PBYTE) LocalAlloc( LPTR, cbDecodedPrivateKey );
  372. if( pbDecodedPrivateKey == NULL )
  373. {
  374. SetLastError( ERROR_OUTOFMEMORY );
  375. goto EndDecodeKey;
  376. }
  377. //
  378. // Actually fill in the buffer
  379. //
  380. if( !CryptDecodeObject( X509_ASN_ENCODING,
  381. szPrivateKeyInfoEncode,
  382. pPrivateFile->EncryptedBlob.pbData,
  383. pPrivateFile->EncryptedBlob.cbData,
  384. 0,
  385. pbDecodedPrivateKey,
  386. &cbDecodedPrivateKey ) )
  387. {
  388. err = GetLastError();
  389. // iisDebugOut((_T("Error 0x%x decoding the private key"), err);
  390. goto EndDecodeKey;
  391. }
  392. // On NT 4 the ff holds true : <- from Alex Mallet
  393. // Although key is going to be used for key exchange, mark it as being
  394. // used for signing, because only 512-bit key exchange keys are supported
  395. // in the non-domestic rsabase.dll, whereas signing keys can be up to
  396. // 2048 bits.
  397. //
  398. // On NT 5, PROV_RSA_FULL should be changed to PROV_RSA_SCHANNEL, and
  399. // aiKeyAlg to CALG_RSA_KEYX, because PROV_RSA_SCHANNEL, which is only
  400. // installed on NT 5, supports 1024-bit private keys for key exchange
  401. //
  402. // On NT4, Schannel doesn't care whether a key is marked for signing or exchange,
  403. // but on NT5 it does, so aiKeyAlg must be set appropriately
  404. //
  405. ((BLOBHEADER *) pbDecodedPrivateKey)->aiKeyAlg = CALG_RSA_KEYX;
  406. //
  407. // Clean out the key container, pszKeyContainer
  408. //
  409. CryptAcquireContext(&hProv,
  410. pszKeyContainer,
  411. NULL,
  412. PROV_RSA_SCHANNEL,
  413. CRYPT_DELETEKEYSET | CRYPT_MACHINE_KEYSET);
  414. //
  415. // Create a CryptoAPI key container in which to store the key.
  416. //
  417. if( !CryptAcquireContext( &hProv,
  418. pszKeyContainer,
  419. NULL,
  420. PROV_RSA_SCHANNEL,
  421. CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET))
  422. {
  423. err = GetLastError();
  424. // iisDebugOut((_T("Error 0x%x calling CryptAcquireContext"), err);
  425. goto EndDecodeKey;
  426. }
  427. //
  428. // Import the private key blob into the key container.
  429. //
  430. if( !CryptImportKey( hProv,
  431. pbDecodedPrivateKey,
  432. cbDecodedPrivateKey,
  433. 0,
  434. CRYPT_EXPORTABLE, //so we can export it later
  435. &hPrivateKey ) )
  436. {
  437. err = GetLastError();
  438. // iisDebugOut((_T("Error 0x%x importing PRIVATEKEYBLOB"), err);
  439. goto EndDecodeKey;
  440. }
  441. //
  442. // Fill in the CRYPT_KEY_PROV_INFO structure, with the same parameters we
  443. // used in the call to CryptAcquireContext() above
  444. //
  445. //
  446. // container name in the structure is a unicode string, so we need to convert
  447. //
  448. if ( pszKeyContainer != NULL )
  449. {
  450. // point the key container name to the passed in string
  451. // WARNING: this does not actually copy the string, just the pointer
  452. // to it. So the strings needs to remain valid until the ProvInfo is commited.
  453. pCryptKeyProvInfo->pwszContainerName = pszKeyContainer;
  454. }
  455. else
  456. {
  457. pCryptKeyProvInfo->pwszContainerName = NULL;
  458. }
  459. pCryptKeyProvInfo->pwszProvName = NULL;
  460. pCryptKeyProvInfo->dwProvType = PROV_RSA_FULL;
  461. pCryptKeyProvInfo->dwFlags = 0x20; // allow the cert to be exchanged
  462. pCryptKeyProvInfo->cProvParam = 0;
  463. pCryptKeyProvInfo->rgProvParam = NULL;
  464. pCryptKeyProvInfo->dwKeySpec = AT_KEYEXCHANGE; // allow the cert to be exchanged
  465. fSuccess = TRUE;
  466. EndDecodeKey:
  467. //
  468. // Clean-up that happens regardless of success/failure
  469. //
  470. if ( pPrivateFile )
  471. {
  472. LocalFree( pPrivateFile );
  473. }
  474. if ( pbDecodedPrivateKey )
  475. {
  476. LocalFree( pbDecodedPrivateKey );
  477. }
  478. if ( hPrivateKey )
  479. {
  480. CryptDestroyKey( hPrivateKey );
  481. }
  482. if ( hProv )
  483. {
  484. CryptReleaseContext( hProv, 0 );
  485. }
  486. return fSuccess;
  487. } //DecodeAndImportPrivateKey
  488. //--------------------------------------------------------------------------------------------
  489. /*++
  490. Routine Description:
  491. Takes an incoming PKCS10 request and saves it as a property attached to the key. It also
  492. checks if the request is in the old internal Keyring format or not......
  493. Arguments:
  494. pCert - CAPI certificate context pointer for the cert to save the request on
  495. pbPKCS10req - pointer to the request
  496. cbPKCS10req - size of the request
  497. Returns:
  498. BOOL indicating success/failure
  499. --*/
  500. BOOL FImportAndStoreRequest( PCCERT_CONTEXT pCert, PVOID pbPKCS10req, DWORD cbPKCS10req )
  501. {
  502. BOOL f;
  503. DWORD err;
  504. // if any NULLS are passed in, fail gracefully
  505. if ( !pCert || !pbPKCS10req || !cbPKCS10req )
  506. return FALSE;
  507. // first, check if the incoming request is actually pointing to an old KeyRing internal
  508. // request format. That just means that the real request is actuall slightly into
  509. // the block. The way you tell is by testing the first DWORD to see it
  510. // is REQUEST_HEADER_IDENTIFIER
  511. // start by seeing if this is a new style key request
  512. LPREQUEST_HEADER pHeader = (LPREQUEST_HEADER)pbPKCS10req;
  513. if ( pHeader->Identifier == REQUEST_HEADER_IDENTIFIER )
  514. {
  515. // update the request pointer and data count
  516. pbPKCS10req = (PBYTE)pbPKCS10req + pHeader->cbSizeOfHeader;
  517. cbPKCS10req = pHeader->cbRequestSize;
  518. }
  519. // now save the request onto the key
  520. CRYPT_DATA_BLOB dataBlob;
  521. ZeroMemory( &dataBlob, sizeof(dataBlob) );
  522. dataBlob.pbData = (PBYTE)pbPKCS10req; // pointer to blob data
  523. dataBlob.cbData = cbPKCS10req; // blob length info
  524. f = CertSetCertificateContextProperty(
  525. pCert,
  526. CERTWIZ_REQUEST_PROP_ID,
  527. 0,
  528. &dataBlob
  529. );
  530. err = GetLastError();
  531. /*
  532. HRESULT hRes = CertTool_SetBinaryBlobProp(
  533. pCert, // cert context to set the prop on
  534. pbPKCS10req, // pointer to blob data
  535. cbPKCS10req, // blob length info
  536. CERTWIZ_REQUEST_PROP_ID,// property ID for context
  537. TRUE // the request is already encoded
  538. );
  539. */
  540. return f;
  541. }
  542. #endif //_CHICAGO_