Windows2003-3790/inetsrv/iis/admin/ssltools/certupgr.cpp
2020-09-30 16:53:55 +02:00

725 lines
24 KiB
C++

/*++
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_