windows-nt/Source/XPSP1/NT/inetsrv/iis/setup/osrc/sslkeys.cpp
2020-09-26 16:20:57 +08:00

1128 lines
44 KiB
C++

/* code to upgrade SSL keys to the latest mechanism
Since there have been several mechanisms to do this in the past we need
several mechanisms to store and retrieve the private and public portions
of the keys.
IIS2/3 used the LSA mechanism to store the keys as secrets in the registry.
IIS4 stored the keys directly in the metabase as secured data objects.
IIS5 will be using the native NT5 Protected Storage mechanism to keep the keys.
This means that we are no longer in the business of storing, protecting and
retrieving the keys. It will all be done in NT maintained facilities. However,
we still need to migrate the keys over to the new storage mechanism, which is
what this code is all about.
One more thing. Previously the keys were associated with the virtual servers
in an indirect manner. The keys (IIS4) were stored in a metabase location that
was parallel to the virtual servers. Then each key had an IP\Port binding
associated with it that mapped the key back to the original server.
This has caused no end of confusion for the users as they struggle to associate
keys with virtual servers.
Now the references to the keys in the PStore are stored directly on each virtual
server, creating a implicit releationship between the key and the server.
The old mapping scheme also supported the concept of wildcarded IP or Port
address. Whereas this new scheme does not. This means the upgrading will be done
as a several stop process. First, we look for all the existing keys that are bound
to a specific IP/Port combination. This takes precedence over wildcards and is
applied to the keys first. Then IP/wild is applied to any matching virtual server
that does not already have a key on it. Then wile/Port. Since we have always
required that there can only be one default key at a time, as soon as we encounter
it in the process we can just apply it to the master-properties level.
Fortunately, this whole file is on NT only, so we can assume everything is UNICODE always
*/
#include "stdafx.h"
// this file is also only used on NT, so don't do anything if its win9X
#ifndef _CHICAGO_
#include <ole2.h>
#include "iadm.h"
#include "iiscnfgp.h"
#include "mdkey.h"
#include "lsaKeys.h"
#include "setupapi.h"
#undef MAX_SERVICE_NAME_LEN
#include "elem.h"
#include "mdentry.h"
#include "inetinfo.h"
#include "inetcom.h"
#include "logtype.h"
#include "ilogobj.hxx"
#include "ocmanage.h"
#include "sslkeys.h"
extern OCMANAGER_ROUTINES gHelperRoutines;
#include <wincrypt.h>
#define SECURITY_WIN32
#include <sspi.h>
#include <spseal.h>
#include <issperr.h>
#include <schnlsp.h>
#include "certupgr.h"
const LPCTSTR MDNAME_INCOMPLETE = _T("incomplete");
const LPCTSTR MDNAME_DISABLED = _T("disabled");
const LPCTSTR MDNAME_DEFAULT = _T("default");
const LPCTSTR SZ_SERVER_KEYTYPE = _T("IIsWebServer");
const LPTSTR SZ_SSLKEYS_NODE = _T("SSLKeys");
const LPTSTR SZ_W3SVC_PATH = _T("LM/W3SVC");
const LPTSTR SZ_SSLKEYS_PATH = _T("LM/W3SVC/SSLKeys");
const LPWSTR SZ_CAPI_STORE = L"MY";
#define ALLOW_DELETE_KEYS // Normally defined. Don't define for test purposes.
//------------------------------------------------------------------------------
// Given the name of a key in the metabase migrate it to the PStore. At this point we
// are actually only loading and preparing the raw data. The routines to stick it in the
// right place are in an external library so they can be shared with other utilities.
// since the metabase key is already opened by the calling routine, pass it in as a
// parameter.
// returns TRUE for success
PCCERT_CONTEXT MigrateKeyToPStore( CMDKey* pmdKey, CString& csMetaKeyName )
{
iisDebugOut((LOG_TYPE_TRACE, _T("MigrateKeyToPStore():Start.%s."), (LPCTSTR)csMetaKeyName));
BOOL fSuccess = FALSE;
BOOL f;
DWORD dwAttr, dwUType, dwDType, cbLen, dwLength;
PVOID pbPrivateKey = NULL;
DWORD cbPrivateKey = 0;
PVOID pbPublicKey = NULL;
DWORD cbPublicKey = 0;
PVOID pbRequest = NULL;
DWORD cbRequest = 0;
PCHAR pszPassword = NULL;
PCCERT_CONTEXT pcCertContext = NULL;
// the actual sub key path this the sslkeys dir plus the key name. The actual metabase
// object is opened to the w3svc level
CString csSubKeyPath = _T("SSLKeys/");
csSubKeyPath += csMetaKeyName;
// get the private key - required ---------
dwAttr = 0;
dwUType = IIS_MD_UT_SERVER;
dwDType = BINARY_METADATA;
// this first call is just to get the size of the pointer we need
f = pmdKey->GetData(MD_SSL_PRIVATE_KEY,&dwAttr,&dwUType,&dwDType,&cbPrivateKey,NULL,0,(PWCHAR)(LPCTSTR)csSubKeyPath);
// if the get data fails on the private key, we have nothing to do
if ( cbPrivateKey == 0 )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED: Unable to read private key for %s"), (LPCTSTR)csMetaKeyName));
return NULL;
}
// allocate the buffer for the private key
pbPrivateKey = GlobalAlloc( GPTR, cbPrivateKey );
if ( !pbPrivateKey )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED to allocate memory for private key.")));
return NULL;
}
// do the real call to get the data from the metabase
f = pmdKey->GetData(MD_SSL_PRIVATE_KEY,&dwAttr,&dwUType,&dwDType,&cbPrivateKey,(PUCHAR)pbPrivateKey,cbPrivateKey,(PWCHAR)(LPCTSTR)csSubKeyPath);
// if the get data fails on the private key, we have nothing to do
if ( !f )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED: Unable to read private key for %s"), (LPCTSTR)csMetaKeyName));
goto cleanup;
}
// get the password -required ------------
// the password is stored as an ansi binary secure item.
dwAttr = 0;
dwUType = IIS_MD_UT_SERVER;
dwDType = BINARY_METADATA;
cbLen = 0;
// this first call is just to get the size of the pointer we need
f = pmdKey->GetData(MD_SSL_KEY_PASSWORD,&dwAttr,&dwUType,&dwDType,&cbLen,NULL,0,(PWCHAR)(LPCTSTR)csSubKeyPath);
// if the get data fails on the password, we have nothing to do
if ( cbLen == 0 )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED retrieve password. Nothing to do.")));
goto cleanup;
}
// allocate the buffer for the password
pszPassword = (PCHAR)GlobalAlloc( GPTR, cbLen );
if ( !pszPassword )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED to allocate memory for password.")));
goto cleanup;
}
// do the real call to get the data from the metabase
f = pmdKey->GetData(MD_SSL_KEY_PASSWORD,&dwAttr,&dwUType,&dwDType,&cbLen,(PUCHAR)pszPassword,cbLen,(PWCHAR)(LPCTSTR)csSubKeyPath);
// if the get data fails on the password, we have nothing to do
if ( !f )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED: Unable to read ssl password for %s"), (LPCTSTR)csMetaKeyName));
goto cleanup;
}
// get the public key -optional -----------
dwAttr = 0;
dwUType = IIS_MD_UT_SERVER;
dwDType = BINARY_METADATA;
// this first call is just to get the size of the pointer we need
f = pmdKey->GetData(MD_SSL_PUBLIC_KEY,&dwAttr,&dwUType,&dwDType,&cbPublicKey,NULL,0,(PWCHAR)(LPCTSTR)csSubKeyPath);
// the public key is optional, so don't fail if we don't get it
if ( cbPublicKey )
{
// allocate the buffer for the private key
pbPublicKey = GlobalAlloc( GPTR, cbPublicKey );
if ( !pbPublicKey )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED to allocate memory for public key.")));
}
else
{
// do the real call to get the data from the metabase
f = pmdKey->GetData(MD_SSL_PUBLIC_KEY,&dwAttr,&dwUType,&dwDType,&cbPublicKey,(PUCHAR)pbPublicKey,cbPublicKey,(PWCHAR)(LPCTSTR)csSubKeyPath);
// if the get data fails on the public key, clean it up and reset it to null
if ( !f )
{
if ( pbPublicKey )
{
GlobalFree( pbPublicKey );
pbPublicKey = NULL;
}
cbPublicKey = 0;
}
}
}
// get the request -optional -----------
dwAttr = 0;
dwUType = IIS_MD_UT_SERVER;
dwDType = BINARY_METADATA;
// this first call is just to get the size of the pointer we need
f = pmdKey->GetData(MD_SSL_KEY_REQUEST,&dwAttr,&dwUType,&dwDType,
&cbRequest,NULL,0,(PWCHAR)(LPCTSTR)csSubKeyPath);
// the request is optional, so don't fail if we don't get it
if ( cbRequest )
{
// allocate the buffer for the private key
pbRequest = GlobalAlloc( GPTR, cbRequest );
if ( !pbRequest )
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():FAILED to allocate memory for key request.")));
}
else
{
// do the real call to get the data from the metabase
f = pmdKey->GetData(MD_SSL_KEY_REQUEST,&dwAttr,&dwUType,&dwDType,
&cbRequest,(PUCHAR)pbRequest,cbRequest,(PWCHAR)(LPCTSTR)csSubKeyPath);
// if the get data fails on the key request, clean it up and reset it to null
if ( !f )
{
if ( pbRequest )
{
GlobalFree( pbRequest );
pbRequest = NULL;
}
cbRequest = 0;
}
}
}
// ------------------------------------------------------------------
// Now that we've loaded the data, we can call the conversion utility
// ------------------------------------------------------------------
pcCertContext = CopyKRCertToCAPIStore(
pbPrivateKey, cbPrivateKey,
pbPublicKey, cbPublicKey,
pbRequest, cbRequest,
pszPassword,
SZ_CAPI_STORE );
if ( pcCertContext )
{
iisDebugOut((LOG_TYPE_TRACE, _T("MigrateKeyToPStore():CopyKRCertToCAPIStore():Upgrade KR key to CAPI for %s. Success."), (LPCTSTR)csMetaKeyName));
}
else
{
iisDebugOut((LOG_TYPE_ERROR, _T("MigrateKeyToPStore():CopyKRCertToCAPIStore():Upgrade KR key to CAPI for %s. FAILED."), (LPCTSTR)csMetaKeyName));
}
cleanup:
if ( pbPrivateKey ) {GlobalFree( pbPrivateKey );}
if ( pbPublicKey ) {GlobalFree( pbPublicKey );}
if ( pszPassword ) {GlobalFree( pszPassword );}
iisDebugOut((LOG_TYPE_TRACE, _T("MigrateKeyToPStore():End.%s."), (LPCTSTR)csMetaKeyName));
return pcCertContext;
}
//------------------------------------------------------------------------------
// write a reference to a PStore key on a specific node in the metabase
void WriteKeyReference( CMDKey& cmdW3SVC, PWCHAR pwchSubPath, PCCERT_CONTEXT pCert )
{
// get the hash that we need to write out
//
// SHA produces 160 bit hash for any message < 2^64 bits in length
BYTE HashBuffer[40]; // give it some extra size
DWORD dwHashSize = sizeof(HashBuffer);
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertGetCertificateContextProperty().Start.")));
if ( !CertGetCertificateContextProperty( pCert,
CERT_SHA1_HASH_PROP_ID,
(VOID *) HashBuffer,
&dwHashSize ) )
{
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertGetCertificateContextProperty().End.")));
if ( GetLastError() == ERROR_MORE_DATA )
{
//Very odd, cert wants more space ..
iisDebugOut((LOG_TYPE_ERROR, _T("FAILED: StoreCertInfoInMetabase Unable to get hash property")));
}
// We definitely need to store the hash of the cert, so error out
return;
}
else
{
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertGetCertificateContextProperty().End.")));
}
// write out the hash of the certificate
cmdW3SVC.SetData( MD_SSL_CERT_HASH, METADATA_INHERIT, IIS_MD_UT_SERVER, BINARY_METADATA,
dwHashSize, (PUCHAR)&HashBuffer, pwchSubPath );
// write out the name of the store
cmdW3SVC.SetData( MD_SSL_CERT_STORE_NAME, METADATA_INHERIT, IIS_MD_UT_SERVER, STRING_METADATA,
(_tcslen(SZ_CAPI_STORE)+1) * sizeof(TCHAR), (PUCHAR)SZ_CAPI_STORE, pwchSubPath );
}
//------------------------------------------------------------------------------
// store a reference to a PStore key on all the appropriate virtual servers. If csIP
// or csPort is empty, then that item is a wildcard and applies to all virtual servers.
void StoreKeyReference( CMDKey& cmdW3SVC, PCCERT_CONTEXT pCert, CString& csIP, CString& csPort )
{
TCHAR szForDebug[100];
if (csIP)
{
if (csPort){_stprintf(szForDebug, _T("ip:%s port:%s"), csIP, csPort);}
else{_stprintf(szForDebug, _T("ip:%s port:(null)"), csIP);}
}
else
{
if (csPort){_stprintf(szForDebug, _T("ip:(null) port:%s"), csPort);}
else{_stprintf(szForDebug, _T("ip:(null) port:(null)"));}
}
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s."),szForDebug));
// if it was unable to open the node, then there are no keys to upgrade.
if ( (METADATA_HANDLE)cmdW3SVC == NULL )
{
iisDebugOut((LOG_TYPE_ERROR, _T("FAILED: passed in invalid metabase handle")));
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference End")));
return;
}
// generate the iterator for retrieving the virtual servers
CMDKeyIter cmdKeyEnum( cmdW3SVC );
CString csNodeName; // Metabase name for the virtual server
CString csNodeType; // node type indicator string
CString csBinding;
PVOID pData = NULL;
BOOL f;
DWORD dwAttr, dwUType, dwDType, cbLen, dwLength;
// iterate through the virtual servers
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.iterate through the virtual servers"),szForDebug));
while (cmdKeyEnum.Next(&csNodeName) == ERROR_SUCCESS)
{
// some of the keys under this node are not virutal servers. Thus
// we first need to check the node type property. If it is not a
// virtual server then we can just contiue on to the next node.
// get the string that indicates the node type
dwAttr = 0;
dwUType = IIS_MD_UT_SERVER;
dwDType = STRING_METADATA;
cbLen = 200;
f = cmdW3SVC.GetData(MD_KEY_TYPE,
&dwAttr,
&dwUType,
&dwDType,
&cbLen,
(PUCHAR)csNodeType.GetBuffer(cbLen),
cbLen,
(PWCHAR)(LPCTSTR)csNodeName);
csNodeType.ReleaseBuffer();
// check it - if the node is not a virutal server, then continue on to the next node
if ( csNodeType != SZ_SERVER_KEYTYPE )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s not a virtualserver, skip."),szForDebug,csNodeName));
continue;
}
// before we do anything else, check if this virtual server already has a key on it.
// if it does then do not do anything to it. Continue on to the next one
// we don't actually need to load any data for this to work, so we can call GetData
// with a size of zero as if we are querying for the size. If that succeedes, then
// we know that it is there and can continue on
dwAttr = 0; // do not inherit
dwUType = IIS_MD_UT_SERVER;
dwDType = BINARY_METADATA;
dwLength = 0;
cmdW3SVC.GetData( MD_SSL_CERT_HASH,
&dwAttr,
&dwUType,
&dwDType,
&dwLength,
NULL,
0,
0, // do not inherit
IIS_MD_UT_SERVER,
BINARY_METADATA,
(PWCHAR)(LPCTSTR)csNodeName);
// if there is a key there already - continue to the next node
if ( dwLength > 0 )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s already has a key there, skip."),szForDebug,csNodeName));
continue;
}
// this is a valid virtual server with no pre-existing key. Now we need to load
// the bindings and see if we have a match
dwAttr = 0; // do not inherit
dwUType = IIS_MD_UT_SERVER;
dwDType = MULTISZ_METADATA;
dwLength = 0;
// The bindings are in a multi-sz. So, first we need to figure out how much space we need
f = cmdW3SVC.GetData( MD_SECURE_BINDINGS,
&dwAttr,
&dwUType,
&dwDType,
&dwLength,
NULL,
0,
0, // do not inherit
IIS_MD_UT_SERVER,
MULTISZ_METADATA,
(PWCHAR)(LPCTSTR)csNodeName);
// if the length is zero, then there are no bindings
if ( dwLength == 0 )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s data len=0 no bindings, skip."),szForDebug,csNodeName));
continue;
}
// Prepare some space to receive the bindings
TCHAR* pBindings;
// if pData is pointing to something, then we need to free it so that we don't leak
if ( pData )
{
GlobalFree( pData );
pData = NULL;
}
// allocate the space, if it fails, we fail
// note that GPTR causes it to be initialized to zero
pData = GlobalAlloc( GPTR, dwLength + 2 );
if ( !pData )
{
iisDebugOut((LOG_TYPE_ERROR, _T("StoreKeyReference.Start.%s.%s GlobalAlloc failed."),szForDebug,csNodeName));
continue;
}
pBindings = (TCHAR*)pData;
// now get the real data from the metabase
f = cmdW3SVC.GetData( MD_SECURE_BINDINGS,
&dwAttr,
&dwUType,
&dwDType,
&dwLength,
(PUCHAR)pBindings,
dwLength,
0, // do not inherit
IIS_MD_UT_SERVER,
MULTISZ_METADATA,
(PWCHAR)(LPCTSTR)csNodeName );
// if we did not get the bindings, then this node doesn't have any security
// options set on it. We can continue on to the next virtual server
if ( FALSE == f )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s No security options set on it, skip."),szForDebug,csNodeName));
continue;
}
// OK. We do have bindings. Now we get to parse them out and check them
// against the binding strings that were passed in. Note: if a binding
// matches, but has a host-header at the end, then it does not qualify
// got the existing bindings, scan them now - pBindings will be pointing at the second end \0
// when it is time to exit the loop.
while ( *pBindings )
{
csBinding = pBindings;
csBinding.TrimRight();
CString csBindIP;
CString csBindPort; // don't actually care about this one
// get the binding's IP and port sections so we can look for wildcards in the binding itself
PrepIPPortName( csBinding, csBindIP, csBindPort );
// if there is a specified IP, look for it. If we don't find it, go to the next binding.
if ( !csIP.IsEmpty() && !csBindIP.IsEmpty() )
{
// if the IP is not in the binding then bail on this binding
if ( csBinding.Find( csIP ) < 0 )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s:org=%s,findIP=%s bail."),szForDebug,csNodeName,csBinding,csIP));
goto NextBinding;
}
}
// if there is a specified Port, look for it. If we don't find it, go to the next binding.\
// secure bindings themselves always have a port
if ( !csPort.IsEmpty() )
{
// if the Port is not in the binding then bail on this binding
if ( csBinding.Find( csPort ) < 0 )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s:org=%s,findport=%s bail."),szForDebug,csNodeName,csBinding,csPort));
goto NextBinding;
}
}
// test if host headers are there by doing a reverse find for the last colon. Then
// check if it is the last character. If it isn't, then there is a host-header and
// we should go to a different binding
if ( csBinding.ReverseFind(_T(':')) < (csBinding.GetLength()-1) )
{
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.Start.%s.%s:bail2."),szForDebug,csNodeName));
goto NextBinding;
}
// Well, this is a valid binding on a valid virtual server, we can now write out the key
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.%s.%s:Write out the key!"),szForDebug,csNodeName));
WriteKeyReference( cmdW3SVC, (PWCHAR)(LPCTSTR)csNodeName, pCert );
// we can break to get out of the specific bindings loop
break;
NextBinding:
// increment pBindings to the next string
pBindings = _tcsninc( pBindings, _tcslen(pBindings))+1;
}
}
// if pData is pointing to something, then we need to free it so that we don't leak
if ( pData )
{
GlobalFree( pData );
pData = NULL;
}
iisDebugOut((LOG_TYPE_TRACE, _T("StoreKeyReference.End.%s."),szForDebug));
}
//------------------------------------------------------------------------------
// given a metabase key name, create strings that can be used to search the virutal servers
// an empty string is a wildcard.
BOOL PrepIPPortName( CString& csKeyMetaName, CString& csIP, CString& csPort )
{
int iColon;
// the first thing we are going to do is seperate the IP and PORT into seperate strings
// actually, thats not true. Prep the string by putting a colon in it.
csIP.Empty();
csPort = _T(':');
// look for the first ':' and seperate
iColon = csKeyMetaName.Find( _T(':') );
// if we got the colon, we can seperate easy
if ( iColon >= 0 )
{
csIP = csKeyMetaName.Left(iColon);
csPort += csKeyMetaName.Right(csKeyMetaName.GetLength() - iColon - 1);
}
// we did not get the colon, so it is one or the other, look for a '.' to get the IP
else
{
if ( csKeyMetaName.Find( _T('.') ) >= 0 )
csIP = csKeyMetaName;
else
csPort += csKeyMetaName;
}
// finish decorating the strings with colons if appropriate.
if ( !csIP.IsEmpty() )
csIP += _T(':');
// If the only thing in the port string is a : then it is a wildcard. Clear it out.
if ( csPort.GetLength() == 1 )
{
csPort.Empty();
}
else
{
// add a final colon to it
csPort += _T(':');
}
return TRUE;
}
//------------------------------------------------------------------------------
// used when upgrading from IIS2 or IIS3
// this code was in the K2 setup program that shipped. It used to reside in mdentry.cpp and
// has now been encapsulted into its own routine and moved here. The only change to it has been
// to add the Upgradeiis4Toiis5MetabaseSSLKeys call at the end.
void UpgradeLSAKeys( PWCHAR pszwTargetMachine )
{
iisDebugOut((LOG_TYPE_TRACE, _T("UpgradeLSAKeys Start")));
DWORD retCode = KEYLSA_SUCCESS;
MDEntry stMDEntry;
CString csMDPath;
TCHAR tchFriendlyName[_MAX_PATH], tchMetaName[_MAX_PATH];
BOOL fUpgradedAKey = FALSE;
CLSAKeys lsaKeys;
WCHAR wchMachineName[UNLEN + 1];
memset( (PVOID)wchMachineName, 0, sizeof(wchMachineName));
#if defined(UNICODE) || defined(_UNICODE)
wcsncpy(wchMachineName, g_pTheApp->m_csMachineName, UNLEN);
#else
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)g_pTheApp->m_csMachineName, -1, (LPWSTR)wchMachineName, UNLEN);
#endif
retCode = lsaKeys.LoadFirstKey(wchMachineName);
while (retCode == KEYLSA_SUCCESS) {
#if defined(UNICODE) || defined(_UNICODE)
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)lsaKeys.m_szMetaName, -1, (LPWSTR)tchMetaName, _MAX_PATH);
MultiByteToWideChar(CP_ACP, 0, (LPCSTR)lsaKeys.m_szFriendlyName, -1, (LPWSTR)tchFriendlyName, _MAX_PATH);
#else
_tcscpy(tchMetaName, lsaKeys.m_szMetaName);
_tcscpy(tchFriendlyName, lsaKeys.m_szFriendlyName);
#endif
iisDebugOut((LOG_TYPE_TRACE, _T("lsaKeys: FriendName=%s MetaName=%s\n"), tchFriendlyName, tchMetaName));
csMDPath = SZ_SSLKEYS_PATH;
csMDPath += _T("/");
csMDPath += (CString)tchMetaName;
stMDEntry.szMDPath = (LPTSTR)(LPCTSTR)csMDPath;
stMDEntry.dwMDIdentifier = MD_SSL_FRIENDLY_NAME;
stMDEntry.dwMDAttributes = METADATA_INHERIT;
stMDEntry.dwMDUserType = IIS_MD_UT_SERVER;
stMDEntry.dwMDDataType = STRING_METADATA;
stMDEntry.dwMDDataLen = (_tcslen(tchFriendlyName) + 1) * sizeof(TCHAR);
stMDEntry.pbMDData = (LPBYTE)tchFriendlyName;
SetMDEntry(&stMDEntry);
stMDEntry.dwMDIdentifier = MD_SSL_PUBLIC_KEY;
stMDEntry.dwMDAttributes = METADATA_INHERIT | METADATA_SECURE;
stMDEntry.dwMDUserType = IIS_MD_UT_SERVER;
stMDEntry.dwMDDataType = BINARY_METADATA;
stMDEntry.dwMDDataLen = lsaKeys.m_cbPublic;
stMDEntry.pbMDData = (LPBYTE)lsaKeys.m_pPublic;
SetMDEntry(&stMDEntry);
stMDEntry.dwMDIdentifier = MD_SSL_PRIVATE_KEY;
stMDEntry.dwMDDataLen = lsaKeys.m_cbPrivate;
stMDEntry.pbMDData = (LPBYTE)lsaKeys.m_pPrivate;
SetMDEntry(&stMDEntry);
stMDEntry.dwMDIdentifier = MD_SSL_KEY_PASSWORD;
stMDEntry.dwMDDataLen = lsaKeys.m_cbPassword;
stMDEntry.pbMDData = (LPBYTE)lsaKeys.m_pPassword;
SetMDEntry(&stMDEntry);
stMDEntry.dwMDIdentifier = MD_SSL_KEY_REQUEST;
stMDEntry.dwMDDataLen = lsaKeys.m_cbRequest;
stMDEntry.pbMDData = (LPBYTE)lsaKeys.m_pRequest;
SetMDEntry(&stMDEntry);
fUpgradedAKey = TRUE;
retCode = lsaKeys.LoadNextKey();
}
if (retCode == KEYLSA_NO_MORE_KEYS) {
iisDebugOut((LOG_TYPE_TRACE, _T("No More Keys\n")));
lsaKeys.DeleteAllLSAKeys();
}
// Now that the keys have been upgraded to the metabase, upgrade again from
// the metabase to the PStore
if ( fUpgradedAKey )
Upgradeiis4Toiis5MetabaseSSLKeys();
iisDebugOut((LOG_TYPE_TRACE, _T("UpgradeLSAKeys End")));
}
//------------------------------------------------------------------------------
// the plan here is to enumerate all the server keys under the SSLKEYS key in the metabase.
// Then they need to be migrated to the PStore and have their references resaved into
// the correct virtual server.
//
// How the heck does all this work?
//
// iis4.0 metabase looks like this:
// w3svc
// w3svc/1
// w3svc/2
// sslkeys
// sslkeys/(entry1) <--could be one of either of the ssl key types list below
// sslkeys/(entry2) <--
// sslkeys/(entry3) <--
//
// sslkey types:
// sslkeys/MDNAME_DISABLED
// sslkeys/MDNAME_INCOMPLETE
// sslkeys/MDNAME_DEFAULT
// sslkeys/ip:port
//
// step 1. Grab all these sslkeys/entries and move them into the new storage (MigrateKeyToPStore)
// (for each entry we move into the new storage, we add an entry to a Cstring List to say (we did this one already) )
// a. do it in iteration#1 for In this loop we look for the default key, disabled keys, incomplete keys, and keys specified by specific IP/Port pairs.
// b. do it in iteration#2 for IP/wild port keys.
// c. do it in iteration#3 the rest of the keys, which should all be wild ip/Port keys.
// step 2. for each of these keys which we moved to the new storage: store the reference which we get back from CAPI
// in our metabase (StoreKeyReference)
// step 3. Make sure to keep the metabasekeys around, because setup may actually fail: so we don't want to delete the keys
// until we are sure that setup is completed.
// step 4. after setup completes without any errors, we delete all the sslkeys
//
void Upgradeiis4Toiis5MetabaseSSLKeys()
{
iisDebugOut_Start(_T("Upgradeiis4Toiis5MetabaseSSLKeys"), LOG_TYPE_TRACE);
iisDebugOut((LOG_TYPE_TRACE, _T("--------------------------------------")));
CString csMDPath;
// start by testing that the sslkeys node exists.
CMDKey cmdKey;
cmdKey.OpenNode( SZ_SSLKEYS_PATH );
if ( (METADATA_HANDLE)cmdKey == NULL )
{
// there is nothing to do
iisDebugOut((LOG_TYPE_TRACE, _T("Nothing to do.")));
return;
}
cmdKey.Close();
// create a key object for the SSLKeys level in the metabase. Open it too.
cmdKey.OpenNode( SZ_W3SVC_PATH );
if ( (METADATA_HANDLE)cmdKey == NULL )
{
// if it was unable to open the node, then there are no keys to upgrade.
iisDebugOut((LOG_TYPE_WARN, _T("could not open lm/w3svc")));
iisDebugOut_End(_T("Upgradeiis4Toiis5MetabaseSSLKeys,No keys to upgrade"),LOG_TYPE_TRACE);
return;
}
// create and prepare a metadata iterator object for the sslkeys
CMDKeyIter cmdKeyEnum(cmdKey);
CString csKeyName; // Metabase name for the key
// used to parse out the name information
CString csIP;
CString csPort;
//CString csSubPath;
PCCERT_CONTEXT pCert = NULL;
PCCERT_CONTEXT pDefaultCert = NULL;
BOOL bUpgradeToPStoreIsGood = TRUE;
// do the first iteration. In this loop we look for the default key, disabled keys,
// incomplete keys, and keys specified by specific IP/Port pairs.
// Note: cmdKeyEnum.m_index is the index member for the iterator
iisDebugOut((LOG_TYPE_TRACE, _T("1.first interate for default,disabled,incomplete,and keys specified by specific IP/Port pairs.")));
while (cmdKeyEnum.Next(&csKeyName, SZ_SSLKEYS_NODE ) == ERROR_SUCCESS)
{
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("KeyName=%s."),csKeyName));
pCert = NULL;
// look for disabled keys
if ( csKeyName.Find(MDNAME_DISABLED) >= 0)
{
pCert = MigrateKeyToPStore( &cmdKey, csKeyName );
if (!pCert){bUpgradeToPStoreIsGood = FALSE;}
}
// look for incomplete keys
else if ( csKeyName.Find(MDNAME_INCOMPLETE) >= 0)
{
pCert = MigrateKeyToPStore( &cmdKey, csKeyName );
if (!pCert){bUpgradeToPStoreIsGood = FALSE;}
}
// look for the default key
else if ( csKeyName.Find(MDNAME_DEFAULT) >= 0)
{
pDefaultCert = MigrateKeyToPStore( &cmdKey, csKeyName );
if (!pDefaultCert){bUpgradeToPStoreIsGood = FALSE;}
}
// parse the IP/Port name
else
{
// we are only taking keys that have both the IP and the Port specified at this time
PrepIPPortName( csKeyName, csIP, csPort );
if ( !csIP.IsEmpty() && !csPort.IsEmpty() )
{
// move the key from the metabase to the
pCert = MigrateKeyToPStore( &cmdKey, csKeyName );
if ( pCert )
{StoreKeyReference( cmdKey, pCert, csIP, csPort );}
else
{bUpgradeToPStoreIsGood = FALSE;}
}
}
// don't leak CAPI certificate contexts now that we are done with it
if ( pCert )
{
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().Start.")));
CertFreeCertificateContext( pCert );
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().End.")));
}
} // end while part 1
// do the second iteration looking only for IP/wild port keys.
iisDebugOut((LOG_TYPE_TRACE, _T("2.Second iteration looking only for IP/wild port keys.")));
cmdKeyEnum.Reset();
while (cmdKeyEnum.Next(&csKeyName, SZ_SSLKEYS_NODE ) == ERROR_SUCCESS)
{
pCert = NULL;
// parse the IP/Port name
// we are only taking keys that have the IP specified at this time
PrepIPPortName( csKeyName, csIP, csPort );
if ( !csIP.IsEmpty() && csPort.IsEmpty() )
{
// move the key from the metabase to the
pCert = MigrateKeyToPStore( &cmdKey, csKeyName );
if ( pCert )
{StoreKeyReference( cmdKey, pCert, csIP, csPort );}
else
{bUpgradeToPStoreIsGood = FALSE;}
}
// don't leak CAPI certificate contexts now that we are done with it
if ( pCert )
{
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().Start.")));
CertFreeCertificateContext( pCert );
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().End.")));
}
}
// upgrade the rest of the keys, which should all be wild ip/Port keys.
iisDebugOut((LOG_TYPE_TRACE, _T("3.upgrade the rest of the keys, which should all be wild ip/Port keys.")));
cmdKeyEnum.Reset();
while (cmdKeyEnum.Next(&csKeyName, SZ_SSLKEYS_NODE) == ERROR_SUCCESS)
{
pCert = NULL;
// parse the IP/Port name
// we are only taking keys that have the PORT specified at this time
PrepIPPortName( csKeyName, csIP, csPort );
if ( !csPort.IsEmpty() && csIP.IsEmpty())
{
// move the key from the metabase to the
pCert = MigrateKeyToPStore( &cmdKey, csKeyName );
if ( pCert )
{StoreKeyReference( cmdKey, pCert, csIP, csPort );}
else
{bUpgradeToPStoreIsGood = FALSE;}
}
// don't leak CAPI certificate contexts now that we are done with it
if ( pCert )
{
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().Start.")));
CertFreeCertificateContext( pCert );
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().End.")));
}
}
// if there is one, write the default key reference out
if ( pDefaultCert )
{
iisDebugOut((LOG_TYPE_TRACE, _T("4.write default key reference out")));
CString csPortDefault;
// old way which used to put it on the lm/w3svc node.
// but we can't do that anymore since that node can't be accessed by the iis snap-in!
//WriteKeyReference( cmdKey, L"", pDefaultCert );
csPortDefault = _T(":443:");
StoreKeyReference_Default( cmdKey, pDefaultCert, csPortDefault );
// don't leak CAPI certificate contexts now that we are done with it
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().Start.")));
CertFreeCertificateContext( pDefaultCert );
iisDebugOut((LOG_TYPE_TRACE_WIN32_API, _T("CRYPT32.dll:CertFreeCertificateContext().End.")));
}
//#ifdef ALLOW_DELETE_KEYS
if (TRUE == bUpgradeToPStoreIsGood)
{
iisDebugOut((LOG_TYPE_TRACE, _T("Upgradeiis4Toiis5MetabaseSSLKeys. 5. Removing upgraded sslkeys node.")));
// delete the sslkeys node in the metabase
cmdKey.DeleteNode( SZ_SSLKEYS_NODE );
}
else
{
iisDebugOut((LOG_TYPE_TRACE, _T("Upgradeiis4Toiis5MetabaseSSLKeys. 6. MigrateKeyToPStore failed so keeping ssl key in metabase.")));
}
//#endif //ALLOW_DELETE_KEYS
// close the master properties key.
cmdKey.Close();
iisDebugOut_End(_T("Upgradeiis4Toiis5MetabaseSSLKeys"), LOG_TYPE_TRACE);
iisDebugOut((LOG_TYPE_TRACE, _T("--------------------------------------")));
// force the metabase to write.
WriteToMD_ForceMetabaseToWriteToDisk();
return;
}
//------------------------------------------------------------------------------
// store a reference to a PStore key on all the appropriate virtual servers.
void StoreKeyReference_Default( CMDKey& cmdW3SVC, PCCERT_CONTEXT pCert, CString& csPort )
{
iisDebugOut_Start(_T("StoreKeyReference_Default"), LOG_TYPE_TRACE);
// generate the iterator for retrieving the virtual servers
CMDKeyIter cmdKeyEnum( cmdW3SVC );
CString csNodeName; // Metabase name for the virtual server
CString csNodeType; // node type indicator string
CString csBinding;
PVOID pData = NULL;
BOOL f;
DWORD dwAttr, dwUType, dwDType, cbLen, dwLength;
// We are looking for a particular port which is stored in csPort.
// if there is no csPort passed in then lets get out of here!
if ( csPort.IsEmpty() )
{
goto StoreKeyReference_Default_Exit;
}
// if it was unable to open the node, then there are no keys to upgrade.
if ( (METADATA_HANDLE)cmdW3SVC == NULL )
{
iisDebugOut((LOG_TYPE_ERROR, _T("passed in invalid metabase handle")));
goto StoreKeyReference_Default_Exit;
}
// iterate through the virtual servers
while (cmdKeyEnum.Next(&csNodeName) == ERROR_SUCCESS)
{
// some of the keys under this node are not virutal servers. Thus
// we first need to check the node type property. If it is not a
// virtual server then we can just contiue on to the next node.
// get the string that indicates the node type
dwAttr = 0;
dwUType = IIS_MD_UT_SERVER;
dwDType = STRING_METADATA;
cbLen = 200;
f = cmdW3SVC.GetData(MD_KEY_TYPE,
&dwAttr,
&dwUType,
&dwDType,
&cbLen,
(PUCHAR)csNodeType.GetBuffer(cbLen),
cbLen,
(PWCHAR)(LPCTSTR)csNodeName);
csNodeType.ReleaseBuffer();
// check it - if the node is not a virutal server, then continue on to the next node
if ( csNodeType != SZ_SERVER_KEYTYPE )
{
continue;
}
// before we do anything else, check if this virtual server already has a key on it.
// if it does then do not do anything to it. Continue on to the next one
// we don't actually need to load any data for this to work, so we can call GetData
// with a size of zero as if we are querying for the size. If that succeedes, then
// we know that it is there and can continue on
dwAttr = 0; // do not inherit
dwUType = IIS_MD_UT_SERVER;
dwDType = BINARY_METADATA;
dwLength = 0;
cmdW3SVC.GetData(MD_SSL_CERT_HASH,
&dwAttr,
&dwUType,
&dwDType,
&dwLength,
NULL,
0,
0, // do not inherit
IIS_MD_UT_SERVER,
BINARY_METADATA,
(PWCHAR)(LPCTSTR)csNodeName);
// if there is a key there already - continue to the next node
if ( dwLength > 0 )
{
continue;
}
// this is a valid virtual server with no pre-existing key. Now we need to load
// the bindings and see if we have a match
dwAttr = 0; // do not inherit
dwUType = IIS_MD_UT_SERVER;
dwDType = MULTISZ_METADATA;
dwLength = 0;
// The bindings are in a multi-sz. So, first we need to figure out how much space we need
f = cmdW3SVC.GetData( MD_SECURE_BINDINGS,
&dwAttr,
&dwUType,
&dwDType,
&dwLength,
NULL,
0,
0, // do not inherit
IIS_MD_UT_SERVER,
MULTISZ_METADATA,
(PWCHAR)(LPCTSTR)csNodeName);
// if the length is zero, then there are no bindings
if ( dwLength == 0 )
{
continue;
}
// Prepare some space to receive the bindings
TCHAR* pBindings;
// if pData is pointing to something, then we need to free it so that we don't leak
if ( pData )
{
GlobalFree( pData );
pData = NULL;
}
// allocate the space, if it fails, we fail
// note that GPTR causes it to be initialized to zero
pData = GlobalAlloc( GPTR, dwLength + 2 );
if ( !pData )
{
iisDebugOut((LOG_TYPE_ERROR, _T("%s GlobalAlloc failed."),csNodeName));
continue;
}
pBindings = (TCHAR*)pData;
// now get the real data from the metabase
f = cmdW3SVC.GetData( MD_SECURE_BINDINGS,
&dwAttr,
&dwUType,
&dwDType,
&dwLength,
(PUCHAR)pBindings,
dwLength,
0, // do not inherit
IIS_MD_UT_SERVER,
MULTISZ_METADATA,
(PWCHAR)(LPCTSTR)csNodeName );
// if we did not get the bindings, then this node doesn't have any security
// options set on it. We can continue on to the next virtual server
if ( FALSE == f )
{
continue;
}
// OK. We do have bindings. Now we get to parse them out and check them
// against the binding strings that were passed in. Note: if a binding
// matches, but has a host-header at the end, then it does not qualify
// got the existing bindings, scan them now - pBindings will be pointing at the second end \0
// when it is time to exit the loop.
while ( *pBindings )
{
csBinding = pBindings;
csBinding.TrimRight();
// We are looking for a particular port which is stored in csPort.
// if there is no csPort passed in then lets get out of here!
if ( csPort.IsEmpty() )
{
break;
}
else
{
// if the Port is not in the binding then bail on this binding
if ( csBinding.Find( csPort ) < 0 )
{
iisDebugOut((LOG_TYPE_TRACE, _T("%s:org=%s,findport=%s bail."),csNodeName,csBinding,csPort));
goto NextBinding;
}
}
// test if host headers are there by doing a reverse find for the last colon. Then
// check if it is the last character. If it isn't, then there is a host-header and
// we should go to a different binding
if ( csBinding.ReverseFind(_T(':')) < (csBinding.GetLength()-1) )
{
iisDebugOut((LOG_TYPE_TRACE, _T("%s:bail2."),csNodeName));
goto NextBinding;
}
// Well, this is a valid binding on a valid virtual server, we can now write out the key
iisDebugOut((LOG_TYPE_TRACE, _T("%s:Write out the key!"),csNodeName));
WriteKeyReference( cmdW3SVC, (PWCHAR)(LPCTSTR)csNodeName, pCert );
// we can break to get out of the specific bindings loop
break;
NextBinding:
// increment pBindings to the next string
pBindings = _tcsninc( pBindings, _tcslen(pBindings))+1;
}
}
// if pData is pointing to something, then we need to free it so that we don't leak
if ( pData )
{
GlobalFree( pData );
pData = NULL;
}
StoreKeyReference_Default_Exit:
iisDebugOut_End(_T("StoreKeyReference_Default"), LOG_TYPE_TRACE);
}
#endif //_CHICAGO_