windows-nt/Source/XPSP1/NT/base/win32/fusion/sxs/strongname.cpp

787 lines
20 KiB
C++
Raw Permalink Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
strongname.cpp
Abstract:
Fusion Win32 implementation of the Fusion URT strong-name stuff
Author:
Jon Wiswall (jonwis) 11-October-2000
Revision History:
jonwis/7-November-2000: Added ability to find a strong name from a
certificate context structure, as well as the ability to scan
a catalog for strong names. Also changed the way strong names
are generated from a public key to be identically in-line with
Fusion URT.
--*/
#include "stdinc.h"
#include "wincrypt.h"
#include "fusionbuffer.h"
#include "strongname.h"
#include "hashfile.h"
#include "Sxsp.h"
BOOL
SxspHashStringToBytes(
PCWSTR hsHashString,
SIZE_T cchHashString,
CFusionArray<BYTE> &OutputBuffer
)
{
//
// 2 here is not sizeof(WCHAR) it is how many digits a byte takes to print (or be scanned from,
// as is actually happening here)
//
DWORD dwByteCount = static_cast<DWORD>(cchHashString) / 2;
DWORD dwIdx = 0;
int iHi, iLo;
BOOL bSuccess = FALSE;
FN_TRACE_WIN32(bSuccess);
//
// We look on odd numbers with disdain.
//
PARAMETER_CHECK((cchHashString % 2) == 0);
if ( OutputBuffer.GetSize() != dwByteCount )
{
IFW32FALSE_EXIT(OutputBuffer.Win32SetSize(dwByteCount, CFusionArray<BYTE>::eSetSizeModeExact));
}
//
// Sneak through the list of characters and turn them into the
// hi and lo nibbles per byte position, then write them into the
// output buffer.
//
for (DWORD i = 0; (i < cchHashString) && (dwIdx < OutputBuffer.GetSize()); i += 2)
{
if (((iHi = SxspHexDigitToValue(hsHashString[i])) >= 0) &&
((iLo = SxspHexDigitToValue(hsHashString[i+1])) >= 0))
{
OutputBuffer[dwIdx++] = static_cast<BYTE>(((iHi & 0xF) << 4) | (iLo & 0xF));
}
else
{
// Something bad happened while trying to read from the string,
// maybe it contained invalid values?
goto Exit;
}
}
bSuccess = TRUE;
Exit:
return bSuccess;
}
inline VOID
pReverseByteString(PBYTE pbBytes, SIZE_T cbBytes)
{
SIZE_T index = 0;
if (cbBytes-- == 0) return;
while (index < cbBytes)
{
BYTE bLeft = pbBytes[index];
BYTE bRight = pbBytes[cbBytes];
pbBytes[index++] = bRight;
pbBytes[cbBytes--] = bLeft;
}
}
BOOL
SxspHashBytesToString(
IN const BYTE* pbSource,
IN SIZE_T cbSource,
OUT CBaseStringBuffer &sbDestination
)
{
BOOL bSuccess = FALSE;
DWORD i;
PWSTR pwsCursor;
static WCHAR HexCharList[] = L"0123456789abcdef";
CStringBufferAccessor Accessor;
FN_TRACE_WIN32(bSuccess);
sbDestination.Clear();
IFW32FALSE_EXIT(sbDestination.Win32ResizeBuffer((cbSource + 1) * 2, eDoNotPreserveBufferContents));
Accessor.Attach(&sbDestination);
pwsCursor = Accessor;
for (i = 0; i < cbSource; i++)
{
pwsCursor[i*2] = HexCharList[ (pbSource[i] >> 4) & 0x0F ];
pwsCursor[i*2+1] = HexCharList[ pbSource[i] & 0x0F ];
}
//
// Because of the way string accessors and clear works, we have to clip off
// the rest by a null character. Odd, but it works.
//
pwsCursor[i*2] = L'\0';
bSuccess = TRUE;
Exit:
return bSuccess;
}
BOOL
SxspGetStrongNameOfKey(
IN const CFusionArray<BYTE> &PublicKeyBits,
OUT CFusionArray<BYTE> &StrongNameBits
)
/*++
Note to posterity:
This implementation has been blessed by the Fusion URT people to be identically
in synch with their implementation. Do _not_ change anything here unless you're
really sure there's a bug or there's a change in spec. The basic operation of this
is as follows:
- Get crypto provider
- Create a SHA1 hash object from the crypto stuff
- Hash the data
- Extract the hash data into the output buffer
- Move the low order 8-bytes of the hash (bytes 11 through 19) down to 0-7
- Reverse the bytes to obtain a "network ordered" 64-bit string
The last two steps are the important thing - work with Rudi Martin (Fusion URT)
if you think there's a better way.
--*/
{
FN_PROLOG_WIN32
CFusionHash hHash;
PSXS_PUBLIC_KEY_INFO pPubKeyInfo;
PARAMETER_CHECK(PublicKeyBits.GetSize() >= sizeof(*pPubKeyInfo));
//
// Convert our pointer back for a second - it's a persisted version of this
// structure anyhow.
//
pPubKeyInfo = (PSXS_PUBLIC_KEY_INFO)PublicKeyBits.GetArrayPtr();
//
// Make ourselves a hash object.
//
IFW32FALSE_EXIT(hHash.Win32Initialize(CALG_SHA1));
//
// Hash the actual data that we were passed in to generate the strong name.
//
IFW32FALSE_EXIT(
hHash.Win32HashData(
PublicKeyBits.GetArrayPtr(),
PublicKeyBits.GetSize()));
//
// Find out how big the hash data really is from what was hashed.
//
IFW32FALSE_EXIT(hHash.Win32GetValue(StrongNameBits));
//
// Move the last eight bytes of the hash downwards using memmove, because
// it knows about things like overlapping blocks.
//
PBYTE pbBits = static_cast<PBYTE>(StrongNameBits.GetArrayPtr());
::RtlMoveMemory(
pbBits,
pbBits + (StrongNameBits.GetSize() - STRONG_NAME_BYTE_LENGTH),
STRONG_NAME_BYTE_LENGTH);
pReverseByteString(pbBits, STRONG_NAME_BYTE_LENGTH);
IFW32FALSE_EXIT(StrongNameBits.Win32SetSize(STRONG_NAME_BYTE_LENGTH, CFusionArray<BYTE>::eSetSizeModeExact));
FN_EPILOG
}
BOOL
SxspDoesStrongNameMatchKey(
IN const CBaseStringBuffer &rbuffKeyString,
IN const CBaseStringBuffer &rbuffStrongNameString,
OUT BOOL &rfKeyMatchesStrongName
)
{
FN_PROLOG_WIN32
CSmallStringBuffer buffStrongNameCandidate;
PARAMETER_CHECK(::SxspIsFullHexString(rbuffKeyString, rbuffKeyString.Cch()));
PARAMETER_CHECK(::SxspIsFullHexString(rbuffStrongNameString, rbuffStrongNameString.Cch()));
//
// Convert the key over to its corresponding strong name
//
IFW32FALSE_EXIT(::SxspGetStrongNameOfKey(rbuffKeyString, buffStrongNameCandidate));
//
// And compare what the caller thinks it should be.
//
rfKeyMatchesStrongName = (::FusionpCompareStrings(
rbuffStrongNameString,
rbuffStrongNameString.Cch(),
buffStrongNameCandidate,
buffStrongNameCandidate.Cch(),
false) == 0);
FN_EPILOG
}
BOOL
SxspGetStrongNameOfKey(
IN const CBaseStringBuffer &rbuffKeyString,
OUT CBaseStringBuffer &sbStrongName
)
{
CFusionArray<BYTE> KeyBytes, StrongNameBytes;
BOOL bSuccess = FALSE;
FN_TRACE_WIN32(bSuccess);
//
// Convert the string to bytes, generate the strong name, convert back to
// a string.
//
IFW32FALSE_EXIT(::SxspHashStringToBytes(rbuffKeyString, rbuffKeyString.Cch(), KeyBytes));
IFW32FALSE_EXIT(::SxspGetStrongNameOfKey(KeyBytes, StrongNameBytes));
IFW32FALSE_EXIT(::SxspHashBytesToString(StrongNameBytes.GetArrayPtr(), StrongNameBytes.GetSize(), sbStrongName));
bSuccess = TRUE;
Exit:
return bSuccess;
}
BOOL
SxspAcquireStrongNameFromCertContext(
CBaseStringBuffer &rbuffStrongNameString,
CBaseStringBuffer &sbPublicKeyString,
PCCERT_CONTEXT pCertContext
)
/*++
Note to posterity:
This is the other "black magic" of the strong-name stuff. Fusion URT takes whatever
CryptExportKey blops out, tacks on a magic header of their design (which I have
copied into SXS_PUBLIC_KEY_INFO), then hashes the whole thing. This routine knows
how to interact with a pCertContext object (like one you'd get from a certificate
file or by walking through a catalog) and turn the certificate into a strong name
and public key blob. The public key blob is returned in a hex string, and can
be converted back to bytes (for whatever purpose) via SxspHashStringToBytes.
Don't change anything you see below, unless there's a bug or there's been a spec
change. If you've got problems with this file, please notify Jon Wiswall (jonwis)
and he'll be able to better help you with debugging or whatnot.
--*/
{
BOOL bSuccess = FALSE;
HCRYPTPROV hCryptProv = NULL;
HCRYPTHASH hCryptHash = NULL;
HCRYPTKEY hCryptKey = NULL;
BYTE bKeyInfo[2048] = { 0 };
PSXS_PUBLIC_KEY_INFO pKeyWrapping = reinterpret_cast<PSXS_PUBLIC_KEY_INFO>(bKeyInfo);
DWORD dwDump;
CFusionArray<BYTE> bPublicKeyContainer;
CFusionArray<BYTE> bStrongNameContainer;
FN_TRACE_WIN32(bSuccess);
rbuffStrongNameString.Clear();
sbPublicKeyString.Clear();
//
// Get a crypto context that only does RSA verification - ie, doesn't use private keys
//
IFW32FALSE_EXIT(::SxspAcquireGlobalCryptContext(&hCryptProv));
//
// Take the public key info that we found on this certificate context and blop it back
// into a real internal crypto key.
//
IFW32FALSE_ORIGINATE_AND_EXIT(
::CryptImportPublicKeyInfoEx(
hCryptProv,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
&(pCertContext->pCertInfo->SubjectPublicKeyInfo),
CALG_RSA_SIGN,
0,
NULL,
&hCryptKey));
//
// The stuff we swizzle will be about 200 bytes, so this is serious overkill
// until such time as people start using 16384-bit keys.
//
pKeyWrapping->KeyLength =
sizeof(bKeyInfo) - offsetof(SXS_PUBLIC_KEY_INFO, pbKeyInfo);
//
// Extract the key data from the crypto key back into a byte stream. This seems to
// be what the fusion-urt people do, in order to get a byte string to hash.
//
IFW32FALSE_ORIGINATE_AND_EXIT(
CryptExportKey(
hCryptKey,
NULL,
PUBLICKEYBLOB,
0,
pKeyWrapping->pbKeyInfo,
&pKeyWrapping->KeyLength));
//
// Sacred values from the fusion-urt people
//
pKeyWrapping->SigAlgID = CALG_RSA_SIGN;
pKeyWrapping->HashAlgID = CALG_SHA1;
dwDump = pKeyWrapping->KeyLength + offsetof(SXS_PUBLIC_KEY_INFO, pbKeyInfo);
IFW32FALSE_EXIT(
::SxspHashBytesToString(
reinterpret_cast<const BYTE*>(pKeyWrapping),
dwDump,
sbPublicKeyString));
IFW32FALSE_EXIT(bPublicKeyContainer.Win32Assign(dwDump, bKeyInfo));
IFW32FALSE_EXIT(
::SxspGetStrongNameOfKey(
bPublicKeyContainer,
bStrongNameContainer));
INTERNAL_ERROR_CHECK(bStrongNameContainer.GetSize() == STRONG_NAME_BYTE_LENGTH);
//
// Great - this is the official strong name of the 2000 Fusolympics.
//
IFW32FALSE_EXIT(
::SxspHashBytesToString(
bStrongNameContainer.GetArrayPtr(),
STRONG_NAME_BYTE_LENGTH,
rbuffStrongNameString));
bSuccess = TRUE;
Exit:
if (hCryptKey != NULL) CryptDestroyKey(hCryptKey);
if (hCryptHash != NULL) CryptDestroyHash(hCryptHash);
return bSuccess;
}
inline BOOL
SxspAreStrongNamesAllowedToNotMatchCatalogs(BOOL &bAllowed)
{
//
// This function is our back-door past the strong-name system while
// Whistler is still in beta/rtm. The test certificate, if installed,
// indicates that it's ok to let strong names not match catalogs.
//
// The certificate data here is from \nt\admin\ntsetup\syssetup\crypto.c in
// SetupAddOrRemoveTestCertificate. Please ensure that this gets updated.
//
BOOL fSuccess = FALSE;
FN_TRACE_WIN32(fSuccess);
CRYPT_HASH_BLOB HashBlob;
HCERTSTORE hSystemStore = NULL;
PCCERT_CONTEXT pCertContext = NULL;
BYTE bTestRootHashList[][20] = {
{0x2B, 0xD6, 0x3D, 0x28, 0xD7, 0xBC, 0xD0, 0xE2, 0x51, 0x19, 0x5A, 0xEB, 0x51, 0x92, 0x43, 0xC1, 0x31, 0x42, 0xEB, 0xC3}
};
bAllowed = FALSE;
//
// Cause the root store to be opened on the local machine.
//
IFW32NULL_ORIGINATE_AND_EXIT(
hSystemStore = ::CertOpenStore(
CERT_STORE_PROV_SYSTEM,
X509_ASN_ENCODING,
(HCRYPTPROV)NULL,
CERT_SYSTEM_STORE_LOCAL_MACHINE,
L"ROOT"));
for (int i = 0; i < NUMBER_OF(bTestRootHashList); i++)
{
bool fNotFound;
HashBlob.cbData = sizeof(bTestRootHashList[i]);
HashBlob.pbData = bTestRootHashList[i];
IFW32NULL_ORIGINATE_AND_EXIT_UNLESS2(
pCertContext = ::CertFindCertificateInStore(
hSystemStore,
X509_ASN_ENCODING,
0,
CERT_FIND_HASH,
&HashBlob,
NULL),
LIST_1(static_cast<DWORD>(CRYPT_E_NOT_FOUND)),
fNotFound);
if (pCertContext != NULL)
{
bAllowed = TRUE;
break;
}
}
if (!bAllowed)
{
::FusionpDbgPrintEx(
FUSION_DBG_LEVEL_INFO | FUSION_DBG_LEVEL_INSTALLATION,
"SXS: %s - no test certificate installed on machine\n",
__FUNCTION__);
}
fSuccess = TRUE;
Exit:
CSxsPreserveLastError ple;
if (pCertContext) ::CertFreeCertificateContext(pCertContext);
if (hSystemStore) ::CertCloseStore(hSystemStore, CERT_CLOSE_STORE_FORCE_FLAG);
ple.Restore();
return fSuccess;
}
CPublicKeyInformation::CPublicKeyInformation()
: m_fInitialized(false)
{
}
CPublicKeyInformation::~CPublicKeyInformation()
{
}
BOOL
CPublicKeyInformation::GetStrongNameBytes(
OUT CFusionArray<BYTE> & cbStrongNameBytes
) const
{
FN_PROLOG_WIN32
INTERNAL_ERROR_CHECK(m_fInitialized);
IFW32FALSE_EXIT(m_StrongNameBytes.Win32Clone(cbStrongNameBytes));
FN_EPILOG
}
BOOL
CPublicKeyInformation::GetStrongNameString(
OUT CBaseStringBuffer &rbuffStrongNameString
) const
{
FN_PROLOG_WIN32
rbuffStrongNameString.Clear();
INTERNAL_ERROR_CHECK(m_fInitialized);
IFW32FALSE_EXIT(rbuffStrongNameString.Win32Assign(m_StrongNameString));
FN_EPILOG
}
BOOL
CPublicKeyInformation::GetPublicKeyBitLength(
OUT ULONG &ulKeyLength
) const
{
BOOL fSuccess = FALSE;
BOOL fLieAboutPublicKeyBitLength = FALSE;
FN_TRACE_WIN32(fSuccess);
ulKeyLength = 0;
INTERNAL_ERROR_CHECK(m_fInitialized);
IFW32FALSE_EXIT(::SxspAreStrongNamesAllowedToNotMatchCatalogs(fLieAboutPublicKeyBitLength));
if (fLieAboutPublicKeyBitLength)
{
#if DBG
FusionpDbgPrintEx(
FUSION_DBG_LEVEL_WFP | FUSION_DBG_LEVEL_INFO,
"SXS: %s() - Lying about key length because we're still in test mode (%lu actual, %lu spoofed.)\n",
__FUNCTION__,
m_KeyLength,
SXS_MINIMAL_SIGNING_KEY_LENGTH);
#endif
ulKeyLength = SXS_MINIMAL_SIGNING_KEY_LENGTH;
}
else
{
ulKeyLength = m_KeyLength;
}
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
CPublicKeyInformation::GetWrappedPublicKeyBytes(
OUT CFusionArray<BYTE> &bPublicKeybytes
) const
{
FN_PROLOG_WIN32
INTERNAL_ERROR_CHECK(m_fInitialized);
IFW32FALSE_EXIT(m_PublicKeyBytes.Win32Clone(bPublicKeybytes));
FN_EPILOG
}
BOOL
CPublicKeyInformation::Initialize(
IN const CBaseStringBuffer &rsbCatalogFile
)
{
BOOL fSuccess = FALSE;
CFusionFile CatalogFile;
FN_TRACE_WIN32(fSuccess);
IFW32FALSE_EXIT(m_CatalogSourceFileName.Win32Assign(rsbCatalogFile));
IFW32FALSE_EXIT(
CatalogFile.Win32CreateFile(
rsbCatalogFile,
GENERIC_READ,
FILE_SHARE_READ,
OPEN_EXISTING));
IFW32FALSE_EXIT(this->Initialize(CatalogFile));
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
CPublicKeyInformation::Initialize(
IN PCWSTR pszCatalogFile
)
{
BOOL fSuccess = FALSE;
CFusionFile CatalogFile;
FN_TRACE_WIN32(fSuccess);
IFW32FALSE_EXIT(m_CatalogSourceFileName.Win32Assign(pszCatalogFile, wcslen(pszCatalogFile)));
IFW32FALSE_EXIT(
CatalogFile.Win32CreateFile(
pszCatalogFile,
GENERIC_READ,
FILE_SHARE_READ,
OPEN_EXISTING));
IFW32FALSE_EXIT(this->Initialize(CatalogFile));
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
CPublicKeyInformation::Initialize(
IN CFusionFile& CatalogFileHandle
)
{
BOOL fSuccess = FALSE;
CFileMapping FileMapping;
CMappedViewOfFile MappedFileView;
ULONGLONG cbCatalogFile = 0;
HCERTSTORE hTempStore = NULL;
PCCERT_CONTEXT pSignerContext = NULL;
PCCTL_CONTEXT pContext = NULL;
FN_TRACE_WIN32(fSuccess);
PARAMETER_CHECK(CatalogFileHandle != INVALID_HANDLE_VALUE);
IFW32FALSE_EXIT(CatalogFileHandle.Win32GetSize(cbCatalogFile));
IFW32FALSE_EXIT(FileMapping.Win32CreateFileMapping(CatalogFileHandle, PAGE_READONLY, cbCatalogFile, NULL));
IFW32FALSE_EXIT(MappedFileView.Win32MapViewOfFile(FileMapping, FILE_MAP_READ, 0, (SIZE_T)cbCatalogFile));
IFW32NULL_EXIT(pContext = (PCCTL_CONTEXT)CertCreateCTLContext(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
static_cast<const BYTE*>(static_cast<void*>(MappedFileView)),
static_cast<DWORD>(cbCatalogFile)));
hTempStore = pContext->hCertStore;
IFW32FALSE_ORIGINATE_AND_EXIT(::CryptMsgGetAndVerifySigner(
pContext->hCryptMsg,
1,
&hTempStore,
0,
&pSignerContext,
NULL));
// BUGBUG
IFW32NULL_EXIT(pSignerContext);
IFW32FALSE_EXIT(this->Initialize(pSignerContext));
fSuccess = TRUE;
Exit:
return fSuccess;
}
BOOL
CPublicKeyInformation::Initialize(IN PCCERT_CONTEXT pCertContext)
{
BOOL fSuccess = FALSE;
DWORD dwNameStringLength;
CStringBufferAccessor Access;
FN_TRACE_WIN32(fSuccess);
PARAMETER_CHECK(pCertContext != NULL);
IFW32FALSE_EXIT(
::SxspAcquireStrongNameFromCertContext(
m_StrongNameString,
m_PublicKeyByteString,
pCertContext));
IFW32FALSE_EXIT(SxspHashStringToBytes(m_StrongNameString, m_StrongNameString.Cch(), m_StrongNameBytes));
IFW32FALSE_EXIT(SxspHashStringToBytes(m_PublicKeyByteString, m_PublicKeyByteString.Cch(), m_PublicKeyBytes));
IFW32ZERO_EXIT(m_KeyLength = CertGetPublicKeyLength(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
&pCertContext->pCertInfo->SubjectPublicKeyInfo));
Access.Attach(&m_SignerDisplayName);
dwNameStringLength = ::CertGetNameStringW(
pCertContext,
CERT_NAME_FRIENDLY_DISPLAY_TYPE,
0,
NULL,
Access.GetBufferPtr(),
static_cast<DWORD>(Access.GetBufferCch()));
if (dwNameStringLength == 0)
{
TRACE_WIN32_FAILURE_ORIGINATION(CertGetNameString);
goto Exit;
}
if (dwNameStringLength > Access.GetBufferCch())
{
Access.Detach();
IFW32FALSE_EXIT(m_SignerDisplayName.Win32ResizeBuffer(dwNameStringLength, eDoNotPreserveBufferContents));
Access.Attach(&m_SignerDisplayName);
dwNameStringLength = ::CertGetNameStringW(
pCertContext,
CERT_NAME_FRIENDLY_DISPLAY_TYPE,
0,
NULL,
Access.GetBufferPtr(),
static_cast<DWORD>(Access.GetBufferCch()));
}
Access.Detach();
m_fInitialized = true;
fSuccess = TRUE;
Exit:
{
CSxsPreserveLastError ple;
if (pCertContext != NULL)
::CertFreeCertificateContext(pCertContext);
ple.Restore();
}
return fSuccess;
}
BOOL
CPublicKeyInformation::GetSignerNiceName(
OUT CBaseStringBuffer &rbuffName
)
{
FN_PROLOG_WIN32
INTERNAL_ERROR_CHECK(m_fInitialized);
IFW32FALSE_EXIT(rbuffName.Win32Assign(m_SignerDisplayName));
FN_EPILOG
}
BOOL
CPublicKeyInformation::DoesStrongNameMatchSigner(
IN const CBaseStringBuffer &rbuffTestStrongName,
OUT BOOL &rfStrongNameMatchesSigner
) const
{
BOOL fSuccess = FALSE;
BOOL fCanStrongNameMismatch = FALSE;
FN_TRACE_WIN32(fSuccess);
rfStrongNameMatchesSigner = (::FusionpCompareStrings(
rbuffTestStrongName,
rbuffTestStrongName.Cch(),
m_StrongNameString,
m_StrongNameString.Cch(),
false) == 0);
if (!rfStrongNameMatchesSigner)
{
IFW32FALSE_EXIT(::SxspAreStrongNamesAllowedToNotMatchCatalogs(fCanStrongNameMismatch));
if (fCanStrongNameMismatch)
{
::FusionpDbgPrintEx(
FUSION_DBG_LEVEL_WFP,
"SXS.DLL: %s - !!notice!! Strong name %ls not in catalog %ls, test code allows this\n"
" Please make sure that you have tested with realsigned catalogs.\n",
__FUNCTION__,
static_cast<PCWSTR>(rbuffTestStrongName),
static_cast<PCWSTR>(m_CatalogSourceFileName));
rfStrongNameMatchesSigner = TRUE;
}
}
FN_EPILOG
}