windows-nt/Source/XPSP1/NT/ds/security/protocols/kerberos/server/pkserv.cxx
2020-09-26 16:20:57 +08:00

2422 lines
64 KiB
C++

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1997
//
// File: pkserv.cxx
//
// Contents: Server side public key support for Kerberos
//
//
// History: 24-Nov-1997 MikeSw Created
//
//------------------------------------------------------------------------
#include "kdcsvr.hxx"
#include <wininet.h> // for SECURITY_FLAG_xxx
#include <sclogon.h> // ScHelperXXX
#include <cryptui.h> // for CryptUiXXX
#include <certca.h> // for CA*XXX
#define FILENO FILENO_PKSERV
//
// This is the cert store containing the CTL used to verify client certificates
//
HCERTSTORE KdcCertStore = NULL;
HCRYPTPROV KdcClientProvider = NULL;
PCCERT_CONTEXT GlobalKdcCert = NULL;
HANDLE KdcCertStoreChangeEvent = NULL;
TimeStamp KdcLastChangeEventTime;
RTL_CRITICAL_SECTION KdcGlobalCertCritSect;
BOOLEAN KdcGlobalCertCritSectInitialized = FALSE;
HANDLE KdcCertStoreWait = NULL;
BOOLEAN KdcPKIInitialized = FALSE;
BOOLEAN Kdc3DesSupported = TRUE;
HANDLE KdcCaNotificationHandle = NULL;
#define KDC_ROOT_STORE L"ROOT"
#define KDC_PRIVATE_MY_STORE L"MY"
#define MAX_TEMPLATE_NAME_VALUE_SIZE 80 // sizeof (CERT_NAME_VALUE) + wcslen(SmartcardLogon)
KERB_OBJECT_ID KdcSignatureAlg[10];
NTSTATUS
KdcGetKdcCertificate(PCCERT_CONTEXT *KdcCert);
//+-------------------------------------------------------------------------
//
// Function: KdcCheckCertificate
//
// Synopsis: a helper routine to verify the certificate. It will check
// CRLs, CTLs
//
// Effects:
//
// Arguments:
// CertContext - the certificate to check
// EmbeddedUPNOk - returns TRUE if the certificate can
// be translated to a user by looking at the
// subject name.
// returns FALSE if the certificate must be
// mapped by looking in the user's mapped certificate
// ds attribute.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCheckCertificate(
IN PCCERT_CONTEXT CertContext,
OUT PBOOLEAN EmbeddedUPNOk,
IN OUT PKERB_EXT_ERROR pExtendedError,
IN OUT OPTIONAL PCERT_CHAIN_POLICY_STATUS FinalChainStatus,
IN BOOLEAN KdcCert
)
{
NTSTATUS Status = STATUS_SUCCESS;
KERBERR KerbErr = KDC_ERR_NONE;
CERT_CHAIN_PARA ChainParameters = {0};
LPSTR Usage = (KdcCert ? KERB_PKINIT_KDC_CERT_TYPE : KERB_PKINIT_CLIENT_CERT_TYPE);
PCCERT_CHAIN_CONTEXT ChainContext = NULL;
CERT_CHAIN_POLICY_STATUS PolicyStatus ={0};
ChainParameters.cbSize = sizeof(CERT_CHAIN_PARA);
ChainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
ChainParameters.RequestedUsage.Usage.cUsageIdentifier = 1;
ChainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = &Usage;
*EmbeddedUPNOk = FALSE;
if (!CertGetCertificateChain(
HCCE_LOCAL_MACHINE,
CertContext,
NULL, // evaluate at current time
NULL, // no additional stores
&ChainParameters,
CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
NULL, // reserved
&ChainContext
))
{
DebugLog((DEB_WARN,"Failed to verify certificate chain: %0x%x\n",GetLastError()));
KerbErr = KDC_ERR_CLIENT_NOT_TRUSTED;
goto Cleanup;
}
else
{
CERT_CHAIN_POLICY_PARA ChainPolicy;
ZeroMemory(&ChainPolicy, sizeof(ChainPolicy));
ChainPolicy.cbSize = sizeof(ChainPolicy);
ZeroMemory(&PolicyStatus, sizeof(PolicyStatus));
PolicyStatus.cbSize = sizeof(PolicyStatus);
PolicyStatus.lChainIndex = -1;
PolicyStatus.lElementIndex = -1;
if (!CertVerifyCertificateChainPolicy(
CERT_CHAIN_POLICY_NT_AUTH,
ChainContext,
&ChainPolicy,
&PolicyStatus))
{
DebugLog((DEB_WARN,"CertVerifyCertificateChainPolicy failure: %0x%x\n", GetLastError()));
KerbErr = KDC_ERR_CLIENT_NOT_TRUSTED;
goto Cleanup;
}
if(PolicyStatus.dwError == S_OK)
{
*EmbeddedUPNOk = TRUE;
}
else if(CERT_E_UNTRUSTEDCA == PolicyStatus.dwError)
{
// We can't use this cert for fast-mapping, but we can still
// slow-map it.
*EmbeddedUPNOk = FALSE;
}
else
{
DebugLog((DEB_WARN,"CertVerifyCertificateChainPolicy - Chain Status failure: %0x%x\n",PolicyStatus.dwError));
KerbErr = KDC_ERR_CLIENT_NOT_TRUSTED;
goto Cleanup;
}
}
Cleanup:
if (PolicyStatus.dwError != S_OK)
{
FILL_EXT_ERROR_EX(pExtendedError, PolicyStatus.dwError,FILENO,__LINE__);
if (ARGUMENT_PRESENT(FinalChainStatus))
{
RtlCopyMemory(
FinalChainStatus,
&PolicyStatus,
sizeof(CERT_CHAIN_POLICY_STATUS)
);
}
}
if (ChainContext != NULL)
{
CertFreeCertificateChain(ChainContext);
}
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyClientCertName
//
// Synopsis: Verifies that the mapping of a client's cert name matches
// the mapping of the client name from the AS request
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcVerifyClientCertName(
IN PCCERT_CONTEXT ClientCert,
IN PKDC_TICKET_INFO ClientTicketInfo
)
{
ULONG NameLength = 0;
UNICODE_STRING NameString = {0};
UNICODE_STRING ClientRealm = {0};
PKERB_INTERNAL_NAME ClientName = NULL;
KDC_TICKET_INFO TicketInfo = {0};
BOOLEAN ClientReferral = FALSE;
KERBERR KerbErr = KDC_ERR_NONE;
KERB_EXT_ERROR ExtendedError;
ULONG ExtensionIndex = 0;
//
// Get the client name from the cert
//
if(STATUS_SUCCESS != KerbGetPrincipalNameFromCertificate(ClientCert, &NameString))
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
D_DebugLog((DEB_TRACE,"Email name from certificate is %wZ\n",&NameString));
KerbErr = KerbConvertStringToKdcName(
&ClientName,
&NameString
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
ClientName->NameType = KRB_NT_ENTERPRISE_PRINCIPAL;
//
// Now crack the name & see if it refers to us
//
//
// Normalize the client name.
//
KerbErr = KdcNormalize(
ClientName,
NULL,
NULL,
KDC_NAME_CLIENT,
&ClientReferral,
&ClientRealm,
&TicketInfo,
&ExtendedError,
NULL, // no user handle
0L, // no fields to fetch
0L, // no extended fields
NULL, // no user all
NULL // no group membership
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"Failed to normalize name "));
KerbPrintKdcName(DEB_ERROR,ClientName);
goto Cleanup;
}
//
// If this is a referral, return an error and the true realm name
// of the client
//
if (ClientReferral)
{
KerbErr = KDC_ERR_WRONG_REALM;
DebugLog((DEB_WARN,"Client tried to logon to account in another realm\n"));
goto Cleanup;
}
//
// Verify the client cert matches the client
//
if (TicketInfo.UserId != ClientTicketInfo->UserId)
{
DebugLog((DEB_ERROR,"Cert name doesn't match user name: %wZ, %wZ\n",
&NameString, &ClientTicketInfo->AccountName));
KerbErr = KDC_ERR_CLIENT_NAME_MISMATCH;
goto Cleanup;
}
Cleanup:
KerbFreeString( &NameString);
KerbFreeKdcName( &ClientName );
FreeTicketInfo( &TicketInfo );
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcConvertNameString
//
// Synopsis: Converts the cr-lf to , in a dn
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
void
KdcConvertNameString(
IN PUNICODE_STRING Name,
IN WCHAR ReplacementChar
)
{
PWCHAR Comma1, Comma2;
//
// Scan through the name, converting "\r\n" to the replacement char. This
// should be done by the CertNameToStr APIs, but that won't happen for
// a while.
//
Comma1 = Comma2 = Name->Buffer ;
while ( *Comma2 )
{
*Comma1 = *Comma2 ;
if ( *Comma2 == L'\r' )
{
if ( *(Comma2 + 1) == L'\n' )
{
*Comma1 = ReplacementChar;
Comma2++ ;
}
}
Comma1++;
Comma2++;
}
*Comma1 = L'\0';
Name->Length = wcslen( Name->Buffer ) * sizeof( WCHAR );
}
//+-------------------------------------------------------------------------
//
// Function: KdcVerifyMappedClientCertIdentity
//
// Synopsis: Verifies that the mapping of a client's cert identity
// the mapping of the client name from the AS request. The
// cert should be in the list of mapped ceritificates for this
// user.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
#define ISSUER_HEADER L"<I>"
#define CCH_ISSUER_HEADER 3
#define SUBJECT_HEADER L"<S>"
#define CCH_SUBJECT_HEADER 3
KERBERR
KdcVerifyMappedClientCertIdentity(
IN PCCERT_CONTEXT ClientCert,
IN PKDC_TICKET_INFO ClientTicketInfo
)
{
KERBERR KerbErr = KDC_ERR_CLIENT_NAME_MISMATCH;
//
// Disable this code for now
//
#ifdef notdef
UNICODE_STRING CompoundName = {0};
ULONG SubjectLength ;
ULONG IssuerLength ;
NTSTATUS Status ;
PWCHAR Current ;
KDC_TICKET_INFO TicketInfo = {0};
DWORD dwNameToStrFlags = CERT_X500_NAME_STR |
CERT_NAME_STR_NO_PLUS_FLAG |
CERT_NAME_STR_CRLF_FLAG;
//
// Build the name of the form <i>issuer <s> subject
//
IssuerLength = CertNameToStr( ClientCert->dwCertEncodingType,
&ClientCert->pCertInfo->Issuer,
dwNameToStrFlags,
NULL,
0 );
SubjectLength = CertNameToStr( ClientCert->dwCertEncodingType,
&ClientCert->pCertInfo->Subject,
dwNameToStrFlags,
NULL,
0 );
if ( ( IssuerLength == 0 ) ||
( SubjectLength == 0 ) )
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
CompoundName.MaximumLength = (USHORT) (SubjectLength + IssuerLength +
CCH_ISSUER_HEADER + CCH_SUBJECT_HEADER) *
sizeof( WCHAR ) ;
CompoundName.Buffer = (LPWSTR) MIDL_user_allocate( CompoundName.MaximumLength );
if ( CompoundName.Buffer == NULL )
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
wcscpy( CompoundName.Buffer, ISSUER_HEADER );
Current = CompoundName.Buffer + CCH_ISSUER_HEADER ;
IssuerLength = CertNameToStr( ClientCert->dwCertEncodingType,
&ClientCert->pCertInfo->Issuer,
dwNameToStrFlags,
Current,
IssuerLength );
Current += IssuerLength - 1 ;
wcscpy( Current, SUBJECT_HEADER );
Current += CCH_SUBJECT_HEADER ;
SubjectLength = CertNameToStr( ClientCert->dwCertEncodingType,
&ClientCert->pCertInfo->Subject,
dwNameToStrFlags,
Current,
SubjectLength );
KdcConvertNameString(
&CompoundName,
L','
);
//
// Get ticket info for this name
//
KerbErr = KdcGetTicketInfo(
&CompoundName,
SAM_OPEN_BY_ALTERNATE_ID,
NULL, // no kerb principal name
NULL,
&TicketInfo,
NULL, // no handle
0L, // no fields to fetch
0L, // no extended fields
NULL, // no user all
NULL // no membership
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR,"Failed to get ticket info for %wZ to verify certZ\n",
&CompoundName));
goto Cleanup;
}
if (TicketInfo.UserId != ClientTicketInfo->UserId)
{
D_DebugLog((DEB_ERROR,"Cert name doesn't match user name: %wZ, %wZ\n",
&TicketInfo.AccountName, &ClientTicketInfo->AccountName));
KerbErr = KRB_AP_ERR_BADMATCH;
goto Cleanup;
}
Cleanup:
KerbFreeString(&CompoundName);
FreeTicketInfo( &TicketInfo );
#endif
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: KdcCheckForEtype
//
// Synopsis: Checks if a client supports a particular etype
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns: TRUE if it does, false if it doesn't
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOLEAN
KdcCheckForEtype(
IN PKERB_CRYPT_LIST CryptList,
IN ULONG Etype
)
{
PKERB_CRYPT_LIST List = CryptList;
while (List != NULL)
{
if ((ULONG) List->value == Etype)
{
return(TRUE);
}
List=List->next;
}
return(FALSE);
}
//+-------------------------------------------------------------------------
//
// Function: KdcCheckPkinitPreAuthData
//
// Synopsis:
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
KERBERR
KdcCheckPkinitPreAuthData(
IN PKDC_TICKET_INFO ClientTicketInfo,
IN SAMPR_HANDLE UserHandle,
IN OPTIONAL PKERB_PA_DATA_LIST PreAuthData,
IN PKERB_KDC_REQUEST_BODY ClientRequest,
OUT PKERB_PA_DATA_LIST * OutputPreAuthData,
OUT PULONG Nonce,
OUT PKERB_ENCRYPTION_KEY EncryptionKey,
OUT PUNICODE_STRING TransitedRealm,
IN OUT PKERB_EXT_ERROR pExtendedError
)
{
NTSTATUS Status;
KERBERR KerbErr = KDC_ERR_NONE;
PKERB_PA_PK_AS_REQ PkAsReq = NULL;
PKERB_PA_PK_AS_REQ2 PkAsReq2 = NULL;
PKERB_CERTIFICATE UserCert = NULL;
PCCERT_CONTEXT CertContext = NULL;
PCCERT_CONTEXT KdcCert = NULL;
HCRYPTKEY ClientKey = NULL;
PBYTE PackedAuthenticator = NULL;
ULONG PackedAuthenticatorSize;
PBYTE PackedKeyPack = NULL;
ULONG PackedKeyPackSize;
PBYTE SignedKeyPack = NULL;
ULONG SignedKeyPackSize;
PKERB_SIGNATURE Signature = NULL;
PKERB_PK_AUTHENTICATOR PkAuthenticator = NULL;
CERT_CHAIN_POLICY_STATUS FinalChainStatus = {0};
UNICODE_STRING ClientKdcName = {0};
ULONG ClientKdcNameType;
LARGE_INTEGER ClientTime;
LARGE_INTEGER CurrentTime;
PULONG EtypeArray = NULL;
ULONG EtypeCount = 0;
ULONG CommonEtype;
KERB_SIGNED_REPLY_KEY_PACKAGE KeyPack = {0};
KERB_REPLY_KEY_PACKAGE ReplyKey = {0};
HCRYPTPROV KdcProvider = NULL;
BOOL FreeProvider = FALSE;
#define KERB_PK_MAX_SIGNATURE_SIZE 128
BYTE PkSignature[KERB_PK_MAX_SIGNATURE_SIZE];
ULONG PkSignatureLength = KERB_PK_MAX_SIGNATURE_SIZE;
ULONG RequiredSize = 0;
PBYTE EncryptedKeyPack = NULL;
PKERB_PA_DATA_LIST PackedPkAsRep = NULL;
CRYPT_ENCRYPT_MESSAGE_PARA MessageParam = {0};
PBYTE PackedKey = NULL;
ULONG PackedKeySize = 0;
ULONG EncryptionOverhead = 0;
ULONG BlockSize = 0;
KERB_ENCRYPTION_KEY TempKey = {0};
PKERB_CERTIFICATE_LIST CertList = NULL;
CRYPT_ALGORITHM_IDENTIFIER CryptAlg = {0};
PKERB_AUTH_PACKAGE AuthPack = NULL;
BOOLEAN EmbeddedUPNOk = FALSE;
BOOLEAN Used3Des = FALSE;
ULONG TransitedLength = 0;
ULONG Index;
DWORD dwNameToStrFlags = CERT_X500_NAME_STR |
CERT_NAME_STR_NO_PLUS_FLAG |
CERT_NAME_STR_CRLF_FLAG;
//
// Prepare the output variables
//
*OutputPreAuthData = NULL;
RtlZeroMemory(
EncryptionKey,
sizeof(KERB_ENCRYPTION_KEY)
);
*Nonce = 0;
//
// If we don't do this preauth, return such
//
Status = KdcGetKdcCertificate(&KdcCert);
if (!NT_SUCCESS(Status))
{
//
// Log an event
//
ReportServiceEvent(
EVENTLOG_ERROR_TYPE,
KDCEVENT_NO_KDC_CERTIFICATE,
0,
NULL,
0
);
FILL_EXT_ERROR_EX(pExtendedError, STATUS_PKINIT_FAILURE, FILENO, __LINE__);
return(KDC_ERR_PADATA_TYPE_NOSUPP);
}
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime );
//
// First, unpack the outer KRB-PA-PK-AS-REQ
//
KerbErr = KerbUnpackData(
PreAuthData->value.preauth_data.value,
PreAuthData->value.preauth_data.length,
KERB_PA_PK_AS_REQ_PDU,
(PVOID *) &PkAsReq
);
if (!KERB_SUCCESS(KerbErr))
{
//
// Try the older variation
//
KerbErr = KerbUnpackData(
PreAuthData->value.preauth_data.value,
PreAuthData->value.preauth_data.length,
KERB_PA_PK_AS_REQ2_PDU,
(PVOID *) &PkAsReq2
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"Failed to unpack PA-PK-AS-REQ(2): 0x%x\n",KerbErr));
goto Cleanup;
}
}
if (PkAsReq != NULL)
{
//
// Verify the signature
//
Status = ScHelperVerifyPkcsMessage(
NULL,
KdcClientProvider,
PkAsReq->signed_auth_pack.value,
PkAsReq->signed_auth_pack.length,
PackedAuthenticator,
&PackedAuthenticatorSize,
NULL // don't return certificate context
);
if ((Status != ERROR_MORE_DATA) && (Status != STATUS_SUCCESS))
{
DebugLog((DEB_ERROR,"Failed to verify message: %x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
PackedAuthenticator = (PBYTE) MIDL_user_allocate(PackedAuthenticatorSize);
if (PackedAuthenticator == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Status = ScHelperVerifyPkcsMessage(
NULL,
KdcClientProvider,
PkAsReq->signed_auth_pack.value,
PkAsReq->signed_auth_pack.length,
PackedAuthenticator,
&PackedAuthenticatorSize,
&CertContext
);
if (Status != STATUS_SUCCESS)
{
DebugLog((DEB_ERROR,"Failed to verify message: %x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
//
// Unpack the auth package
//
KerbErr = KerbUnpackData(
PackedAuthenticator,
PackedAuthenticatorSize,
KERB_AUTH_PACKAGE_PDU,
(PVOID *)&AuthPack
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
PkAuthenticator = &AuthPack->pk_authenticator;
}
else
{
DsysAssert(PkAsReq2 != NULL);
//
// Get the user certificate & verify
//
if ((PkAsReq2->bit_mask & user_certs_present) == 0)
{
DebugLog((DEB_ERROR,"Client tried to use pkinit w/o client cert\n"));
KerbErr = KDC_ERR_BADOPTION;
goto Cleanup;
}
//
// Just use the first of the certificates
//
UserCert = &PkAsReq2->user_certs->value;
//
// We only handle x509 certificates
//
if (UserCert->cert_type != KERB_CERTIFICATE_TYPE_X509)
{
DebugLog((DEB_ERROR,"User supplied bad cert type: %d\n",UserCert->cert_type));
KerbErr = KDC_ERR_BADOPTION;
goto Cleanup;
}
//
// Decode the certificate.
//
CertContext = CertCreateCertificateContext(
X509_ASN_ENCODING,
UserCert->cert_data.value,
UserCert->cert_data.length
);
if (CertContext == NULL)
{
Status = GetLastError();
DebugLog((DEB_ERROR,"Failed to create certificate context: 0x%x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Verify the authenticator
//
Signature = &PkAsReq2->signed_auth_pack.auth_package_signature;
//
// Now import the key from the certificate
//
if (!CryptImportPublicKeyInfo(
KdcClientProvider,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
&CertContext->pCertInfo->SubjectPublicKeyInfo,
&ClientKey
))
{
DebugLog((DEB_ERROR,"Failed to import public key: 0x%x\n",GetLastError()));
FILL_EXT_ERROR(pExtendedError, GetLastError(), FILENO, __LINE__);
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Encode the data to be verified
//
KerbErr = KerbPackData(
&PkAsReq2->signed_auth_pack.auth_package,
KERB_AUTH_PACKAGE_PDU,
&PackedAuthenticatorSize,
&PackedAuthenticator
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Verify the signature on the message
//
if (!KerbCompareObjectIds(
Signature->signature_algorithm.algorithm,
KdcSignatureAlg
))
{
DebugLog((DEB_ERROR,"Unsupported signature algorithm (not MD5)\n"));
KerbErr = KDC_ERR_SUMTYPE_NOSUPP;
goto Cleanup;
}
Status = ScHelperVerifyMessage(
NULL, // no logon info
KdcClientProvider,
CertContext,
KERB_PKINIT_SIGNATURE_ALG,
PackedAuthenticator,
PackedAuthenticatorSize,
Signature->pkcs_signature.value,
Signature->pkcs_signature.length / 8 // because it is a bit string
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to verify message: 0x%x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KDC_ERR_INVALID_SIG;
goto Cleanup;
}
//
// Now check the information in the authenticator itself.
//
PkAuthenticator = &PkAsReq2->signed_auth_pack.auth_package.pk_authenticator;
}
//
// Call a helper routine to verify the certificate. It will check
// CRLs, CTLs,
//
KerbErr = KdcCheckCertificate(
CertContext,
&EmbeddedUPNOk,
pExtendedError,
&FinalChainStatus,
FALSE // not a kdc certificate
);
//
// Assume B3 certs aren't being used
// anymore
//
/*if(!KERB_SUCCESS(KerbErr))
{
KerbErr = KdcCheckB3Certificate(
CertContext,
&EmbeddedUPNOk
);
} */
if (!KERB_SUCCESS(KerbErr))
{
//
// Dumb this down for release? FESTER
//
if ((KDCInfoLevel & DEB_T_PKI) != 0)
{
LPWSTR Tmp = NULL;
Tmp = KerbBuildNullTerminatedString(&ClientTicketInfo->AccountName);
if (Tmp != NULL)
{
ReportServiceEvent(
EVENTLOG_WARNING_TYPE,
KDCEVENT_INVALID_CLIENT_CERTIFICATE,
sizeof(FinalChainStatus) - sizeof(void*), // don't need ptr.
&FinalChainStatus,
1,
Tmp
);
MIDL_user_free(Tmp);
}
}
DebugLog((DEB_ERROR,"Failed to check CLIENT certificate: 0x%x\n",KerbErr));
goto Cleanup;
}
//
// Verify the cert is for the right client
//
if(EmbeddedUPNOk)
{
KerbErr = KdcVerifyClientCertName(
CertContext,
ClientTicketInfo
);
}
else
{
KerbErr = KdcVerifyMappedClientCertIdentity(
CertContext,
ClientTicketInfo
);
}
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"KDC failed to verify client's identity from cert\n"));
goto Cleanup;
}
#ifdef later
//
// BUG 455112: this code breaks MIT KDCs, which can't handle a strange
// x.500 name in the transited field. So, for NT5, disable the code
//
//
// Put the issuer name in as a transited realm, as it is invovled in
// the authentication decision.
//
TransitedLength = CertNameToStr( CertContext->dwCertEncodingType,
&CertContext->pCertInfo->Issuer,
dwNameToStrFlags,
NULL,
0 );
if ( TransitedLength == 0 )
{
D_DebugLog((DEB_ERROR,"Failed to get issuer name: 0x%x\n",GetLastError()));
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
TransitedRealm->MaximumLength = (USHORT) TransitedLength * sizeof(WCHAR) + sizeof(WCHAR);
TransitedRealm->Length = (USHORT) TransitedLength * sizeof(WCHAR);
TransitedRealm->Buffer = (LPWSTR) MIDL_user_allocate( TransitedRealm->MaximumLength );
if ( TransitedRealm->Buffer == NULL )
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
TransitedLength = CertNameToStr( CertContext->dwCertEncodingType,
&CertContext->pCertInfo->Issuer,
dwNameToStrFlags,
TransitedRealm->Buffer,
TransitedLength );
if ( TransitedLength == 0 )
{
DebugLog((DEB_ERROR,"Failed to get issuer name: 0x%x\n",GetLastError()));
FILL_EXT_ERROR(pExtendedError, GetLastError(), FILENO, __LINE__);
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Convert the "." to "/"
//
KdcConvertNameString(
TransitedRealm,
L'/'
);
#endif // later
//
// Verify the realm name is correct
//
if (!SecData.IsOurRealm(
&PkAuthenticator->kdc_realm
))
{
DebugLog((DEB_ERROR,"Client used wrong realm in PK authenticator: %s\n",
PkAuthenticator->kdc_realm
));
KerbErr = KDC_ERR_S_PRINCIPAL_UNKNOWN;
goto Cleanup;
}
//
// Verify the service realm and kdc name is correct
//
KerbErr = KerbConvertPrincipalNameToString(
&ClientKdcName,
&ClientKdcNameType,
&PkAuthenticator->kdc_name
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
if (!RtlEqualUnicodeString(
SecData.KdcFullServiceKdcName(),
&ClientKdcName,
TRUE))
{
if (!RtlEqualUnicodeString(
SecData.KdcFullServiceDnsName(),
&ClientKdcName,
TRUE))
{
if (!RtlEqualUnicodeString(
SecData.KdcFullServiceName(),
&ClientKdcName,
TRUE))
{
DebugLog((DEB_ERROR,"Client provided KDC name is wrong: %wZ\n",
&ClientKdcName));
KerbErr = KDC_ERR_KDC_NAME_MISMATCH;
goto Cleanup;
}
}
}
//
// Now verify the time
//
KerbConvertGeneralizedTimeToLargeInt(
&ClientTime,
&PkAuthenticator->client_time,
PkAuthenticator->cusec
);
if (!KerbCheckTimeSkew(
&CurrentTime,
&ClientTime,
&SkewTime))
{
KerbErr = KRB_AP_ERR_SKEW;
goto Cleanup;
}
*Nonce = PkAuthenticator->nonce;
//
// Generate a temporary key. First find a good encryption type
//
KerbErr = KerbConvertCryptListToArray(
&EtypeArray,
&EtypeCount,
ClientRequest->encryption_type
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
Status = CDFindCommonCSystem(
EtypeCount,
EtypeArray,
&CommonEtype
);
if (!NT_SUCCESS(Status))
{
KerbErr = KDC_ERR_ETYPE_NOTSUPP;
goto Cleanup;
}
KerbErr = KerbMakeKey(
CommonEtype,
EncryptionKey
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Build the return structure
//
PackedPkAsRep = (PKERB_PA_DATA_LIST) MIDL_user_allocate(sizeof(KERB_PA_DATA_LIST));
if (PackedPkAsRep == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
RtlZeroMemory(
PackedPkAsRep,
sizeof(KERB_PA_DATA_LIST)
);
PackedPkAsRep->next = NULL;
PackedPkAsRep->value.preauth_data_type = KRB5_PADATA_PK_AS_REP;
//
// Success. Now build the reply
//
if (PkAsReq2 != NULL)
{
KERB_PA_PK_AS_REP2 Reply = {0};
//
// Create the reply key package
//
//
// Create the reply key package, which contains the key used to encrypt
// the AS_REPLY.
//
KeyPack.reply_key_package.nonce = *Nonce;
KeyPack.reply_key_package.reply_key = *EncryptionKey;
KerbErr = KerbPackData(
&KeyPack.reply_key_package,
KERB_REPLY_KEY_PACKAGE2_PDU,
&PackedKeyPackSize,
&PackedKeyPack
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Acquire a crypt context for the private key of the certificate
//
if (!CryptAcquireCertificatePrivateKey(
KdcCert,
0, // no flags
NULL, // reserved
&KdcProvider,
NULL, // no key spec
&FreeProvider
))
{
DebugLog((DEB_ERROR,"Failed to acquire KDC certificate private key: 0x%x\n",GetLastError()));
FILL_EXT_ERROR(pExtendedError, GetLastError(), FILENO, __LINE__);
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now, to sign the reply key package
//
Status = ScHelperSignMessage(
NULL, // no pin
NULL, // no logon info
KdcProvider,
KERB_PKINIT_SIGNATURE_ALG,
PackedKeyPack,
PackedKeyPackSize,
PkSignature,
&PkSignatureLength
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to sign keypack: 0x%x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
//
// Copy the temporary signature into the return structure
//
KeyPack.reply_key_signature.pkcs_signature.length = PkSignatureLength * 8; // because it is a bit string
KeyPack.reply_key_signature.pkcs_signature.value = PkSignature;
KeyPack.reply_key_signature.signature_algorithm.algorithm = KdcSignatureAlg;
//
// Now marshall the signed key package
//
KerbErr = KerbPackData(
&KeyPack,
KERB_SIGNED_REPLY_KEY_PACKAGE_PDU,
&SignedKeyPackSize,
&SignedKeyPack
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Just encrypt the key package
//
PackedKey = SignedKeyPack;
PackedKeySize = SignedKeyPackSize;
//
// Zero these out so we don't free them twice
//
SignedKeyPack = NULL;
SignedKeyPackSize = 0;
//
// Compute the size of the encrypted temp key
//
//
ChangeCryptAlg2:
if (Kdc3DesSupported && KdcCheckForEtype(ClientRequest->encryption_type, KERB_PKINIT_SEAL_ETYPE))
{
Used3Des = TRUE;
CryptAlg.pszObjId = KERB_PKINIT_SEAL_OID;
}
else
{
CryptAlg.pszObjId = KERB_PKINIT_EXPORT_SEAL_OID;
if (!KdcCheckForEtype(ClientRequest->encryption_type, KERB_PKINIT_EXPORT_SEAL_ETYPE))
{
DebugLog((DEB_WARN,"Client doesn't claim to support exportable pkinit encryption type %d\n",
KERB_PKINIT_EXPORT_SEAL_ETYPE));
}
}
RequiredSize = 0;
Status = ScHelperEncryptMessage(
NULL,
KdcClientProvider,
CertContext,
&CryptAlg,
PackedKey,
PackedKeySize,
NULL,
(PULONG) &RequiredSize
);
if ((Status != ERROR_MORE_DATA) && (Status != STATUS_SUCCESS))
{
//
// 3des is only supported on domestic builds with the
// strong cryptography pack installed.
//
if ((Status == NTE_BAD_ALGID) && (Used3Des))
{
Kdc3DesSupported = FALSE;
goto ChangeCryptAlg2;
}
DebugLog((DEB_ERROR,"Failed to encrypt message: %x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
//
// Allocate the output size
//
EncryptedKeyPack = (PBYTE) MIDL_user_allocate(RequiredSize);
if (EncryptedKeyPack == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Really do the encryption
//
Status = ScHelperEncryptMessage(
NULL,
KdcClientProvider,
CertContext,
&CryptAlg,
PackedKey,
PackedKeySize,
EncryptedKeyPack,
&RequiredSize
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to encrypt message: %x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
//
// Create the cert list for the reply
//
KerbErr = KerbCreateCertificateList(
&CertList,
KdcCert
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// We will be returning the KDC cert as well as a package containing
// a temporary key
//
Reply.bit_mask |= KERB_PA_PK_AS_REP2_kdc_cert_present;
//
// Now, to finish the reply, we need a handle to the KDCs certificate
//
Reply.kdc_cert = (KERB_PA_PK_AS_REP2_kdc_cert) CertList;
Reply.temp_key_package.choice = pkinit_enveloped_data_chosen;
Reply.temp_key_package.u.pkinit_enveloped_data.length = (int) RequiredSize;
Reply.temp_key_package.u.pkinit_enveloped_data.value = EncryptedKeyPack;
KerbErr = KerbPackData(
&Reply,
KERB_PA_PK_AS_REP2_PDU,
(PULONG) &PackedPkAsRep->value.preauth_data.length,
&PackedPkAsRep->value.preauth_data.value
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
else
{
KERB_PA_PK_AS_REP Reply = {0};
//
// Create the reply key package
//
//
// Create the reply key package, which contains the key used to encrypt
// the AS_REPLY.
//
ReplyKey.nonce = *Nonce;
ReplyKey.reply_key = *EncryptionKey;
KerbErr = KerbPackData(
&ReplyKey,
KERB_REPLY_KEY_PACKAGE_PDU,
&PackedKeyPackSize,
&PackedKeyPack
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
//
// Acquire a crypt context for the private key of the certificate
//
if (!CryptAcquireCertificatePrivateKey(
KdcCert,
0, // no flags
NULL, // reserved
&KdcProvider,
NULL, // no key spec
&FreeProvider
))
{
DebugLog((DEB_ERROR,"Failed to acquire KDC certificate private key: 0x%x\n",GetLastError()));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now, to sign the reply key package
//
CryptAlg.pszObjId = KERB_PKINIT_SIGNATURE_OID;
Status = ScHelperSignPkcsMessage(
NULL, // no pin
NULL, // no logon info
KdcProvider,
KdcCert,
&CryptAlg,
CRYPT_MESSAGE_SILENT_KEYSET_FLAG, // dwSignMessageFlags
PackedKeyPack,
PackedKeyPackSize,
SignedKeyPack,
&SignedKeyPackSize
);
if ((Status != ERROR_MORE_DATA) && (Status != STATUS_SUCCESS))
{
DebugLog((DEB_ERROR,"Failed to encrypt message: %x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
SignedKeyPack = (PBYTE) MIDL_user_allocate(SignedKeyPackSize);
if (SignedKeyPack == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
Status = ScHelperSignPkcsMessage(
NULL, // no pin
NULL, // no logon info
KdcProvider,
KdcCert,
&CryptAlg,
CRYPT_MESSAGE_SILENT_KEYSET_FLAG, // dwSignMessageFlags
PackedKeyPack,
PackedKeyPackSize,
SignedKeyPack,
&SignedKeyPackSize
);
if (Status != STATUS_SUCCESS)
{
DebugLog((DEB_ERROR,"Failed to sign pkcs message: 0x%x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Now encrypt the content
//
//
// Compute the size of the encrypted temp key
//
//
ChangeCryptAlg:
if (Kdc3DesSupported && KdcCheckForEtype(ClientRequest->encryption_type, KERB_PKINIT_SEAL_ETYPE))
{
Used3Des = TRUE;
CryptAlg.pszObjId = KERB_PKINIT_SEAL_OID;
}
else
{
CryptAlg.pszObjId = KERB_PKINIT_EXPORT_SEAL_OID;
if (!KdcCheckForEtype(ClientRequest->encryption_type, KERB_PKINIT_EXPORT_SEAL_ETYPE))
{
DebugLog((DEB_WARN,"Client doesn't claim to support exportable pkinit encryption type %d\n",
KERB_PKINIT_EXPORT_SEAL_ETYPE));
}
}
RequiredSize = 0;
Status = ScHelperEncryptMessage(
NULL,
KdcClientProvider,
CertContext,
&CryptAlg,
SignedKeyPack,
SignedKeyPackSize,
NULL,
(PULONG) &RequiredSize
);
if ((Status != ERROR_MORE_DATA) && (Status != STATUS_SUCCESS))
{
//
// 3des is only supported on domestic builds with the
// strong cryptography pack installed.
//
if ((Status == NTE_BAD_ALGID) && (Used3Des))
{
Kdc3DesSupported = FALSE;
goto ChangeCryptAlg;
}
DebugLog((DEB_ERROR,"Failed to encrypt message (crypto mismatch?): %x\n",Status));
FILL_EXT_ERROR_EX(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
//
// Allocate the output size
//
EncryptedKeyPack = (PBYTE) MIDL_user_allocate(RequiredSize);
if (EncryptedKeyPack == NULL)
{
KerbErr = KRB_ERR_GENERIC;
goto Cleanup;
}
//
// Really do the encryption
//
Status = ScHelperEncryptMessage(
NULL,
KdcClientProvider,
CertContext,
&CryptAlg,
SignedKeyPack,
SignedKeyPackSize,
EncryptedKeyPack,
&RequiredSize
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to encrypt message: %x\n",Status));
FILL_EXT_ERROR(pExtendedError, Status, FILENO, __LINE__);
KerbErr = KRB_AP_ERR_MODIFIED;
goto Cleanup;
}
Reply.u.key_package.value = EncryptedKeyPack;
Reply.u.key_package.length = RequiredSize;
Reply.choice = pkinit_enveloped_data_chosen;
KerbErr = KerbPackData(
&Reply,
KERB_PA_PK_AS_REP_PDU,
(PULONG) &PackedPkAsRep->value.preauth_data.length,
&PackedPkAsRep->value.preauth_data.value
);
if (!KERB_SUCCESS(KerbErr))
{
goto Cleanup;
}
}
*OutputPreAuthData = PackedPkAsRep;
PackedPkAsRep = NULL;
Cleanup:
if (FreeProvider)
{
CryptReleaseContext(
KdcProvider,
0
);
}
if (PkAsReq != NULL)
{
KerbFreeData(
KERB_PA_PK_AS_REQ_PDU,
PkAsReq
);
}
if (PkAsReq2 != NULL)
{
KerbFreeData(
KERB_PA_PK_AS_REQ2_PDU,
PkAsReq2
);
}
if (SignedKeyPack != NULL)
{
KdcFreeEncodedData(SignedKeyPack);
}
if (PackedKeyPack != NULL)
{
KdcFreeEncodedData(PackedKeyPack);
}
if (PackedAuthenticator != NULL)
{
KdcFreeEncodedData(PackedAuthenticator);
}
if (ClientKey != NULL)
{
CryptDestroyKey(ClientKey);
}
if (CertContext != NULL)
{
CertFreeCertificateContext(CertContext);
}
if(KdcCert)
{
CertFreeCertificateContext(KdcCert);
}
if (EncryptedKeyPack != NULL)
{
MIDL_user_free(EncryptedKeyPack);
}
if (EtypeArray != NULL)
{
MIDL_user_free(EtypeArray);
}
KerbFreeCertificateList(
CertList
);
KerbFreeKey(&TempKey);
if (PackedKey != NULL)
{
MIDL_user_free(PackedKey);
}
if (PackedPkAsRep != NULL)
{
if (PackedPkAsRep->value.preauth_data.value != NULL)
{
MIDL_user_free(PackedPkAsRep->value.preauth_data.value);
}
MIDL_user_free(PackedPkAsRep);
}
KerbFreeString(&ClientKdcName);
return(KerbErr);
}
//+-------------------------------------------------------------------------
//
// Function: VerifyDCCertificate
//
// Synopsis:
//
// Effects:
//
// Arguments: IN: A certificate context
//
// Requires: TRUE is the certificate has a smart card logon EKU; or its template
// name is DomainController
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOL VerifyDCCertificate(PCCERT_CONTEXT pCertContext)
{
BOOL fDCCert=FALSE;
CERT_EXTENSION *pExtension = NULL;
DWORD cbSize = 0;
DWORD dwIndex = 0;
PCERT_NAME_VALUE pTemplateName = NULL;
CERT_ENHKEY_USAGE *pEnhKeyUsage=NULL;
if(NULL == (pCertContext->pCertInfo))
goto Cleanup;
//find the EKU extension
pExtension =CertFindExtension(szOID_ENHANCED_KEY_USAGE,
pCertContext->pCertInfo->cExtension,
pCertContext->pCertInfo->rgExtension);
if(pExtension)
{
if(CryptDecodeObject(X509_ASN_ENCODING,
X509_ENHANCED_KEY_USAGE,
pExtension->Value.pbData,
pExtension->Value.cbData,
0,
NULL,
&cbSize))
{
pEnhKeyUsage=(CERT_ENHKEY_USAGE *)MIDL_user_allocate(cbSize);
if(pEnhKeyUsage)
{
if(CryptDecodeObject(X509_ASN_ENCODING,
X509_ENHANCED_KEY_USAGE,
pExtension->Value.pbData,
pExtension->Value.cbData,
0,
pEnhKeyUsage,
&cbSize))
{
for(dwIndex=0; dwIndex < pEnhKeyUsage->cUsageIdentifier; dwIndex++)
{
if(0 == strcmp(szOID_KP_SMARTCARD_LOGON,
(pEnhKeyUsage->rgpszUsageIdentifier)[dwIndex]))
{
//we find it
fDCCert=TRUE;
break;
}
}
}
}
}
}
//check if we have found it via the enhanced key usage extension
if(fDCCert)
goto Cleanup;
//find the V1 template extension
pExtension =CertFindExtension(szOID_ENROLL_CERTTYPE_EXTENSION,
pCertContext->pCertInfo->cExtension,
pCertContext->pCertInfo->rgExtension);
if(pExtension == NULL)
goto Cleanup;
cbSize=0;
if(!CryptDecodeObject(X509_ASN_ENCODING,
X509_UNICODE_ANY_STRING,
pExtension->Value.pbData,
pExtension->Value.cbData,
0,
NULL,
&cbSize))
goto Cleanup;
pTemplateName = (CERT_NAME_VALUE *)MIDL_user_allocate(cbSize);
if(NULL == pTemplateName)
goto Cleanup;
if(!CryptDecodeObject(X509_ASN_ENCODING,
X509_UNICODE_ANY_STRING,
pExtension->Value.pbData,
pExtension->Value.cbData,
0,
pTemplateName,
&cbSize))
goto Cleanup;
if(wcscmp((LPWSTR) pTemplateName->Value.pbData, wszCERTTYPE_DC) != 0)
goto Cleanup;
fDCCert=TRUE;
Cleanup:
if(pEnhKeyUsage)
MIDL_user_free(pEnhKeyUsage);
if(pTemplateName)
MIDL_user_free(pTemplateName);
return fDCCert;
}
//+-------------------------------------------------------------------------
//
// Function: KdcMyStoreWaitHandler
//
// Synopsis: Retrieves a copy of the KDC cert
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KdcMyStoreWaitHandler(
PVOID pVoid,
BOOLEAN fTimeout
)
{
PCCERT_CONTEXT Certificate = NULL, OldCertificate = NULL;
CERT_CHAIN_POLICY_STATUS FinalChainStatus = {0};
KERB_EXT_ERROR DummyError;
KERBERR KerbErr;
BOOLEAN DummyBool, Found = FALSE;
BOOLEAN UsedPrexistingCertificate = FALSE;
ULONG PropertySize = 0;
// Diagnostic: When's the last time this event fired?
GetSystemTimeAsFileTime((PFILETIME) &KdcLastChangeEventTime);
//
// This was triggered by a timeout, so disable the store notification
// for now...
//
if (fTimeout)
{
if (!CertControlStore(
KdcCertStore, // in, the store to be controlled
0, // in, not used.
CERT_STORE_CTRL_CANCEL_NOTIFY,
&KdcCertStoreChangeEvent
))
{
D_DebugLog((DEB_ERROR, "CertControlStore (cancel notify) failed - %x\n", GetLastError()));
}
}
D_DebugLog((DEB_T_PKI, "Triggering KdcMyStoreWaitHandler()\n"));
//
// Resync store
//
CertControlStore(
KdcCertStore, // in, the store to be controlled
0, // in, not used.
CERT_STORE_CTRL_RESYNC, // in, control action type
NULL // Just resync store
);
RtlEnterCriticalSection(&KdcGlobalCertCritSect);
// Our my store changed, so we need to find the cert again.
if(GlobalKdcCert)
{
OldCertificate = GlobalKdcCert;
KerbErr = KdcCheckCertificate(
GlobalKdcCert,
&DummyBool,
&DummyError,
&FinalChainStatus,
TRUE // this is a kdc certificate
);
if (!KERB_SUCCESS(KerbErr))
{
GlobalKdcCert = NULL;
}
else
{
// certificate is good!
// However, it may have been deleted, so
// verify its existance
while ((Certificate = CertEnumCertificatesInStore(
KdcCertStore,
Certificate)) != NULL)
{
if (CertCompareCertificate(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
GlobalKdcCert->pCertInfo,
Certificate->pCertInfo
))
{
Found = TRUE;
break; // still there
}
}
if (NULL != Certificate)
{
CertFreeCertificateContext(Certificate);
Certificate = NULL;
}
if (Found)
{
goto Rearm;
}
GlobalKdcCert = NULL;
}
}
if (NULL == GlobalKdcCert)
{
//
// Enumerate all the certificates looking for the one we want
//
while ((Certificate = CertEnumCertificatesInStore(
KdcCertStore,
Certificate)) != NULL)
{
//
// Check to see if the certificate is the one we want
//
if (!CertGetCertificateContextProperty(
Certificate,
CERT_KEY_PROV_INFO_PROP_ID,
NULL, // no data
&PropertySize))
{
continue;
}
//
// Make sure the certificate is indeed a domain conroller cert
if(!VerifyDCCertificate(Certificate))
{
continue;
}
//
// Make sure the cert we selected was "good"
//
KerbErr = KdcCheckCertificate(
Certificate,
&DummyBool,
&DummyError,
NULL,
TRUE // this is a kdc certificate
);
if (!KERB_SUCCESS(KerbErr))
{
continue;
}
break;
}
}
// Couldn't find a good certificate!
if (NULL == Certificate)
{
DebugLog((DEB_ERROR, "No valid KDC certificate was available\n"));
//
// Keep the old one... We might just be getting an offline CA
//
if (OldCertificate != NULL)
{
DebugLog((DEB_T_PKI, "Re-using old certificate\n"));
GlobalKdcCert = OldCertificate;
if ((KDCInfoLevel & DEB_T_PKI) != 0)
{
ReportServiceEvent(
EVENTLOG_WARNING_TYPE,
KDCEVENT_INVALID_KDC_CERTIFICATE,
sizeof(FinalChainStatus) - sizeof(void*), // don't need ptr.
&FinalChainStatus,
0
);
}
}
else
//
// Never had one...
//
{
GlobalKdcCert = NULL;
}
}
else
{
D_DebugLog((DEB_T_PKI, "Picked new KDC certificate\n"));
GlobalKdcCert = Certificate;
if (OldCertificate != NULL)
{
CertFreeCertificateContext(OldCertificate);
}
}
Rearm:
RtlLeaveCriticalSection(&KdcGlobalCertCritSect);
//
// This was moved here because of race conditions associated w/ my store
// chain building, where the event was getting fired rapidly, leading
// us to loose notification, and thus- the re-arm.
//
CertControlStore(
KdcCertStore, // in, the store to be controlled
0, // in, not used.
CERT_STORE_CTRL_NOTIFY_CHANGE, // in, control action type
&KdcCertStoreChangeEvent // in, the handle of the event
);
}
//+-------------------------------------------------------------------------
//
// Function: KdcGetKdcCertificate
//
// Synopsis: Retrieves a copy of the KDC cert
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcGetKdcCertificate(
PCCERT_CONTEXT *KdcCert
)
{
NTSTATUS Status = STATUS_SUCCESS;
if(!KdcGlobalCertCritSectInitialized)
{
return STATUS_OBJECT_NAME_NOT_FOUND;
}
RtlEnterCriticalSection(&KdcGlobalCertCritSect);
if (GlobalKdcCert == NULL)
{
DebugLog((DEB_WARN,"Unable to find KDC certificate in KDC store\n"));
Status = STATUS_OBJECT_NAME_NOT_FOUND;
goto Cleanup;
}
// Increment the ref count, so if we change certs while the caller of this
// is still using this cert, we won't delete it out from under.
*KdcCert = CertDuplicateCertificateContext(GlobalKdcCert);
if(*KdcCert == NULL)
{
Status = STATUS_OBJECT_NAME_NOT_FOUND;
goto Cleanup;
}
Cleanup:
RtlLeaveCriticalSection(&KdcGlobalCertCritSect);
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcInitializeCerts
//
// Synopsis: Initializes data for cert handling
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KdcInitializeCerts(
VOID
)
{
NTSTATUS Status = STATUS_SUCCESS;
PCCERT_CONTEXT Certificate = NULL;
ULONG Index;
LPSTR TempString = NULL, StringCopy = NULL, EndPtr = NULL;
ULONG TenHours;
TenHours = (ULONG) 1000 * 60 * 60 * 10;
Status = RtlInitializeCriticalSection(&KdcGlobalCertCritSect);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
KdcGlobalCertCritSectInitialized = TRUE;
if (!CryptAcquireContext(
&KdcClientProvider,
NULL, // default container
NULL,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT
))
{
Status = GetLastError();
DebugLog((DEB_ERROR,"Failed to acquire client crypt context: 0x%x\n",Status));
goto Cleanup;
}
//
// Open the KDC store to get the KDC cert
//
KdcCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0, // no encoding
NULL, // no provider
CERT_STORE_OPEN_EXISTING_FLAG |
CERT_STORE_NO_CRYPT_RELEASE_FLAG |
CERT_SYSTEM_STORE_LOCAL_MACHINE,
KDC_PRIVATE_MY_STORE
);
if (KdcCertStore == NULL)
{
Status = GetLastError();
DebugLog((DEB_ERROR,"Failed to open %ws store: 0x%x\n", KDC_PRIVATE_MY_STORE,Status));
Status = STATUS_OBJECT_NAME_NOT_FOUND;
goto Cleanup;
}
// Create an auto-reset event that is to be signaled when
// the my store is changed. This event is initialized to Signaled
// so that on first call to get a cert, we assume the my store is changed
// and do all the work.
KdcCertStoreChangeEvent = CreateEvent(
NULL,
FALSE,
FALSE,
NULL);
if(NULL == KdcCertStoreChangeEvent)
{
Status = GetLastError();
goto Cleanup;
}
if (! RegisterWaitForSingleObject(&KdcCertStoreWait,
KdcCertStoreChangeEvent,
KdcMyStoreWaitHandler,
NULL,
TenHours,
WT_EXECUTEDEFAULT
))
{
Status = GetLastError();
goto Cleanup;
}
// Arm the cert store for change notification
// CERT_CONTROL_STORE_NOTIFY_CHANGE.
if(!CertControlStore(
KdcCertStore, // The store to be controlled
0, // Not used
CERT_STORE_CTRL_NOTIFY_CHANGE, // Control action type
&KdcCertStoreChangeEvent)) // Points to the event handle.
// When a change is detected,
// a signal is written to the
// space pointed to by
// hHandle.
{
// Notification is not avaialble, so kill the Event
Status = GetLastError();
goto Cleanup;
}
// Initialize the GlobalCert
KdcMyStoreWaitHandler (NULL, TRUE);
//
// Initialize the object IDs
//
Index = 0;
StringCopy = (LPSTR) MIDL_user_allocate(strlen(KERB_PKINIT_SIGNATURE_OID)+1);
if (StringCopy == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Scan the string for every '.' separated number
//
strcpy(
StringCopy,
KERB_PKINIT_SIGNATURE_OID
);
TempString = StringCopy;
EndPtr = TempString;
while (TempString != NULL)
{
ULONG Temp;
while (*EndPtr != '\0' && *EndPtr != '.')
{
EndPtr++;
}
if (*EndPtr == '.')
{
*EndPtr = '\0';
EndPtr++;
}
else
{
EndPtr = NULL;
}
sscanf(TempString,"%u",&Temp);
KdcSignatureAlg[Index].value = (USHORT) Temp;
KdcSignatureAlg[Index].next = &KdcSignatureAlg[Index+1];
Index++;
TempString = EndPtr;
}
DsysAssert(Index != 0);
KdcSignatureAlg[Index-1].next = NULL;
MIDL_user_free(StringCopy);
StringCopy = NULL;
Cleanup:
if (!NT_SUCCESS(Status))
{
KdcCleanupCerts(FALSE);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KdcCleanupCerts
//
// Synopsis: Cleans up data associated with certificate handling
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KdcCleanupCerts(
IN BOOLEAN CleanupScavenger
)
{
HANDLE WaitHandle;
//
// Pete code used to hold the critsec in the callback.
//
if(KdcCertStoreWait)
{
WaitHandle = (HANDLE) InterlockedExchangePointer(&KdcCertStoreWait,NULL);
UnregisterWaitEx(WaitHandle, INVALID_HANDLE_VALUE);
}
if(KdcGlobalCertCritSectInitialized)
{
RtlEnterCriticalSection(&KdcGlobalCertCritSect);
}
if (GlobalKdcCert != NULL)
{
CertFreeCertificateContext(
GlobalKdcCert
);
GlobalKdcCert = NULL;
}
if (KdcCertStore != NULL)
{
CertCloseStore(
KdcCertStore,
CERT_CLOSE_STORE_FORCE_FLAG
);
KdcCertStore = NULL;
}
if(KdcCertStoreChangeEvent)
{
CloseHandle(KdcCertStoreChangeEvent);
KdcCertStoreChangeEvent = NULL;
}
if (KdcClientProvider != NULL)
{
CryptReleaseContext(
KdcClientProvider,
0 // no flags
);
KdcClientProvider = NULL;
}
if(KdcGlobalCertCritSectInitialized)
{
RtlLeaveCriticalSection(&KdcGlobalCertCritSect);
RtlDeleteCriticalSection(&KdcGlobalCertCritSect);
KdcGlobalCertCritSectInitialized = FALSE;
}
}