//+----------------------------------------------------------------------- // // 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 // for SECURITY_FLAG_xxx #include // ScHelperXXX #include // for CryptUiXXX #include // 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"" #define CCH_ISSUER_HEADER 3 #define SUBJECT_HEADER L"" #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 issuer 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; } }