1430 lines
40 KiB
C++
1430 lines
40 KiB
C++
/*++
|
||
|
||
Copyright (C) Microsoft Corporation, 1998 - 1999
|
||
|
||
Module Name:
|
||
|
||
|
||
|
||
Abstract:
|
||
|
||
Functions to manipulate SSL-tyle principal names and certificates
|
||
|
||
Author:
|
||
|
||
Jeff Roberts
|
||
|
||
Revisions:
|
||
|
||
Jeff Roberts (jroberts) 1-20-1998
|
||
|
||
created the file
|
||
|
||
--*/
|
||
|
||
#include <precomp.hxx>
|
||
|
||
#include <rpcssl.h>
|
||
#include <cryptimp.hxx>
|
||
#include <CharConv.hxx>
|
||
|
||
#define INITIAL_NAME_LENGTH 100
|
||
|
||
// The prefix lengths do not include the NULL char. Actual strings are one
|
||
// character longer. This is generally OK, since these strings are prefixes and
|
||
// the NULL char gets overwritten.
|
||
|
||
#define MSSTD_PREFIX_LENGTH 6
|
||
const RPC_CHAR MSSTD_PREFIX[] = RPC_T("msstd:");
|
||
|
||
#define FULLPATH_PREFIX_LENGTH 8
|
||
const RPC_CHAR FULLPATH_PREFIX[] = RPC_T("fullsic:");
|
||
|
||
//------------------------------------------------------------------------
|
||
|
||
DWORD
|
||
AddComponentName( RPC_CHAR * * pBuffer,
|
||
unsigned long * pBufferLength,
|
||
unsigned long * pCursor,
|
||
CERT_NAME_BLOB * Name
|
||
);
|
||
|
||
DWORD
|
||
RpcCertMatchPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR PrincipalName[]
|
||
);
|
||
|
||
DWORD
|
||
MatchFullPathPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR EncodedPrincipalName[]
|
||
);
|
||
|
||
DWORD
|
||
CompareRdnElement(
|
||
PCCERT_CONTEXT Context,
|
||
DWORD dwGetNameStringType,
|
||
void *pvGetNameStringTypePara,
|
||
RPC_CHAR PrincipalName[],
|
||
BOOL CaseSensitive
|
||
);
|
||
|
||
DWORD
|
||
MarkPrincipalNameComponents(
|
||
RPC_CHAR * PrincipalName,
|
||
unsigned * pCount
|
||
);
|
||
|
||
PCCERT_CONTEXT
|
||
FindMatchingCertificate( HCERTSTORE Store,
|
||
RPC_CHAR * SubjectName,
|
||
RPC_CHAR * IssuerName
|
||
);
|
||
|
||
DWORD
|
||
MatchMsPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR EncodedPrincipalName[]
|
||
);
|
||
|
||
DWORD
|
||
DecodeEscapedString(
|
||
RPC_CHAR * Source,
|
||
RPC_CHAR * Destination
|
||
);
|
||
|
||
DWORD
|
||
FindIssuerContext( PCCERT_CONTEXT * pContext,
|
||
HCERTSTORE * pStore,
|
||
BOOL * pfFreeStore
|
||
);
|
||
|
||
RPC_CHAR *
|
||
EndOfRfc1779Name(
|
||
RPC_CHAR * Name
|
||
);
|
||
|
||
unsigned
|
||
Int4StrLen(
|
||
unsigned long * String
|
||
);
|
||
|
||
//------------------------------------------------------------------------
|
||
|
||
RPC_CHAR * __cdecl RpcpStringReverse (
|
||
RPC_CHAR * string
|
||
)
|
||
{
|
||
RPC_CHAR *start = string;
|
||
RPC_CHAR *left = string;
|
||
RPC_CHAR ch;
|
||
|
||
while (*string++) /* find end of string */
|
||
;
|
||
string -= sizeof(RPC_CHAR);
|
||
|
||
while (left < string)
|
||
{
|
||
ch = *left;
|
||
*left++ = *string;
|
||
*string-- = ch;
|
||
}
|
||
|
||
return(start);
|
||
}
|
||
|
||
|
||
DWORD
|
||
RpcCertMatchPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR PrincipalName[]
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine
|
||
|
||
Arguments:
|
||
|
||
<Context> is a CryptoAPI 2.0 context
|
||
|
||
<PrincipalName> is a principal name prefixed with either "msstd:" or "fullsic:"
|
||
|
||
Return Value:
|
||
|
||
0 if successful, otherwise an error
|
||
|
||
--*/
|
||
{
|
||
DWORD Status = 0;
|
||
|
||
InitializeIfNecessary();
|
||
|
||
if (!LoadCrypt32Imports())
|
||
{
|
||
return GetLastError();
|
||
}
|
||
|
||
if (0 == RpcpStringNCompare(PrincipalName, MSSTD_PREFIX, MSSTD_PREFIX_LENGTH))
|
||
{
|
||
Status = MatchMsPrincipalName(Context, PrincipalName + MSSTD_PREFIX_LENGTH);
|
||
}
|
||
else if (0 == RpcpStringNCompare(PrincipalName, FULLPATH_PREFIX, FULLPATH_PREFIX_LENGTH))
|
||
{
|
||
Status = MatchFullPathPrincipalName(Context, PrincipalName + FULLPATH_PREFIX_LENGTH);
|
||
}
|
||
else
|
||
{
|
||
ASSERT( 0 && "RPC: principal name is in incorrect format" );
|
||
Status = ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
DWORD
|
||
MatchMsPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR EncodedPrincipalName[]
|
||
)
|
||
{
|
||
PCERT_NAME_INFO Subject = 0;
|
||
DWORD Status = 0;
|
||
RPC_CHAR * PrincipalName = 0;
|
||
|
||
//
|
||
// It's time to decode the principal name.
|
||
// The encoding is just a matter of extra backslashes when the
|
||
// principal name contains our metacharacters. Decoding will
|
||
// always make the string smaller.
|
||
//
|
||
PrincipalName = (RPC_CHAR *) _alloca(sizeof(RPC_CHAR) * RpcpStringLength(EncodedPrincipalName));
|
||
|
||
Status = DecodeEscapedString(EncodedPrincipalName, PrincipalName);
|
||
if (Status)
|
||
{
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
Status,
|
||
EEInfoDLMatchMsPrincipalName10);
|
||
|
||
return Status;
|
||
}
|
||
|
||
|
||
if (RpcpCharacter((const RPC_SCHAR *) PrincipalName, '@'))
|
||
{
|
||
// Compare the principal name to the certificate subject's
|
||
// email-address RDN attribute.
|
||
return CompareRdnElement(Context, CERT_NAME_EMAIL_TYPE, NULL, PrincipalName, TRUE);
|
||
}
|
||
else
|
||
{
|
||
// Compare the principal name to the certificate subject's
|
||
// common-name RDN attribute.
|
||
return CompareRdnElement(Context, CERT_NAME_ATTR_TYPE, szOID_COMMON_NAME, PrincipalName, FALSE);
|
||
}
|
||
|
||
ASSERT( 0 );
|
||
}
|
||
|
||
|
||
DWORD
|
||
CompareRdnElement(
|
||
PCCERT_CONTEXT Context,
|
||
DWORD dwGetNameStringType,
|
||
void *pvGetNameStringTypePara,
|
||
RPC_CHAR PrincipalName[],
|
||
BOOL CaseSensitive
|
||
)
|
||
{
|
||
//
|
||
// Decode the certificate's Subject field so we can see its attributes.
|
||
//
|
||
DWORD Status = 0;
|
||
|
||
RPC_CHAR * pPrincipalName;
|
||
|
||
DWORD NameSize = CertGetNameStringW(Context,
|
||
dwGetNameStringType,
|
||
0, // dwFlags
|
||
pvGetNameStringTypePara, // pvTypePara
|
||
NULL,
|
||
0);
|
||
|
||
if (NameSize > 1)
|
||
{
|
||
pPrincipalName = (RPC_CHAR *) _alloca( sizeof(wchar_t) * NameSize );
|
||
|
||
NameSize = CertGetNameStringW(Context,
|
||
dwGetNameStringType,
|
||
0,
|
||
pvGetNameStringTypePara,
|
||
pPrincipalName,
|
||
NameSize);
|
||
|
||
ASSERT(NameSize > 1);
|
||
}
|
||
else
|
||
{
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
ERROR_ACCESS_DENIED,
|
||
EEInfoDLCompareRdnElement10,
|
||
GetLastError());
|
||
|
||
return ERROR_ACCESS_DENIED;
|
||
}
|
||
|
||
//
|
||
// Now compare the cetificate info to the principal name.
|
||
//
|
||
if (CaseSensitive)
|
||
{
|
||
if (0 != RpcpStringSCompare(PrincipalName, pPrincipalName))
|
||
{
|
||
return ERROR_ACCESS_DENIED;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (0 != RpcpStringCompareInt(PrincipalName, pPrincipalName))
|
||
{
|
||
return ERROR_ACCESS_DENIED;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
DWORD
|
||
DecodeEscapedString(
|
||
RPC_CHAR * Source,
|
||
RPC_CHAR * Destination
|
||
)
|
||
{
|
||
for (;;)
|
||
{
|
||
if (*Source == '\\')
|
||
{
|
||
if(Source[1] != '<' && Source[1] != '*')
|
||
{
|
||
Source++;
|
||
}
|
||
if (!*Source)
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
*Destination = *Source;
|
||
}
|
||
else
|
||
{
|
||
*Destination = *Source;
|
||
if (!*Source)
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
++Source;
|
||
++Destination;
|
||
}
|
||
}
|
||
|
||
|
||
DWORD
|
||
MatchFullPathPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR EncodedPrincipalName[]
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
|
||
Arguments:
|
||
|
||
<Context> is a CryptoAPI 2.0 context
|
||
|
||
<PrincipalName> is a principal name with no prefix
|
||
|
||
It can be in several forms:
|
||
|
||
- complete and explicit, e.g.
|
||
|
||
"\<CA>\<Name>"
|
||
"\<CA>\<SA1>\<SA2>\<Name>"
|
||
|
||
- wildcard in the subject-name position:
|
||
|
||
"\<CA>\*"
|
||
|
||
- no CA specified
|
||
|
||
"name" - This is equivalent to "\*\<CA>"
|
||
|
||
Return Value:
|
||
|
||
0 if all components were successfully validated
|
||
ERROR_NO_TOP_LEVEL_AUTHORITY if the top cert isn't in the CA store
|
||
|
||
Some other error if something failed.
|
||
|
||
--*/
|
||
{
|
||
DWORD Status = 0;
|
||
DWORD ReturnedStatus = 0;
|
||
HCERTSTORE Store = Context->hCertStore;
|
||
unsigned PrincipalNameLength;
|
||
RPC_CHAR * CopyOfPrincipalName = 0;
|
||
|
||
if (!*EncodedPrincipalName)
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
PrincipalNameLength = RpcpStringLength(EncodedPrincipalName);
|
||
|
||
//
|
||
// If the name doesn't begin with a backslash, then treat it as "\*\subjectname".
|
||
//
|
||
if (EncodedPrincipalName[0] != '\\')
|
||
{
|
||
CopyOfPrincipalName = (RPC_CHAR *) _alloca( (5 + PrincipalNameLength + 2) *
|
||
sizeof(RPC_CHAR));
|
||
|
||
RpcpStringCopy(CopyOfPrincipalName, RPC_CONST_STRING("\\*\\<"));
|
||
RpcpStringCat(CopyOfPrincipalName, EncodedPrincipalName);
|
||
RpcpStringCat(CopyOfPrincipalName, RPC_CONST_STRING(">"));
|
||
|
||
return MatchFullPathPrincipalName( Context, CopyOfPrincipalName );
|
||
}
|
||
|
||
//
|
||
// Form is "\<CA>\<Name>". More precisely, the name looks like a
|
||
// file system path, where each element of the path
|
||
// is either
|
||
// "*"
|
||
// or
|
||
// '<' plus an RFC1779 name plus '>'
|
||
//
|
||
unsigned idx;
|
||
unsigned ComponentCount;
|
||
RPC_CHAR ** Components = 0;
|
||
RPC_CHAR * Cursor;
|
||
|
||
CopyOfPrincipalName = (RPC_CHAR *) _alloca( (PrincipalNameLength + 2) * sizeof(RPC_CHAR));
|
||
|
||
RpcpStringCopy(CopyOfPrincipalName, EncodedPrincipalName);
|
||
|
||
//
|
||
// Sizing pass. Count the number of path elements and separate them by '\0'.
|
||
//
|
||
Status = MarkPrincipalNameComponents( CopyOfPrincipalName, &ComponentCount );
|
||
if (Status)
|
||
{
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
Status,
|
||
EEInfoDLMatchFullPathPrincipalName10);
|
||
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Init the component array. The 0'th element of the array points to
|
||
// the last component of the principal name (i.e., the client's subject name).
|
||
//
|
||
Components = (RPC_CHAR **) _alloca( sizeof(RPC_CHAR *) * ComponentCount );
|
||
|
||
Cursor = CopyOfPrincipalName;
|
||
idx = ComponentCount-1;
|
||
do
|
||
{
|
||
while (!*Cursor)
|
||
{
|
||
++Cursor;
|
||
}
|
||
|
||
ASSERT( Cursor < CopyOfPrincipalName+PrincipalNameLength );
|
||
|
||
Components[idx] = Cursor;
|
||
|
||
Cursor += RpcpStringLength( Cursor );
|
||
}
|
||
while ( idx-- > 0 );
|
||
|
||
//
|
||
// Verify the principal name of all certs except the top authority one.
|
||
//
|
||
PCCERT_CONTEXT NewContext = 0;
|
||
for (idx=0; idx < ComponentCount-1; ++idx)
|
||
{
|
||
if (NewContext)
|
||
{
|
||
CertFreeCertificateContext( NewContext );
|
||
}
|
||
|
||
NewContext = FindMatchingCertificate( Store,
|
||
Components[idx],
|
||
Components[idx+1]
|
||
);
|
||
if (!NewContext)
|
||
{
|
||
Status = GetLastError();
|
||
|
||
if (Status == ERROR_NOT_ENOUGH_MEMORY)
|
||
{
|
||
ReturnedStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
else
|
||
{
|
||
ReturnedStatus = ERROR_ACCESS_DENIED;
|
||
}
|
||
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
ReturnedStatus,
|
||
EEInfoDLMatchFullPathPrincipalName20,
|
||
Status);
|
||
|
||
return ReturnedStatus;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Look for the top-level authority certificate in the CA and ROOT stores,
|
||
// matching the last remaining principal name component.
|
||
//
|
||
HCERTSTORE CaStore;
|
||
HCERTSTORE RootStore;
|
||
|
||
CaStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
|
||
0,
|
||
0,
|
||
CERT_SYSTEM_STORE_CURRENT_USER,
|
||
RPC_CONST_STRING("CA")
|
||
);
|
||
if (!CaStore)
|
||
{
|
||
Status = GetLastError();
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
Status,
|
||
EEInfoDLMatchFullPathPrincipalName30);
|
||
|
||
CertFreeCertificateContext( NewContext );
|
||
return Status;
|
||
}
|
||
|
||
|
||
RootStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
|
||
0,
|
||
0,
|
||
CERT_SYSTEM_STORE_CURRENT_USER,
|
||
RPC_CONST_STRING("ROOT")
|
||
);
|
||
if (!RootStore)
|
||
{
|
||
Status = GetLastError();
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
Status,
|
||
EEInfoDLMatchFullPathPrincipalName40);
|
||
|
||
CertCloseStore( CaStore, 0 );
|
||
CertFreeCertificateContext( NewContext );
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Use Context instead of Components[] for the issuer name
|
||
// so that the self-signed cert that we look for
|
||
// is always the one that matches the Context we have.
|
||
// Otherwise we'd be wrong about strings with a wildcard as top-level authority.
|
||
//
|
||
PCCERT_CONTEXT Parent = 0;
|
||
while (1)
|
||
{
|
||
Parent = CertFindCertificateInStore(
|
||
RootStore,
|
||
X509_ASN_ENCODING,
|
||
0, // no flags
|
||
CERT_FIND_SUBJECT_NAME, // exact match
|
||
&NewContext->pCertInfo->Issuer,
|
||
Parent
|
||
);
|
||
if (!Parent)
|
||
{
|
||
Parent = CertFindCertificateInStore(
|
||
CaStore,
|
||
X509_ASN_ENCODING,
|
||
0, // no flags
|
||
CERT_FIND_SUBJECT_NAME, // exact match
|
||
&NewContext->pCertInfo->Issuer,
|
||
Parent
|
||
);
|
||
}
|
||
|
||
if (!Parent)
|
||
{
|
||
goto finish;
|
||
}
|
||
|
||
if (CertCompareCertificateName( X509_ASN_ENCODING,
|
||
& Parent->pCertInfo->Issuer,
|
||
&Context->pCertInfo->Issuer
|
||
))
|
||
{
|
||
goto finish;
|
||
}
|
||
}
|
||
|
||
finish:
|
||
|
||
CertCloseStore( CaStore, 0 );
|
||
CertCloseStore( RootStore, 0 );
|
||
CertFreeCertificateContext( NewContext );
|
||
|
||
if (!Parent)
|
||
{
|
||
Status = GetLastError();
|
||
if (Status == ERROR_NOT_ENOUGH_MEMORY)
|
||
{
|
||
ReturnedStatus = ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
else
|
||
{
|
||
ReturnedStatus = SEC_E_NO_AUTHENTICATING_AUTHORITY;
|
||
}
|
||
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
ReturnedStatus,
|
||
EEInfoDLMatchFullPathPrincipalName50,
|
||
Status);
|
||
|
||
return ReturnedStatus;
|
||
}
|
||
|
||
CertFreeCertificateContext( Parent );
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
PCCERT_CONTEXT
|
||
FindMatchingCertificate( HCERTSTORE Store,
|
||
RPC_CHAR * SubjectName,
|
||
RPC_CHAR * IssuerName
|
||
)
|
||
{
|
||
PCCERT_CONTEXT Context = 0;
|
||
CERT_NAME_BLOB * IssuerBlob = 0;
|
||
CERT_NAME_BLOB * SubjectBlob = 0;
|
||
|
||
ASSERT( IssuerName[0] != '*' || SubjectName[0] != '*' );
|
||
|
||
//
|
||
// Make an issuer blob, if necessary.
|
||
//
|
||
if (IssuerName[0] != '*')
|
||
{
|
||
unsigned long BlobSize = INITIAL_NAME_LENGTH;
|
||
|
||
IssuerBlob = (CERT_NAME_BLOB *) _alloca( sizeof(CERT_NAME_BLOB)+INITIAL_NAME_LENGTH );
|
||
|
||
IssuerBlob->cbData = INITIAL_NAME_LENGTH;
|
||
IssuerBlob->pbData = (unsigned char *) (IssuerBlob+1);
|
||
|
||
if (! CertStrToNameT( X509_ASN_ENCODING,
|
||
IssuerName,
|
||
0, // no format restrictions
|
||
NULL, // reserved, MBZ
|
||
IssuerBlob->pbData,
|
||
&IssuerBlob->cbData,
|
||
NULL // would point to first invalid character
|
||
))
|
||
{
|
||
BlobSize = IssuerBlob->cbData;
|
||
|
||
IssuerBlob = (CERT_NAME_BLOB *) _alloca( sizeof(CERT_NAME_BLOB)+BlobSize );
|
||
|
||
IssuerBlob->cbData = BlobSize;
|
||
IssuerBlob->pbData = (unsigned char *) (IssuerBlob+1);
|
||
|
||
if (! CertStrToNameT( X509_ASN_ENCODING,
|
||
IssuerName,
|
||
0, // no format restrictions
|
||
NULL, // reserved, MBZ
|
||
IssuerBlob->pbData,
|
||
&IssuerBlob->cbData,
|
||
NULL // would point to first invalid character
|
||
))
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Make a subject blob, if necessary.
|
||
//
|
||
if (SubjectName[0] != '*')
|
||
{
|
||
unsigned long BlobSize = INITIAL_NAME_LENGTH;
|
||
|
||
SubjectBlob = (CERT_NAME_BLOB *) _alloca( sizeof(CERT_NAME_BLOB)+INITIAL_NAME_LENGTH );
|
||
|
||
SubjectBlob->cbData = INITIAL_NAME_LENGTH;
|
||
SubjectBlob->pbData = (unsigned char *) (SubjectBlob+1);
|
||
|
||
if (! CertStrToNameT( X509_ASN_ENCODING,
|
||
SubjectName,
|
||
0, // no format restrictions
|
||
NULL, // reserved, MBZ
|
||
SubjectBlob->pbData,
|
||
&SubjectBlob->cbData,
|
||
NULL // would point to first invalid character
|
||
))
|
||
{
|
||
BlobSize = SubjectBlob->cbData;
|
||
|
||
SubjectBlob = (CERT_NAME_BLOB *) _alloca( sizeof(CERT_NAME_BLOB)+BlobSize );
|
||
|
||
SubjectBlob->cbData = BlobSize;
|
||
SubjectBlob->pbData = (unsigned char *) (SubjectBlob+1);
|
||
|
||
if (! CertStrToNameT( X509_ASN_ENCODING,
|
||
SubjectName,
|
||
0, // no format restrictions
|
||
NULL, // reserved, MBZ
|
||
SubjectBlob->pbData,
|
||
&SubjectBlob->cbData,
|
||
NULL // would point to first invalid character
|
||
))
|
||
{
|
||
return 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Search for a certificate.
|
||
//
|
||
if (SubjectName[0] == '*')
|
||
{
|
||
//
|
||
// Search by issuer only.
|
||
//
|
||
Context = CertFindCertificateInStore(
|
||
Store,
|
||
X509_ASN_ENCODING,
|
||
0, // no flags
|
||
CERT_FIND_ISSUER_NAME, // exact match
|
||
IssuerBlob,
|
||
NULL // previous context in search
|
||
);
|
||
return Context;
|
||
}
|
||
else if (IssuerName[0] == '*')
|
||
{
|
||
//
|
||
// Search by subject only.
|
||
//
|
||
Context = CertFindCertificateInStore(
|
||
Store,
|
||
X509_ASN_ENCODING,
|
||
0, // no flags
|
||
CERT_FIND_SUBJECT_NAME, // exact match
|
||
SubjectBlob,
|
||
NULL // previous context in search
|
||
);
|
||
return Context;
|
||
}
|
||
else
|
||
{
|
||
//
|
||
// Search by both. The primary search is by subject,
|
||
// on the theory that most subject names are distinct and
|
||
// most issuer names are not.
|
||
//
|
||
while (1)
|
||
{
|
||
Context = CertFindCertificateInStore(
|
||
Store,
|
||
X509_ASN_ENCODING,
|
||
0, // no flags
|
||
CERT_FIND_SUBJECT_NAME, // exact match
|
||
SubjectBlob,
|
||
NULL // previous context in search
|
||
);
|
||
if (!Context)
|
||
{
|
||
return Context;
|
||
}
|
||
|
||
if (CertCompareCertificateName( X509_ASN_ENCODING,
|
||
&Context->pCertInfo->Issuer,
|
||
IssuerBlob
|
||
))
|
||
{
|
||
return Context;
|
||
}
|
||
}
|
||
}
|
||
|
||
ASSERT( 0 && "should never get here" );
|
||
}
|
||
|
||
|
||
DWORD
|
||
MarkPrincipalNameComponents(
|
||
RPC_CHAR * PrincipalName,
|
||
unsigned * pCount
|
||
)
|
||
{
|
||
RPC_CHAR * Cursor;
|
||
unsigned ComponentCount;
|
||
|
||
ComponentCount = 0;
|
||
Cursor = PrincipalName;
|
||
do
|
||
{
|
||
++ComponentCount;
|
||
|
||
if ( *Cursor != '\\' )
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
*Cursor = '\0';
|
||
++Cursor;
|
||
|
||
if (*Cursor == '*')
|
||
{
|
||
++Cursor;
|
||
}
|
||
else if (*Cursor == '<')
|
||
{
|
||
*Cursor = '\0';
|
||
++Cursor;
|
||
|
||
Cursor = EndOfRfc1779Name( Cursor );
|
||
if (*Cursor != '>')
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
*Cursor = '\0';
|
||
++Cursor;
|
||
}
|
||
else
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
}
|
||
while ( *Cursor );
|
||
|
||
*pCount = ComponentCount;
|
||
|
||
return 0;
|
||
}
|
||
|
||
RPC_CHAR *
|
||
EndOfRfc1779Name(
|
||
RPC_CHAR * Name
|
||
)
|
||
{
|
||
unsigned Quotes = 0;
|
||
|
||
for ( ; *Name; ++Name)
|
||
{
|
||
if (*Name == '>')
|
||
{
|
||
if (0 == (Quotes % 2))
|
||
{
|
||
return Name;
|
||
}
|
||
}
|
||
else if (*Name == '\\')
|
||
{
|
||
++Name;
|
||
}
|
||
else if (*Name == '"')
|
||
{
|
||
++Quotes;
|
||
}
|
||
}
|
||
|
||
return Name;
|
||
}
|
||
|
||
|
||
RPC_STATUS
|
||
RpcCertGeneratePrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
DWORD Flags,
|
||
RPC_CHAR ** pBuffer
|
||
)
|
||
{
|
||
THREAD *Thread;
|
||
DWORD Status = 0;
|
||
|
||
InitializeIfNecessary();
|
||
|
||
Thread = ThreadSelf();
|
||
if (Thread)
|
||
{
|
||
RpcpPurgeEEInfoFromThreadIfNecessary(Thread);
|
||
}
|
||
|
||
if (!LoadCrypt32Imports())
|
||
{
|
||
return GetLastError();
|
||
}
|
||
|
||
if (Flags & RPC_C_FULL_CERT_CHAIN)
|
||
{
|
||
RPC_CHAR * Buffer;
|
||
unsigned long Cursor = 0;
|
||
unsigned long BufferLength = INITIAL_NAME_LENGTH;
|
||
HCERTSTORE Store = Context->hCertStore;
|
||
BOOL fFreeStore = FALSE;
|
||
|
||
Buffer = new RPC_CHAR[BufferLength];
|
||
if (!Buffer)
|
||
{
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
PCCERT_CONTEXT Node = Context;
|
||
do
|
||
{
|
||
Status = AddComponentName( &Buffer,
|
||
&BufferLength,
|
||
&Cursor,
|
||
&Node->pCertInfo->Subject
|
||
);
|
||
if (Status)
|
||
{
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
Status,
|
||
EEInfoDLRpcCertGeneratePrincipalName10);
|
||
|
||
return Status;
|
||
}
|
||
|
||
//
|
||
// Load the next certificate.
|
||
//
|
||
PCCERT_CONTEXT OldNode = Node;
|
||
|
||
if (CertCompareCertificateName( X509_ASN_ENCODING,
|
||
&Node->pCertInfo->Subject,
|
||
&Node->pCertInfo->Issuer
|
||
))
|
||
{
|
||
break;
|
||
}
|
||
|
||
Node = CertFindCertificateInStore( Store,
|
||
X509_ASN_ENCODING,
|
||
0, // no flags
|
||
CERT_FIND_SUBJECT_NAME, // exact match
|
||
&OldNode->pCertInfo->Issuer,
|
||
NULL // previous context in search
|
||
);
|
||
if (!Node)
|
||
{
|
||
//
|
||
// Take the top-level CA name from the current node's issuer field.
|
||
//
|
||
Status = AddComponentName( &Buffer,
|
||
&BufferLength,
|
||
&Cursor,
|
||
&OldNode->pCertInfo->Issuer
|
||
);
|
||
if (Status)
|
||
{
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
Status,
|
||
EEInfoDLRpcCertGeneratePrincipalName20);
|
||
|
||
CertFreeCertificateContext( OldNode );
|
||
return Status;
|
||
}
|
||
}
|
||
|
||
if (OldNode != Context)
|
||
{
|
||
CertFreeCertificateContext( OldNode );
|
||
}
|
||
}
|
||
while ( Node );
|
||
|
||
if (Cursor+FULLPATH_PREFIX_LENGTH+1 > BufferLength)
|
||
{
|
||
DWORD LongerBufferLength = BufferLength+FULLPATH_PREFIX_LENGTH+1;
|
||
RPC_CHAR * LongerBuffer = new RPC_CHAR[LongerBufferLength];
|
||
if (!LongerBuffer)
|
||
{
|
||
delete Buffer;
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
RpcpStringNCopy( LongerBuffer, Buffer, BufferLength );
|
||
|
||
delete Buffer;
|
||
|
||
Buffer = LongerBuffer;
|
||
BufferLength = LongerBufferLength;
|
||
}
|
||
|
||
RpcpStringCopy( Buffer+Cursor,FULLPATH_PREFIX );
|
||
RpcpStringReverse( Buffer+Cursor );
|
||
RpcpStringReverse( Buffer );
|
||
|
||
*pBuffer = Buffer;
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
|
||
RPC_CHAR * outputName;
|
||
|
||
DWORD NameSize = CertGetNameStringW(Context,
|
||
CERT_NAME_EMAIL_TYPE,
|
||
0, // dwFlags
|
||
NULL, // pvTypePara
|
||
NULL,
|
||
0);
|
||
|
||
if (NameSize > 1)
|
||
{
|
||
outputName = new RPC_CHAR[ NameSize + MSSTD_PREFIX_LENGTH ];
|
||
|
||
if (!outputName)
|
||
{
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
RpcpStringCopy(outputName, MSSTD_PREFIX);
|
||
|
||
NameSize = CertGetNameStringW(Context,
|
||
CERT_NAME_EMAIL_TYPE,
|
||
0,
|
||
NULL,
|
||
outputName + MSSTD_PREFIX_LENGTH,
|
||
NameSize);
|
||
|
||
ASSERT(NameSize > 1);
|
||
}
|
||
else
|
||
{
|
||
NameSize = CertGetNameStringW(Context,
|
||
CERT_NAME_ATTR_TYPE,
|
||
0,
|
||
szOID_COMMON_NAME,
|
||
NULL,
|
||
0);
|
||
|
||
if (NameSize > 1)
|
||
{
|
||
outputName = new RPC_CHAR[ NameSize + MSSTD_PREFIX_LENGTH ];
|
||
|
||
if (!outputName)
|
||
{
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
RpcpStringCopy(outputName, MSSTD_PREFIX);
|
||
|
||
NameSize = CertGetNameString(Context,
|
||
CERT_NAME_ATTR_TYPE,
|
||
0,
|
||
szOID_COMMON_NAME,
|
||
outputName + MSSTD_PREFIX_LENGTH,
|
||
NameSize);
|
||
|
||
ASSERT(NameSize > 1);
|
||
}
|
||
else
|
||
{
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
ERROR_INVALID_PARAMETER,
|
||
EEInfoDLRpcCertGeneratePrincipalName30,
|
||
GetLastError());
|
||
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
}
|
||
|
||
*pBuffer = outputName;
|
||
|
||
return 0;
|
||
}
|
||
|
||
ASSERT( 0 && "never reach here" );
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
|
||
DWORD
|
||
AddComponentName( RPC_CHAR * * pBuffer,
|
||
unsigned long * pBufferLength,
|
||
unsigned long * pCursor,
|
||
CERT_NAME_BLOB * Name
|
||
)
|
||
{
|
||
(*pBuffer)[(*pCursor)++] = '>';
|
||
|
||
DWORD Length = *pBufferLength - *pCursor;
|
||
|
||
Length = CertNameToStrT( X509_ASN_ENCODING,
|
||
Name,
|
||
CERT_X500_NAME_STR,
|
||
*pBuffer + *pCursor,
|
||
Length
|
||
);
|
||
|
||
if (Length >= *pBufferLength - *pCursor)
|
||
{
|
||
DWORD LongerBufferLength = 2 * *pBufferLength + Length;
|
||
RPC_CHAR * LongerBuffer = new RPC_CHAR[LongerBufferLength];
|
||
if (!LongerBuffer)
|
||
{
|
||
delete *pBuffer;
|
||
return ERROR_NOT_ENOUGH_MEMORY;
|
||
}
|
||
|
||
RpcpStringNCopy( LongerBuffer, *pBuffer, *pBufferLength );
|
||
|
||
delete *pBuffer;
|
||
|
||
*pBuffer = LongerBuffer;
|
||
*pBufferLength = LongerBufferLength;
|
||
|
||
Length = *pBufferLength - *pCursor;
|
||
|
||
Length = CertNameToStrT( X509_ASN_ENCODING,
|
||
Name,
|
||
CERT_X500_NAME_STR,
|
||
*pBuffer + *pCursor,
|
||
Length
|
||
);
|
||
}
|
||
|
||
RpcpStringReverse(*pBuffer + *pCursor);
|
||
*pCursor += Length-1; // write over the '\0'
|
||
|
||
(*pBuffer)[(*pCursor)++] = '<';
|
||
(*pBuffer)[(*pCursor)++] = '\\';
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
unsigned
|
||
Int4StrLen(
|
||
unsigned long * String
|
||
)
|
||
{
|
||
unsigned long * Cursor = String;
|
||
|
||
while (*Cursor)
|
||
{
|
||
++Cursor;
|
||
}
|
||
|
||
return (unsigned) (Cursor - String);
|
||
}
|
||
|
||
HMODULE Crypt32Handle = 0;
|
||
struct CRYPT32_FUNCTION_TABLE CFT;
|
||
|
||
|
||
BOOL
|
||
LoadCrypt32Imports()
|
||
{
|
||
if (0 == Crypt32Handle)
|
||
{
|
||
RequestGlobalMutex();
|
||
|
||
if (Crypt32Handle)
|
||
{
|
||
goto Cleanup;
|
||
}
|
||
|
||
Crypt32Handle = LoadLibrary(RPC_CONST_SSTRING("crypt32.dll"));
|
||
if (!Crypt32Handle)
|
||
{
|
||
goto Cleanup;
|
||
}
|
||
|
||
FARPROC * ppProc = (FARPROC *) &CFT;
|
||
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertOpenStore");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertCloseStore");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertFindCertificateInStore");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertFreeCertificateContext");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertCompareCertificateName");
|
||
#ifdef UNICODE
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertStrToNameW");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertNameToStrW");
|
||
#else
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertStrToNameA");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertNameToStrA");
|
||
#endif
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertFindRDNAttr");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CryptDecodeObject");
|
||
|
||
#if MANUAL_CERT_CHECK
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertVerifyCertificateChainPolicy");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertGetCertificateChain");
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertFreeCertificateChain");
|
||
#endif
|
||
|
||
*ppProc++ = GetProcAddress(Crypt32Handle, "CertGetNameStringW");
|
||
|
||
ppProc = (FARPROC *) &CFT;
|
||
|
||
for (int i = 0; i < sizeof(CRYPT32_FUNCTION_TABLE)/sizeof(FARPROC); i++)
|
||
{
|
||
if (*ppProc++ == 0)
|
||
{
|
||
FreeLibrary(Crypt32Handle);
|
||
Crypt32Handle = 0;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
ClearGlobalMutex();
|
||
}
|
||
|
||
return TRUE;
|
||
|
||
Cleanup:
|
||
ClearGlobalMutex();
|
||
return FALSE;
|
||
}
|
||
|
||
#if MANUAL_CERT_CHECK
|
||
|
||
DWORD
|
||
RpcCertVerifyContext(
|
||
IN PCERT_CONTEXT Context,
|
||
IN DWORD CapabilityFlags
|
||
)
|
||
{
|
||
DWORD s = 0;
|
||
HCERTSTORE CaStore = 0;
|
||
PCCERT_CHAIN_CONTEXT Chain = 0;
|
||
|
||
//
|
||
// Windows 2000 checked only the CA store, so we should check it also.
|
||
//
|
||
CaStore = CertOpenStore( CERT_STORE_PROV_SYSTEM_W,
|
||
0,
|
||
0,
|
||
CERT_SYSTEM_STORE_CURRENT_USER,
|
||
RPC_CONST_STRING("CA")
|
||
);
|
||
if (!CaStore)
|
||
{
|
||
s = GetLastError();
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
s,
|
||
EEInfoDLRpcCertVerifyContext10);
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Build a certificate chain from the single certificate we have.
|
||
//
|
||
CERT_CHAIN_PARA ChainParameters;
|
||
|
||
ChainParameters.cbSize = sizeof(CERT_CHAIN_PARA);
|
||
ChainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
|
||
ChainParameters.RequestedUsage.Usage.cUsageIdentifier = 0;
|
||
|
||
if (!CertGetCertificateChain( NULL, // use default chain engine
|
||
Context,
|
||
NULL, // match against current time
|
||
CaStore, // aditional store to search
|
||
&ChainParameters,
|
||
0, // no special flags
|
||
0, // reserved, MBZ
|
||
&Chain
|
||
))
|
||
{
|
||
s = GetLastError();
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
s,
|
||
EEInfoDLRpcCertVerifyContext20);
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Verify the chain.
|
||
//
|
||
CERT_CHAIN_POLICY_PARA PolicyParameters;
|
||
CERT_CHAIN_POLICY_STATUS PolicyStatus;
|
||
|
||
PolicyParameters.cbSize = sizeof(CERT_CHAIN_POLICY_PARA);
|
||
PolicyParameters.dwFlags = 0;
|
||
PolicyParameters.pvExtraPolicyPara = 0;
|
||
|
||
if (CapabilityFlags & RPC_C_QOS_CAPABILITIES_ANY_AUTHORITY)
|
||
{
|
||
PolicyParameters.dwFlags |= CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG;
|
||
}
|
||
|
||
PolicyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS);
|
||
|
||
if (!CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_BASE,
|
||
Chain,
|
||
&PolicyParameters,
|
||
&PolicyStatus))
|
||
{
|
||
s = GetLastError();
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
s,
|
||
EEInfoDLRpcCertVerifyContext30);
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
if (PolicyStatus.dwError)
|
||
{
|
||
s = ERROR_ACCESS_DENIED;
|
||
RpcpErrorAddRecord(EEInfoGCSecurityProvider,
|
||
s,
|
||
EEInfoDLRpcCertVerifyContext40,
|
||
GetLastError());
|
||
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Certificate verification succeeded.
|
||
//
|
||
s = 0;
|
||
|
||
Cleanup:
|
||
|
||
if (Chain)
|
||
{
|
||
CertFreeCertificateChain( Chain );
|
||
}
|
||
|
||
if (CaStore)
|
||
{
|
||
CertCloseStore( CaStore, 0 );
|
||
}
|
||
|
||
return s;
|
||
}
|
||
|
||
#endif
|
||
|
||
RPC_STATUS
|
||
ValidateSchannelPrincipalName(
|
||
IN RPC_CHAR * EncodedName
|
||
)
|
||
//
|
||
// Does a quick syntactic check of an SSL principal name.
|
||
// The name should not be modified in any way.
|
||
//
|
||
// It should begin either with "msstd:" or "fullsic:".
|
||
// If msstd, then any non-enpty name will do.
|
||
// If fullsic, it should be a series of RFC1179 names, each surrounded
|
||
// by angle brackets, and separated by backslashes. We check this by cloning
|
||
// the string and marking the components.
|
||
//
|
||
{
|
||
#define MAX_SSL_SPN_LENGTH 8000
|
||
|
||
if (0 == RpcpStringNCompare(EncodedName, MSSTD_PREFIX, MSSTD_PREFIX_LENGTH))
|
||
{
|
||
if (EncodedName[MSSTD_PREFIX_LENGTH] == 0)
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
}
|
||
else if (0 == RpcpStringNCompare(EncodedName, FULLPATH_PREFIX, FULLPATH_PREFIX_LENGTH))
|
||
{
|
||
RPC_STATUS Status;
|
||
size_t Length;
|
||
unsigned ComponentCount;
|
||
RPC_CHAR * PrincipalName;
|
||
RPC_CHAR * EncodedPrincipalName;
|
||
|
||
Length = RpcpStringLength( EncodedName );
|
||
if (Length > MAX_SSL_SPN_LENGTH)
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
PrincipalName = (RPC_CHAR *) _alloca(sizeof(RPC_CHAR) * Length);
|
||
|
||
Status = DecodeEscapedString(EncodedName, PrincipalName);
|
||
if (Status)
|
||
{
|
||
return Status;
|
||
}
|
||
|
||
Status = MarkPrincipalNameComponents( PrincipalName + FULLPATH_PREFIX_LENGTH, &ComponentCount );
|
||
if (Status)
|
||
{
|
||
return Status;
|
||
}
|
||
|
||
if (ComponentCount < 1)
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return ERROR_INVALID_PARAMETER;
|
||
}
|
||
|
||
return RPC_S_OK;
|
||
}
|
||
|
||
RPC_STATUS
|
||
I_RpcTransCertMatchPrincipalName(
|
||
PCCERT_CONTEXT Context,
|
||
RPC_CHAR PrincipalName[]
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies that the passed in context match the passed in
|
||
principal name and frees the certificate.
|
||
|
||
Arguments:
|
||
|
||
Context - certificate context.
|
||
|
||
PrincipalName - a principal name prefixed with either "msstd:" or "fullsic:"
|
||
|
||
Return Value:
|
||
|
||
RPC_S_OK or RPC_S_ACCESS_DENIED
|
||
|
||
--*/
|
||
{
|
||
RPC_STATUS RpcStatus;
|
||
|
||
RpcStatus = RpcCertMatchPrincipalName(Context, PrincipalName);
|
||
|
||
if (RpcStatus == ERROR_OUTOFMEMORY)
|
||
RpcStatus = RPC_S_OUT_OF_MEMORY;
|
||
else if (RpcStatus != RPC_S_OK)
|
||
RpcStatus = RPC_S_ACCESS_DENIED;
|
||
|
||
CertFreeCertificateContext(Context);
|
||
|
||
return RpcStatus;
|
||
} |