|
|
/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
certupgr.cxx
Abstract:
Functions used in upgrading server certs from K2 [server cert in metabase] to Avalanche [server cert in CAPI store].
Author:
Alex Mallet (amallet) 07-Dec-1997 Boyd Multerer (boydm) 20-Jan-1998 Converted to be useful in setup
--*/
#include "stdafx.h"
#include <objbase.h>
#ifndef _CHICAGO_
#include "oidenc.h"
// keyring include
//#include "intrlkey.h"
// This stuff below is moved from above include
#define REQUEST_HEADER_K2B2VERSION 0x0101
#define REQUEST_HEADER_IDENTIFIER 'RHDR'
#define REQUEST_HEADER_CURVERSION 0x0101
typedef struct _KeyRequestHeader { DWORD Identifier; // must be 'RHDR'
DWORD Version; // version of header record
DWORD cbSizeOfHeader; // byte count of header. Afterwards is the request.
DWORD cbRequestSize; // size of the request that follows
BOOL fReqSentToOnlineCA; LONG longRequestID; BOOL fWaitingForApproval; char chCA[MAX_PATH]; } KeyRequestHeader, *LPREQUEST_HEADER; ///--- end of #include "intrlkey.h"
//
//Local includes
//
#include "certupgr.h"
//#include "certtools.h"
// The below define is in some interal schannel header file. John Banes
// told me to just redefine it below as such........ - Boyd
LPCSTR SGC_KEY_SALT = "SGCKEYSALT";
// prototypes
BOOL DecodeAndImportPrivateKey( PBYTE pbEncodedPrivateKey IN, DWORD cbEncodedPrivateKey IN, PCHAR pszPassword IN, PWCHAR pszKeyContainerIN, CRYPT_KEY_PROV_INFO *pCryptKeyProvInfo ); BOOL UpdateCSPInfo( PCCERT_CONTEXT pcCertContext );
BOOL FImportAndStoreRequest( PCCERT_CONTEXT pCert, PVOID pbPKCS10req, DWORD cbPKCS10req );
//-------------------------------------------------------------------------
PCCERT_CONTEXT CopyKRCertToCAPIStore_A( PVOID pbPrivateKey, DWORD cbPrivateKey, PVOID pbPublicKey, DWORD cbPublicKey, PVOID pbPKCS10req, DWORD cbPKCS10req, PCHAR pszPassword, PCHAR pszCAPIStore, BOOL bOverWrite) { PCCERT_CONTEXT pCert = NULL;
// prep the wide strings
PWCHAR pszwCAPIStore = NULL; DWORD lenStore = (strlen(pszCAPIStore)+1) * sizeof(WCHAR); pszwCAPIStore = (PWCHAR)GlobalAlloc( GPTR, lenStore ); if ( !pszwCAPIStore ) goto cleanup;
// convert the strings
MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, pszCAPIStore, -1, pszwCAPIStore, lenStore );
// do the real call
pCert = CopyKRCertToCAPIStore_W( pbPrivateKey, cbPrivateKey, pbPublicKey, cbPublicKey, pbPKCS10req, cbPKCS10req, pszPassword, pszwCAPIStore, bOverWrite);
cleanup: // preserve the last error state
DWORD err = GetLastError();
// clean up the strings
if ( pszwCAPIStore ) GlobalFree( pszwCAPIStore );
// reset the last error state
SetLastError( err );
// return the cert
return pCert; }
//--------------------------------------------------------------------------------------------
// Copies an old Key-Ring style cert to the CAPI store. This cert comes in as two binaries and a password.
PCCERT_CONTEXT CopyKRCertToCAPIStore_W( PVOID pbPrivateKey, DWORD cbPrivateKey, PVOID pbPublicKey, DWORD cbPublicKey, PVOID pbPKCS10req, DWORD cbPKCS10req, PCHAR pszPassword, PWCHAR pszCAPIStore, BOOL bOverWrite) /*++
Routine Description:
Upgrades K2 server certs to Avalanche server certs - reads server cert out of K2 metabase, creates cert context and stores it in CAPI2 "MY" store and writes relevant information back to metabase.
Arguments:
pMDObject - pointer to Metabase object pszOldMBPath - path to where server cert is stored in old MB, relative to SSL_W3_KEYS_MD_PATH pszNewMBPath - fully qualified path to where server cert info should be stored in new MB
Returns:
BOOL indicating success/failure
--*/ { BOOL fSuccess = FALSE;
HCERTSTORE hStore = NULL; PCCERT_CONTEXT pcCertContext = NULL; LPOLESTR polestr = NULL;
// start by opening the CAPI store that we will be saving the certificate into
hStore = CertOpenStore( CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE, pszCAPIStore ); if ( !hStore ) { // iisDebugOut((_T("Error 0x%x calling CertOpenStore \n"), GetLastError());
goto EndUpgradeServerCert; }
// at this point we check to see if a certificate was passed in. If none was, then we need
// to create a dummy-temporary certificate that markes the private key as incomplete. That
// way, then the real certificate comes back from verisign the regular tools can be used
// to complete the key.
//CertCreateSelfSignCertificate()
//
//Create cert context to be stored in CAPI store
//
pbPublicKey = (PVOID)((PBYTE)pbPublicKey + CERT_DER_PREFIX); cbPublicKey -= CERT_DER_PREFIX; pcCertContext = CertCreateCertificateContext( X509_ASN_ENCODING, (PUCHAR)pbPublicKey, cbPublicKey); if ( pcCertContext ) {
// the private key gets stored in a seperate location from the certificate and gets referred to
// by the certificate. We should try to pick a unique name so that some other cert won't step
// on it by accident. There is no formal format for this name whatsoever. Some groups use a
// human-readable string, some use a hash of the cert, and some use a GUID string. All are valid
// although for generated certs the hash or the GUID are probably better.
// get the 128 big md5 hash of the cert for the name
DWORD dwHashSize; BOOL fHash;
BYTE MD5Hash[16]; // give it some extra size
dwHashSize = sizeof(MD5Hash); fHash = CertGetCertificateContextProperty( pcCertContext, CERT_MD5_HASH_PROP_ID, (VOID *) MD5Hash, &dwHashSize );
// Since the MD5 hash is the same size as a guid, we can use the guid utilities to make a
// nice string out of it.
HRESULT hresult; hresult = StringFromCLSID( (REFCLSID)MD5Hash, &polestr );
//
// Now decode private key blob and import it into CAPI1 private key
//
CRYPT_KEY_PROV_INFO CryptKeyProvInfo;
if ( DecodeAndImportPrivateKey( (PUCHAR)pbPrivateKey, cbPrivateKey, pszPassword, polestr, &CryptKeyProvInfo ) ) { //
// Add the private key to the cert context
//
BOOL f; f = CertSetCertificateContextProperty( pcCertContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &CryptKeyProvInfo ); f = UpdateCSPInfo( pcCertContext ); if ( f ) { //
// Store it in the provided store
//
if (bOverWrite) { if ( CertAddCertificateContextToStore( hStore, pcCertContext, CERT_STORE_ADD_REPLACE_EXISTING, NULL ) ) { fSuccess = TRUE;
// Write out the original request as a property on the cert
FImportAndStoreRequest( pcCertContext, pbPKCS10req, cbPKCS10req ); } else { // iisDebugOut((_T("Error 0x%x calling CertAddCertificateContextToStore"), GetLastError());
} } else { if ( CertAddCertificateContextToStore( hStore, pcCertContext, CERT_STORE_ADD_NEW, NULL ) ) { fSuccess = TRUE;
// Write out the original request as a property on the cert
FImportAndStoreRequest( pcCertContext, pbPKCS10req, cbPKCS10req ); } else { // iisDebugOut((_T("Error 0x%x calling CertAddCertificateContextToStore"), GetLastError());
} } } else { // iisDebugOut((_T("Error 0x%x calling CertSetCertificateContextProperty"), GetLastError());
} } } else { // iisDebugOut((_T("Error 0x%x calling CertCreateCertificateContext"), GetLastError());
}
//
//Cleanup that's done only on failure
//
if ( !fSuccess ) { if ( pcCertContext ) { CertFreeCertificateContext( pcCertContext ); } pcCertContext = NULL; }
EndUpgradeServerCert: // cleanup
if ( hStore ) CertCloseStore ( hStore, 0 );
if ( polestr ) CoTaskMemFree( polestr );
// return the answer
return pcCertContext; }
//--------------------------------------------------------------------------------------------
BOOL UpdateCSPInfo( PCCERT_CONTEXT pcCertContext ) { BYTE cbData[1000]; CRYPT_KEY_PROV_INFO* pProvInfo = (CRYPT_KEY_PROV_INFO *) cbData; DWORD dwFoo = 1000; BOOL fSuccess = TRUE;
if ( ! CertGetCertificateContextProperty( pcCertContext, CERT_KEY_PROV_INFO_PROP_ID, pProvInfo, &dwFoo ) ) { fSuccess = FALSE; // iisDebugOut((_T("Fudge. failed to get property : 0x%x"), GetLastError());
} else { pProvInfo->dwProvType = PROV_RSA_SCHANNEL; pProvInfo->pwszProvName = NULL; if ( !CertSetCertificateContextProperty( pcCertContext, CERT_KEY_PROV_INFO_PROP_ID, 0, pProvInfo ) ) { fSuccess = FALSE; // iisDebugOut((_T("Fudge. failed to set property : 0x%x"), GetLastError());
} }
// return success
return fSuccess; }
//--------------------------------------------------------------------------------------------
BOOL DecodeAndImportPrivateKey( PBYTE pbEncodedPrivateKey IN, DWORD cbEncodedPrivateKey IN, PCHAR pszPassword IN, PWCHAR pszKeyContainer IN, CRYPT_KEY_PROV_INFO *pCryptKeyProvInfo ) /*++
Routine Description:
Converts the private key stored in the metabase, in Schannel-internal format, into a key that can be imported via CryptImportKey() to create a CAP1 key blob.
Arguments:
pbEncodedPrivateKey - pointer to [encoded] private key cbEncodedPrivateKey - size of encoded private key blob pszPassword - password used to encode private key pszKeyContainer - container name for private key pCryptKeyProvInfo - pointer to CRYPT_KEY_PROV_INFO structure filled in on success
Returns:
BOOL indicating success/failure
--*/ { BOOL fSuccess = FALSE; DWORD cbPassword = strlen(pszPassword); PPRIVATE_KEY_FILE_ENCODE pPrivateFile = NULL; DWORD cbPrivateFile = 0; MD5_CTX md5Ctx; struct RC4_KEYSTRUCT rc4Key; DWORD i; HCRYPTPROV hProv = NULL; HCRYPTKEY hPrivateKey = NULL; DWORD cbDecodedPrivateKey = 0; PBYTE pbDecodedPrivateKey = NULL;
DWORD err; //
//HACK HACK HACK - need to make sure Schannel is initialized, so it registers
//its custom decoders, which we make use of in the following code. So, make a
//bogus call to an Schannel function
// Note: on NT5, the AcquireCredentialsHandle operates in the lsass process and
// thus will not properly initialize the stuff we need in our process. Thus we
// call SslGenerateRandomBits instead.
//
DWORD dw; SslGenerateRandomBits( (PUCHAR)&dw, sizeof(dw) );
// We have to do a little fixup here. Old versions of
// schannel wrote the wrong header data into the ASN
// for private key files, so we must fix the size data.
pbEncodedPrivateKey[2] = (BYTE) (((cbEncodedPrivateKey - 4) & 0xFF00) >> 8); //Get MSB
pbEncodedPrivateKey[3] = (BYTE) ((cbEncodedPrivateKey - 4) & 0xFF); //Get LSB
//
// ASN.1 decode the private key.
//
//
// Figure out the size of the buffer needed
//
if( !CryptDecodeObject(X509_ASN_ENCODING, szPrivateKeyFileEncode, pbEncodedPrivateKey, cbEncodedPrivateKey, 0, NULL, &cbPrivateFile) ) { err = GetLastError(); // iisDebugOut((_T("Error 0x%x decoding the private key"), err);
goto EndDecodeKey; }
pPrivateFile = (PPRIVATE_KEY_FILE_ENCODE) LocalAlloc( LPTR, cbPrivateFile );
if(pPrivateFile == NULL) { SetLastError( ERROR_OUTOFMEMORY ); goto EndDecodeKey; }
//
// Actually fill in the buffer
//
if( !CryptDecodeObject( X509_ASN_ENCODING, szPrivateKeyFileEncode, pbEncodedPrivateKey, cbEncodedPrivateKey, 0, pPrivateFile, &cbPrivateFile ) ) { err = GetLastError(); // iisDebugOut((_T("Error 0x%x decoding the private key"), err);
goto EndDecodeKey; }
//
// Decrypt the decoded private key using the password.
//
MD5Init(&md5Ctx); MD5Update(&md5Ctx, (PBYTE) pszPassword, cbPassword); MD5Final(&md5Ctx);
rc4_key( &rc4Key, 16, md5Ctx.digest ); // memset( &md5Ctx, 0, sizeof(md5Ctx) );
rc4( &rc4Key, pPrivateFile->EncryptedBlob.cbData, pPrivateFile->EncryptedBlob.pbData );
//
// Build a PRIVATEKEYBLOB from the decrypted private key.
//
//
// Figure out size of buffer needed
//
if( !CryptDecodeObject( X509_ASN_ENCODING, szPrivateKeyInfoEncode, pPrivateFile->EncryptedBlob.pbData, pPrivateFile->EncryptedBlob.cbData, 0, NULL, &cbDecodedPrivateKey ) ) { // NOTE: This stuff is complicated!!! The following code came
// from John Banes. Heck this whole routine pretty much came
// from John Banes. -- Boyd
// Maybe this was a SGC style key.
// Re-encrypt it, and build the SGC decrypting
// key, and re-decrypt it.
BYTE md5Digest[MD5DIGESTLEN];
rc4_key(&rc4Key, 16, md5Ctx.digest); rc4(&rc4Key, pPrivateFile->EncryptedBlob.cbData, pPrivateFile->EncryptedBlob.pbData); CopyMemory(md5Digest, md5Ctx.digest, MD5DIGESTLEN);
MD5Init(&md5Ctx); MD5Update(&md5Ctx, md5Digest, MD5DIGESTLEN); MD5Update(&md5Ctx, (PUCHAR)SGC_KEY_SALT, strlen(SGC_KEY_SALT)); MD5Final(&md5Ctx); rc4_key(&rc4Key, 16, md5Ctx.digest); rc4(&rc4Key, pPrivateFile->EncryptedBlob.cbData, pPrivateFile->EncryptedBlob.pbData);
// Try again...
if(!CryptDecodeObject(X509_ASN_ENCODING, szPrivateKeyInfoEncode, pPrivateFile->EncryptedBlob.pbData, pPrivateFile->EncryptedBlob.cbData, 0, NULL, &cbDecodedPrivateKey)) { ZeroMemory(&md5Ctx, sizeof(md5Ctx)); err = GetLastError(); goto EndDecodeKey; } }
pbDecodedPrivateKey = (PBYTE) LocalAlloc( LPTR, cbDecodedPrivateKey );
if( pbDecodedPrivateKey == NULL ) { SetLastError( ERROR_OUTOFMEMORY ); goto EndDecodeKey; }
//
// Actually fill in the buffer
//
if( !CryptDecodeObject( X509_ASN_ENCODING, szPrivateKeyInfoEncode, pPrivateFile->EncryptedBlob.pbData, pPrivateFile->EncryptedBlob.cbData, 0, pbDecodedPrivateKey, &cbDecodedPrivateKey ) ) { err = GetLastError(); // iisDebugOut((_T("Error 0x%x decoding the private key"), err);
goto EndDecodeKey; }
// On NT 4 the ff holds true : <- from Alex Mallet
// Although key is going to be used for key exchange, mark it as being
// used for signing, because only 512-bit key exchange keys are supported
// in the non-domestic rsabase.dll, whereas signing keys can be up to
// 2048 bits.
//
// On NT 5, PROV_RSA_FULL should be changed to PROV_RSA_SCHANNEL, and
// aiKeyAlg to CALG_RSA_KEYX, because PROV_RSA_SCHANNEL, which is only
// installed on NT 5, supports 1024-bit private keys for key exchange
//
// On NT4, Schannel doesn't care whether a key is marked for signing or exchange,
// but on NT5 it does, so aiKeyAlg must be set appropriately
//
((BLOBHEADER *) pbDecodedPrivateKey)->aiKeyAlg = CALG_RSA_KEYX;
//
// Clean out the key container, pszKeyContainer
//
CryptAcquireContext(&hProv, pszKeyContainer, NULL, PROV_RSA_SCHANNEL, CRYPT_DELETEKEYSET | CRYPT_MACHINE_KEYSET); //
// Create a CryptoAPI key container in which to store the key.
//
if( !CryptAcquireContext( &hProv, pszKeyContainer, NULL, PROV_RSA_SCHANNEL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) { err = GetLastError(); // iisDebugOut((_T("Error 0x%x calling CryptAcquireContext"), err);
goto EndDecodeKey; }
//
// Import the private key blob into the key container.
//
if( !CryptImportKey( hProv, pbDecodedPrivateKey, cbDecodedPrivateKey, 0, CRYPT_EXPORTABLE, //so we can export it later
&hPrivateKey ) ) { err = GetLastError(); // iisDebugOut((_T("Error 0x%x importing PRIVATEKEYBLOB"), err);
goto EndDecodeKey; }
//
// Fill in the CRYPT_KEY_PROV_INFO structure, with the same parameters we
// used in the call to CryptAcquireContext() above
//
//
// container name in the structure is a unicode string, so we need to convert
//
if ( pszKeyContainer != NULL ) { // point the key container name to the passed in string
// WARNING: this does not actually copy the string, just the pointer
// to it. So the strings needs to remain valid until the ProvInfo is commited.
pCryptKeyProvInfo->pwszContainerName = pszKeyContainer; } else { pCryptKeyProvInfo->pwszContainerName = NULL; }
pCryptKeyProvInfo->pwszProvName = NULL; pCryptKeyProvInfo->dwProvType = PROV_RSA_FULL; pCryptKeyProvInfo->dwFlags = 0x20; // allow the cert to be exchanged
pCryptKeyProvInfo->cProvParam = 0; pCryptKeyProvInfo->rgProvParam = NULL; pCryptKeyProvInfo->dwKeySpec = AT_KEYEXCHANGE; // allow the cert to be exchanged
fSuccess = TRUE;
EndDecodeKey:
//
// Clean-up that happens regardless of success/failure
//
if ( pPrivateFile ) { LocalFree( pPrivateFile ); }
if ( pbDecodedPrivateKey ) { LocalFree( pbDecodedPrivateKey ); }
if ( hPrivateKey ) { CryptDestroyKey( hPrivateKey ); }
if ( hProv ) { CryptReleaseContext( hProv, 0 ); }
return fSuccess;
} //DecodeAndImportPrivateKey
//--------------------------------------------------------------------------------------------
/*++
Routine Description:
Takes an incoming PKCS10 request and saves it as a property attached to the key. It also checks if the request is in the old internal Keyring format or not......
Arguments:
pCert - CAPI certificate context pointer for the cert to save the request on pbPKCS10req - pointer to the request cbPKCS10req - size of the request
Returns:
BOOL indicating success/failure
--*/ BOOL FImportAndStoreRequest( PCCERT_CONTEXT pCert, PVOID pbPKCS10req, DWORD cbPKCS10req ) { BOOL f; DWORD err;
// if any NULLS are passed in, fail gracefully
if ( !pCert || !pbPKCS10req || !cbPKCS10req ) return FALSE;
// first, check if the incoming request is actually pointing to an old KeyRing internal
// request format. That just means that the real request is actuall slightly into
// the block. The way you tell is by testing the first DWORD to see it
// is REQUEST_HEADER_IDENTIFIER
// start by seeing if this is a new style key request
LPREQUEST_HEADER pHeader = (LPREQUEST_HEADER)pbPKCS10req; if ( pHeader->Identifier == REQUEST_HEADER_IDENTIFIER ) { // update the request pointer and data count
pbPKCS10req = (PBYTE)pbPKCS10req + pHeader->cbSizeOfHeader; cbPKCS10req = pHeader->cbRequestSize; }
// now save the request onto the key
CRYPT_DATA_BLOB dataBlob; ZeroMemory( &dataBlob, sizeof(dataBlob) ); dataBlob.pbData = (PBYTE)pbPKCS10req; // pointer to blob data
dataBlob.cbData = cbPKCS10req; // blob length info
f = CertSetCertificateContextProperty( pCert, CERTWIZ_REQUEST_PROP_ID, 0, &dataBlob ); err = GetLastError();
/*
HRESULT hRes = CertTool_SetBinaryBlobProp( pCert, // cert context to set the prop on
pbPKCS10req, // pointer to blob data
cbPKCS10req, // blob length info
CERTWIZ_REQUEST_PROP_ID,// property ID for context
TRUE // the request is already encoded
); */
return f; }
#endif //_CHICAGO_
|