|
|
//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File: pkauth.cxx
//
// Contents: Routines for supporting public-key authentication
//
//
// History: 14-October-1997 Created MikeSw
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
//#ifndef WIN32_CHICAGO
extern "C" { #include <stdlib.h>
#include <cryptdll.h>
} //#endif // WIN32_CHICAGO
#ifdef RETAIL_LOG_SUPPORT
static TCHAR THIS_FILE[]=TEXT(__FILE__); #endif
KERB_OBJECT_ID KerbSignatureAlg[10]; #define KERB_SCLOGON_DOMAIN_SUFFIX L"-sclogon"
#define KERB_SCLOGON_DOMAIN_SUFFIX_SIZE (sizeof(KERB_SCLOGON_DOMAIN_SUFFIX) - sizeof(WCHAR))
#ifndef SHA1DIGESTLEN
#define SHA1DIGESTLEN 20
#endif
NTSTATUS KerbInitializeHProvFromCert( IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds );
//+-------------------------------------------------------------------------
//
// Function: KerbComparePublicKeyCreds
//
// Synopsis: Verfies a certificate is valid for the specified usage
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOL KerbComparePublicKeyCreds( IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds1, IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds2 ) {
return CertCompareCertificate( X509_ASN_ENCODING, PkCreds1->CertContext->pCertInfo, PkCreds2->CertContext->pCertInfo );
// more later?
//return (fRet);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCheckCertificate
//
// Synopsis: Verfies a certificate is valid for the specified usage
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbCheckCertificate( IN PCCERT_CONTEXT CertContext, IN LPSTR Usage, IN BOOLEAN LocalLogon // AllowRevocationCheckFailure
) { NTSTATUS Status = STATUS_SUCCESS; CERT_CHAIN_PARA ChainParameters = {0}; PCCERT_CHAIN_CONTEXT ChainContext = NULL;
ChainParameters.cbSize = sizeof(CERT_CHAIN_PARA); ChainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; ChainParameters.RequestedUsage.Usage.cUsageIdentifier = 1; ChainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = &Usage;
if (!CertGetCertificateChain( HCCE_LOCAL_MACHINE, CertContext, NULL, // evaluate at current time
NULL, // no additional stores
&ChainParameters, (LocalLogon? CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY|CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT: CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT), NULL, // reserved
&ChainContext )) { DebugLog((DEB_WARN,"Failed to verify certificate chain: %0x%x\n",GetLastError())); Status = STATUS_PKINIT_FAILURE; } else { CERT_CHAIN_POLICY_PARA ChainPolicy; CERT_CHAIN_POLICY_STATUS PolicyStatus; ZeroMemory(&ChainPolicy, sizeof(ChainPolicy));
ChainPolicy.cbSize = sizeof(ChainPolicy); if (LocalLogon) { ChainPolicy.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS; }
ZeroMemory(&PolicyStatus, sizeof(PolicyStatus)); PolicyStatus.cbSize = sizeof(PolicyStatus); PolicyStatus.lChainIndex = -1; PolicyStatus.lElementIndex = -1;
if (!CertVerifyCertificateChainPolicy( CERT_CHAIN_POLICY_BASE, ChainContext, &ChainPolicy, &PolicyStatus)) { DebugLog((DEB_WARN,"CertVerifyCertificateChainPolicy failure: %0x%x\n", GetLastError())); Status = STATUS_PKINIT_FAILURE; }
if(PolicyStatus.dwError != S_OK) { DebugLog((DEB_WARN,"CertVerifyCertificateChainPolicy - Chain Status failure: %0x%x\n",PolicyStatus.dwError)); KerbReportPkinitError( PolicyStatus.dwError, CertContext ); Status = STATUS_PKINIT_FAILURE; } }
if (ChainContext != NULL) { CertFreeCertificateChain(ChainContext); }
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbVerifyPkAsReply
//
// Synopsis: Verifies the reply from the KDC and retrieves the
// ticket encryption key
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbVerifyPkAsReply( IN PKERB_PA_DATA_LIST InputPaData, IN PKERB_PRIMARY_CREDENTIAL Credentials, IN ULONG Nonce, OUT PKERB_ENCRYPTION_KEY EncryptionKey, OUT PBOOLEAN Done ) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr = KDC_ERR_NONE; PKERB_PA_PK_AS_REP Reply = NULL; PCCERT_CONTEXT KdcCertContext = NULL; PBYTE EncodedKeyPackage = NULL; ULONG KeyPackageSize = 0; PKERB_SIGNED_REPLY_KEY_PACKAGE KeyPackage = NULL; PKERB_REPLY_KEY_PACKAGE ReplyKeyPackage = NULL; PBYTE PackedKeyPack = NULL; ULONG PackedKeyPackSize = 0; HCRYPTKEY PrivateKey = NULL; PKERB_ENCRYPTION_KEY TempKey = NULL; HCRYPTPROV KdcProvider = NULL; BOOLEAN InitializedPkCreds = FALSE;
NTSTATUS TokenStatus = STATUS_SUCCESS; HANDLE ImpersonationToken = NULL;
*Done = TRUE;
//
// Unpack the request
//
KerbErr = KerbUnpackData( InputPaData->value.preauth_data.value, InputPaData->value.preauth_data.length, KERB_PA_PK_AS_REP_PDU, (PVOID *) &Reply ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
if (Reply->choice != key_package_chosen) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
//
// Now we need to verify the signature on the message
//
//
// Make sure the csp data is available
//
if ((Credentials->PublicKeyCreds->InitializationInfo & CSP_DATA_INITIALIZED) == 0) { Status = KerbInitializePkCreds( Credentials->PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; } InitializedPkCreds = TRUE;
} else if ((Credentials->PublicKeyCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS) != 0) { // need to set the PIN and this function does that
Status = KerbInitializeHProvFromCert( Credentials->PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
//
// Decode the contents as an encrypted data buffer
//
Status = __ScHelperDecryptMessage( &Credentials->PublicKeyCreds->Pin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->hProv, Credentials->PublicKeyCreds->CertContext, Reply->u.key_package.value, Reply->u.key_package.length, EncodedKeyPackage, &KeyPackageSize );
if ((Status != STATUS_BUFFER_TOO_SMALL) && (Status != STATUS_SUCCESS)) { DebugLog((DEB_ERROR,"Failed to decrypt pkcs message: %x\n",Status)); goto Cleanup; }
EncodedKeyPackage = (PBYTE) KerbAllocate(KeyPackageSize); if (EncodedKeyPackage == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = __ScHelperDecryptMessage( &Credentials->PublicKeyCreds->Pin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->hProv, Credentials->PublicKeyCreds->CertContext, Reply->u.key_package.value, Reply->u.key_package.length, EncodedKeyPackage, &KeyPackageSize );
if (Status != STATUS_SUCCESS) { DebugLog((DEB_ERROR,"Failed to decrypt pkcs message: %x\n",Status)); goto Cleanup; }
//
// Verify the signature
//
Status = ScHelperVerifyPkcsMessage( Credentials->PublicKeyCreds->CspData, NULL, // we don't care which CSP is used for the verification
EncodedKeyPackage, KeyPackageSize, PackedKeyPack, &PackedKeyPackSize, NULL // don't return certificate context
); if ((Status != STATUS_BUFFER_TOO_SMALL) && (Status != STATUS_SUCCESS)) { DebugLog((DEB_ERROR,"Failed to verify message: %x\n",Status)); goto Cleanup; }
PackedKeyPack = (PBYTE) MIDL_user_allocate(PackedKeyPackSize); if (PackedKeyPack == NULL) { KerbErr = KRB_ERR_GENERIC; Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Status = ScHelperVerifyPkcsMessage( Credentials->PublicKeyCreds->CspData, NULL, // we don't care which CSP is used for the verification
EncodedKeyPackage, KeyPackageSize, PackedKeyPack, &PackedKeyPackSize, &KdcCertContext ); if (Status != STATUS_SUCCESS) { DebugLog((DEB_ERROR,"Failed to verify message: %x\n",Status)); goto Cleanup; }
KerbErr = KerbUnpackData( PackedKeyPack, PackedKeyPackSize, KERB_REPLY_KEY_PACKAGE_PDU, (PVOID *) &ReplyKeyPackage ); if (!KERB_SUCCESS(KerbErr)) { D_DebugLog((DEB_ERROR,"Failed to unpack reply key package\n")); Status = KerbMapKerbError(KerbErr); goto Cleanup; } if (Nonce != (ULONG) ReplyKeyPackage->nonce) { D_DebugLog((DEB_ERROR,"Returned nonce is not correct: 0x%x instead of 0x%x. %ws, line %d\n", ReplyKeyPackage->nonce, Nonce, THIS_FILE, __LINE__ )); Status = STATUS_LOGON_FAILURE; goto Cleanup; }
//
// Finally, copy the encryption key out and return it.
//
if (!KERB_SUCCESS(KerbDuplicateKey( EncryptionKey, &ReplyKeyPackage->reply_key ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Verify the certificate
//
// If we're impersonating, revert, and save off old token. This keeps us from
// going recursive.
//
// Are we impersonating?
//
TokenStatus = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &ImpersonationToken );
if( NT_SUCCESS(TokenStatus) ) { RevertToSelf(); } else if (TokenStatus != STATUS_NO_TOKEN) { Status = TokenStatus; goto Cleanup; }
Status = KerbCheckCertificate( KdcCertContext, KERB_PKINIT_KDC_CERT_TYPE, FALSE // don't allow revocation failures
);
//
// re-impersonate
//
if( ImpersonationToken != NULL ) {
//
// put the thread token back if we were impersonating.
//
SetThreadToken( NULL, ImpersonationToken ); NtClose( ImpersonationToken ); }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to verify KDC certificate: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
Cleanup:
//
// If we initialized these, reset them
//
if (InitializedPkCreds) { KerbReleasePkCreds( NULL, Credentials->PublicKeyCreds ); }
if (Reply != NULL) { KerbFreeData( KERB_PA_PK_AS_REP_PDU, Reply ); }
if (KdcCertContext != NULL) { CertFreeCertificateContext(KdcCertContext); } if (KeyPackage != NULL) { KerbFreeData( KERB_SIGNED_REPLY_KEY_PACKAGE_PDU, KeyPackage ); } if (ReplyKeyPackage != NULL) { KerbFreeData( KERB_REPLY_KEY_PACKAGE_PDU, ReplyKeyPackage ); } if (PackedKeyPack != NULL) { MIDL_user_free(PackedKeyPack); } if (PrivateKey != NULL) { CryptDestroyKey(PrivateKey); } if (TempKey != NULL) { KerbFreeData( KERB_ENCRYPTION_KEY_PDU, TempKey ); } if (KdcProvider != NULL) { CryptReleaseContext( KdcProvider, 0 // no flags
); } if (EncodedKeyPackage != NULL) { KerbFree(EncodedKeyPackage); }
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetUserCertificates
//
// Synopsis: Gets a list of the user certificates
//
// Effects:
//
// Arguments: Credentials - client's credentials containing certificate
// Certficates - receives list of certificates.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetUserCertificates( IN PKERB_PRIMARY_CREDENTIAL Credentials, OUT PKERB_CERTIFICATE_LIST * Certificates ) { NTSTATUS Status = STATUS_SUCCESS;
if (!KERB_SUCCESS(KerbCreateCertificateList( Certificates, Credentials->PublicKeyCreds->CertContext ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
Cleanup: return(Status);
}
#if 0 // could not find any users - markpu - 04/19/2001 - will remove later
//+-------------------------------------------------------------------------
//
// Function: KerbGetTrustedCertifiers
//
// Synopsis: Gets the list of trusted certifiers for this machine
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetTrustedCertifiers( IN PKERB_PRIMARY_CREDENTIAL Credentials, OUT PKERB_CERTIFIER_LIST * Certifiers ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_CERTIFIER_LIST ListEntry = NULL;
//
// Build a dummy list entry
//
ListEntry = (PKERB_CERTIFIER_LIST) KerbAllocate(sizeof(KERB_CERTIFIER_LIST)); if (ListEntry == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
if (!KERB_SUCCESS(KerbConvertStringToPrincipalName( &ListEntry->value, &KerbGlobalKdcServiceName, KRB_NT_PRINCIPAL ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } *Certifiers = ListEntry; ListEntry = NULL; Cleanup: if (ListEntry != NULL) { KerbFreePrincipalName( &ListEntry->value ); KerbFree(ListEntry); } return(Status); }
#endif
//+-------------------------------------------------------------------------
//
// Function: KerbFreePKCreds
//
// Synopsis: Frees the public key creds
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbFreePKCreds( IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds ) { if (NULL != PkCreds) { if (((PkCreds->InitializationInfo & CSP_DATA_INITIALIZED) != 0) && ((PkCreds->InitializationInfo & (CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS | CONTEXT_INITIALIZED_WITH_ACH)) == 0)) { __ScHelperRelease( PkCreds->CspData );
PkCreds->InitializationInfo &= ~CSP_DATA_INITIALIZED; } if (PkCreds->hProv != NULL) { CryptReleaseContext(PkCreds->hProv, 0); PkCreds->hProv = NULL; } if (PkCreds->CertContext != NULL) { CertFreeCertificateContext(PkCreds->CertContext); PkCreds->CertContext = NULL; } KerbFreeString(&PkCreds->Pin); KerbFree(PkCreds); } }
//+-------------------------------------------------------------------------
//
// Function: KerbInitializeHProvFromCert
//
// Synopsis: Initializes the out parameter phProv by getting the key
// prov info from the cert context and acquiring a CSP context
// given this information.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbInitializeHProvFromCert( IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds ) { ULONG cPin; LPWSTR pwszPin = NULL; LPSTR pszPin = NULL; NTSTATUS Status = STATUS_SUCCESS;
if (!CryptAcquireCertificatePrivateKey( PkCreds->CertContext, CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_SILENT, NULL, &PkCreds->hProv, NULL, NULL )) { DebugLog((DEB_ERROR, "CryptAcquireCertificatePrivateKey failed - %x\n", GetLastError())); Status = STATUS_SMARTCARD_SUBSYSTEM_FAILURE; goto Cleanup; }
//
// Convert the pin to ANSI, but only for creds acquired by ACH, as the
// credman isn't "allowed" to cache pins anymore..
//
if (( PkCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_ACH ) != 0) {
if (0 == PkCreds->Pin.Length) { Status = STATUS_LOGON_FAILURE; goto Cleanup; } pwszPin = (LPWSTR)KerbAllocate(PkCreds->Pin.Length + sizeof(WCHAR)); if (NULL == pwszPin) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } RtlCopyMemory(pwszPin, PkCreds->Pin.Buffer, PkCreds->Pin.Length); pwszPin[PkCreds->Pin.Length / sizeof(WCHAR)] = L'\0';
cPin = WideCharToMultiByte( GetACP(), 0, pwszPin, -1, NULL, 0, NULL, NULL);
pszPin = (LPSTR)KerbAllocate((cPin + 1) * sizeof(CHAR)); if (NULL == pszPin) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
cPin = WideCharToMultiByte( GetACP(), 0, pwszPin, -1, pszPin, cPin, NULL, NULL);
if (!CryptSetProvParam( PkCreds->hProv, PP_KEYEXCHANGE_PIN, (LPBYTE)pszPin, 0 )) { Status = STATUS_LOGON_FAILURE; goto Cleanup; } }
Cleanup: if (NULL != pwszPin) { KerbFree(pwszPin); } if (NULL != pszPin) { KerbFree(pszPin); } return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbInitializePkCreds
//
// Synopsis: Initializes or re-initailizes the smart card data in
// the public key creds
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbInitializePkCreds( IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds ) { NTSTATUS Status = STATUS_SUCCESS;
if ((PkCreds->InitializationInfo & CSP_DATA_INITIALIZED) == 0) {
//
// check if we are using cred man creds (already have a cert context)
//
if (((PkCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS) == 0) && ((PkCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_ACH) == 0)) { Status = __ScHelperInitializeContext( PkCreds->CspData, PkCreds->CspDataLength ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"ScHelperInitializeContext failed- %x\n", Status)); goto Cleanup; } PkCreds->InitializationInfo |= CSP_DATA_INITIALIZED; } else { if (PkCreds->CertContext == NULL) { D_DebugLog((DEB_ERROR,"Using cred man creds but cert context is NULL.\n")); Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
PkCreds->InitializationInfo |= CSP_DATA_INITIALIZED; }
}
if (PkCreds->CertContext == NULL) { Status = __ScHelperGetCertFromLogonInfo( PkCreds->CspData, &PkCreds->Pin, &PkCreds->CertContext );
if (Status != STATUS_SUCCESS) { DebugLog((DEB_ERROR,"Failed to get cert from logon info: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); if (NT_SUCCESS(Status)) { Status = STATUS_LOGON_FAILURE; } goto Cleanup; }
}
Cleanup: if (!NT_SUCCESS(Status)) { if (((PkCreds->InitializationInfo & CSP_DATA_INITIALIZED) != 0) && ((PkCreds->InitializationInfo & ( CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS | CONTEXT_INITIALIZED_WITH_ACH)) == 0)) { __ScHelperRelease( PkCreds->CspData );
PkCreds->InitializationInfo &= ~CSP_DATA_INITIALIZED; } } return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbReleasePkCreds
//
// Synopsis: Releaes smart-card resources in the public key creds.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbReleasePkCreds( IN OPTIONAL PKERB_LOGON_SESSION LogonSession, IN OPTIONAL PKERB_PUBLIC_KEY_CREDENTIALS PkCreds ) {
if (ARGUMENT_PRESENT(LogonSession)) { KerbWriteLockLogonSessions( LogonSession ); PkCreds = LogonSession->PrimaryCredentials.PublicKeyCreds; }
KerbFreePKCreds(PkCreds);
if (ARGUMENT_PRESENT(LogonSession)) { LogonSession->PrimaryCredentials.PublicKeyCreds = NULL; KerbUnlockLogonSessions( LogonSession ); } }
//+-------------------------------------------------------------------------
//
// Function: KerbComputePkAuthenticatorSignature
//
// Synopsis: Computes the signature of the PK authenticator by
// marshalling the authenticator, checksumming it, then
// encrypting the checksum with the public key, more or less
//
// Effects:
//
// Arguments: AuthPackage - authenticator to sign
// Credentials - Client's credentials (containing keys)
// Signature - receives signature
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbComputePkAuthenticatorSignature( IN PKERB_AUTH_PACKAGE AuthPackage, IN PKERB_PRIMARY_CREDENTIAL Credentials, OUT PKERB_SIGNATURE Signature ) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr = KDC_ERR_NONE; PBYTE PackedAuthenticator = NULL; ULONG PackedAuthenticatorSize; BOOLEAN InitializedPkCreds = FALSE; PUNICODE_STRING TmpPin = NULL;
#define KERB_PK_MAX_SIGNATURE_SIZE 128
BYTE PkSignature[KERB_PK_MAX_SIGNATURE_SIZE]; ULONG PkSignatureLength = KERB_PK_MAX_SIGNATURE_SIZE;
RtlZeroMemory( Signature, sizeof(KERB_SIGNATURE) );
//
// First marshall the auth package
//
KerbErr = KerbPackData( AuthPackage, KERB_AUTH_PACKAGE_PDU, &PackedAuthenticatorSize, &PackedAuthenticator ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Make sure the csp data is available
//
if ((Credentials->PublicKeyCreds->InitializationInfo & CSP_DATA_INITIALIZED) == 0) { Status = KerbInitializePkCreds( Credentials->PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
InitializedPkCreds = TRUE;
} else if (((Credentials->PublicKeyCreds->InitializationInfo & (CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS | CONTEXT_INITIALIZED_WITH_ACH)) != 0))
{ // need to set the PIN and this function does that
Status = KerbInitializeHProvFromCert( Credentials->PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
// Initialize the PIN for ScHelperSignPkcs routines.
if (((Credentials->PublicKeyCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS) == 0) && (Credentials->PublicKeyCreds->Pin.Buffer != NULL)) { TmpPin = &Credentials->PublicKeyCreds->Pin; }
//
// Now generate the checksum
//
Status = __ScHelperSignMessage( TmpPin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->hProv, KERB_PKINIT_SIGNATURE_ALG, PackedAuthenticator, PackedAuthenticatorSize, PkSignature, &PkSignatureLength ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to sign message with card: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
//
// Build the signature
//
Signature->signature_algorithm.algorithm = KerbSignatureAlg;
//
// Copy the temporary signature into the return structure
//
Signature->pkcs_signature.length = PkSignatureLength * 8; // because it is a bit string
Signature->pkcs_signature.value = (PBYTE) KerbAllocate( PkSignatureLength ); if (Signature->pkcs_signature.value == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
RtlCopyMemory( Signature->pkcs_signature.value, PkSignature, PkSignatureLength );
Status = STATUS_SUCCESS;
Cleanup: if (InitializedPkCreds) { KerbReleasePkCreds( NULL, Credentials->PublicKeyCreds );
}
if (PackedAuthenticator != NULL) { MIDL_user_free(PackedAuthenticator); } return(Status);
}
NTSTATUS KerbGetProvParamWrapper( IN PUNICODE_STRING pPin, IN PBYTE pbLogonInfo, IN OPTIONAL HCRYPTPROV hProv, DWORD dwParam, BYTE*pbData, DWORD *pdwDataLen, DWORD dwFlags ) { NTSTATUS Status = STATUS_SUCCESS;
if (NULL != hProv) { if (!CryptGetProvParam( hProv, dwParam, pbData, pdwDataLen, dwFlags )) { DebugLog((DEB_ERROR, "Failure in SC subsytem - %x\n",GetLastError())); Status = STATUS_SMARTCARD_SUBSYSTEM_FAILURE; goto Cleanup; } } else { Status = __ScHelperGetProvParam( pPin, pbLogonInfo, dwParam, pbData, pdwDataLen, dwFlags ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Failure in SC subsytem - %x\n",Status)); }
} Cleanup: return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbGetSmartCardAlgorithms
//
// Synopsis: Gets the supported encryption types from the
// smart card provider
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetSmartCardAlgorithms( IN PKERB_PRIMARY_CREDENTIAL Credentials, OUT PKERB_CRYPT_LIST * CryptList ) { NTSTATUS Status = STATUS_SUCCESS; PROV_ENUMALGS Data; ULONG DataSize; ULONG Flags = CRYPT_FIRST; #define KERB_SUPPORTED_PK_CRYPT_COUNT 2
ULONG CryptTypes[KERB_SUPPORTED_PK_CRYPT_COUNT]; ULONG CryptCount = 0;
//
// Enumerate through to get the encrypt types
//
while (1) { DataSize = sizeof(Data); Status = KerbGetProvParamWrapper( &Credentials->PublicKeyCreds->Pin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->hProv, PP_ENUMALGS, (BYTE *) &Data, &DataSize, Flags );
if (Status == STATUS_NO_MORE_ENTRIES) { Status = STATUS_SUCCESS; break; }
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"GetProvPram failed: 0x%x\n", Status)); return(Status); }
//
// Reset the flags to enumerate though
//
Flags = 0; // CRYPT_NEXT
//
// Check if it is an encryption algorithm. We only want
// to know about 3des and RC4
//
if (GET_ALG_CLASS(Data.aiAlgid) == ALG_CLASS_DATA_ENCRYPT) { //
// Check the type
//
if (GET_ALG_TYPE(Data.aiAlgid) == ALG_TYPE_BLOCK) { //
// Check for 3des
//
if (GET_ALG_SID(Data.aiAlgid) == ALG_SID_3DES) { //
// Add it to the list.
//
CryptTypes[CryptCount++] = KERB_ETYPE_DES_EDE3_CBC_ENV; } else if (GET_ALG_SID(Data.aiAlgid) == ALG_SID_RC2) { //
// Add it to the list.
//
CryptTypes[CryptCount++] = KERB_ETYPE_RC2_CBC_ENV; }
} } if (CryptCount == KERB_SUPPORTED_PK_CRYPT_COUNT) { break; }
}
//
// Now, if there are any crypt types, convert them.
//
if (CryptCount != 0) { KERBERR KerbErr;
KerbErr = KerbConvertArrayToCryptList( CryptList, CryptTypes, CryptCount ); return(KerbMapKerbError(KerbErr)); } else { //
// We needed one of these, so bail now.
//
DebugLog((DEB_ERROR,"Smart card doesn't support rc2 or 3des for logon - failing out.\n"));
return(STATUS_CRYPTO_SYSTEM_INVALID); }
}
//+-------------------------------------------------------------------------
//
// Function: KerbBuildPkinitPreAuthData
//
// Synopsis: Builds the pre-auth data for a PK-INIT AS request
//
// Effects:
//
// Arguments: Credentials - Credentials to use for this request
// InputPaData - Any PA data returned from DC on previous
// call
// TimeSkew - Known time skew with KDC
// ServiceName - Name for which we are requesting a ticket
// RealmName - name of realm in which we are requesting a ticket
// PreAuthData - receives new PA data
// Done - if returned as TRUE, then routine need not be called
// again
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbBuildPkinitPreauthData( IN PKERB_PRIMARY_CREDENTIAL Credentials, IN OPTIONAL PKERB_PA_DATA_LIST InputPaData, IN PTimeStamp TimeSkew, IN PKERB_INTERNAL_NAME ServiceName, IN PUNICODE_STRING RealmName, IN ULONG Nonce, OUT PKERB_PA_DATA_LIST * PreAuthData, OUT PKERB_ENCRYPTION_KEY EncryptionKey, OUT PKERB_CRYPT_LIST * CryptList, OUT PBOOLEAN Done ) { NTSTATUS Status = STATUS_SUCCESS; KERB_PA_PK_AS_REQ Request = {0}; KERB_AUTH_PACKAGE AuthPack = {0}; PKERB_PA_DATA_LIST ListElement = NULL; ULONG PackedRequestSize = 0; PBYTE PackedRequest = NULL; PBYTE PackedAuthPack = NULL; ULONG PackedAuthPackSize = 0; PBYTE SignedAuthPack = NULL; ULONG SignedAuthPackSize = 0; TimeStamp TimeNow; KERBERR KerbErr; BOOLEAN InitializedPkCreds = FALSE; CRYPT_ALGORITHM_IDENTIFIER CryptAlg = {0}; PUNICODE_STRING TmpPin = NULL; HANDLE ClientTokenHandle = NULL; BOOLEAN Impersonating = FALSE;
//
// If we're using credman, we'll need to impersonate any time we make these calls.
//
if ( Credentials->PublicKeyCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS ) { Status = LsaFunctions->OpenTokenByLogonId( &Credentials->PublicKeyCreds->LogonId, &ClientTokenHandle ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Unable to get the client token handle.\n")); goto Cleanup; }
if(!SetThreadToken(NULL, ClientTokenHandle)) { D_DebugLog((DEB_ERROR,"Unable to impersonate the client token handle.\n")); Status = STATUS_CANNOT_IMPERSONATE; goto Cleanup; }
Impersonating = TRUE; }
//
// If there is any input, check to see if we succeeded the last time
// around
//
if (ARGUMENT_PRESENT(InputPaData)) { Status = KerbVerifyPkAsReply( InputPaData, Credentials, Nonce, EncryptionKey, Done ); goto Cleanup; }
//
// Make sure the csp data is available
//
if ((Credentials->PublicKeyCreds->InitializationInfo & CSP_DATA_INITIALIZED) == 0) { Status = KerbInitializePkCreds( Credentials->PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
InitializedPkCreds = TRUE;
} else if (((Credentials->PublicKeyCreds->InitializationInfo & (CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS | CONTEXT_INITIALIZED_WITH_ACH)) != 0)) { // need to set the PIN and this function does that
Status = KerbInitializeHProvFromCert( Credentials->PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
// Initialize the PIN for ScHelperSignPkcs routines.
if (((Credentials->PublicKeyCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS) == 0) && (Credentials->PublicKeyCreds->Pin.Buffer != NULL)) { TmpPin = &Credentials->PublicKeyCreds->Pin; }
Status = KerbGetSmartCardAlgorithms( Credentials, CryptList ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to get crypt list for smart card: 0x%x\n", Status)); goto Cleanup; }
//
// Do the new pa-pk-as-req
//
//
// Now comes the hard part - the PK authenticator
//
//
// First the KDC name
//
if (!KERB_SUCCESS( KerbConvertKdcNameToPrincipalName( &AuthPack.pk_authenticator.kdc_name, ServiceName ))) { Status = STATUS_SUCCESS; goto Cleanup; }
//
// Then the realm
//
if (!KERB_SUCCESS( KerbConvertUnicodeStringToRealm( &AuthPack.pk_authenticator.kdc_realm, RealmName))) { Status = STATUS_SUCCESS; goto Cleanup; }
//
// Now the time
//
GetSystemTimeAsFileTime((PFILETIME) &TimeNow);
#ifndef WIN32_CHICAGO
TimeNow.QuadPart += TimeSkew->QuadPart; #else // !WIN32_CHICAGO
TimeNow += *TimeSkew; #endif // WIN32_CHICAGO
KerbConvertLargeIntToGeneralizedTimeWrapper( &AuthPack.pk_authenticator.client_time, &AuthPack.pk_authenticator.cusec, &TimeNow);
//
// And finally the nonce
//
AuthPack.pk_authenticator.nonce = Nonce;
//
// Pack up the auth pack so we can sign it
//
KerbErr = KerbPackData( &AuthPack, KERB_AUTH_PACKAGE_PDU, &PackedAuthPackSize, &PackedAuthPack ); if (!KERB_SUCCESS(KerbErr)) { DebugLog((DEB_ERROR,"Failed to pack auth package\n")); Status = KerbMapKerbError(KerbErr); goto Cleanup; }
//
// Now sign it.
//
//
// Now generate the checksum
//
CryptAlg.pszObjId = KERB_PKINIT_SIGNATURE_OID;
Status = __ScHelperSignPkcsMessage( TmpPin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->hProv, Credentials->PublicKeyCreds->CertContext, &CryptAlg, CRYPT_MESSAGE_SILENT_KEYSET_FLAG, // dwSignMessageFlags
PackedAuthPack, PackedAuthPackSize, SignedAuthPack, &SignedAuthPackSize );
if ((Status != STATUS_BUFFER_TOO_SMALL) && (Status != STATUS_SUCCESS)) { DebugLog((DEB_ERROR,"Failed to sign message: %x\n",Status)); goto Cleanup; }
SignedAuthPack = (PBYTE) MIDL_user_allocate(SignedAuthPackSize); if (SignedAuthPack == NULL) { KerbErr = KRB_ERR_GENERIC; goto Cleanup; } Status = __ScHelperSignPkcsMessage( TmpPin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->hProv, Credentials->PublicKeyCreds->CertContext, &CryptAlg, CRYPT_MESSAGE_SILENT_KEYSET_FLAG, // dwSignMessageFlags
PackedAuthPack, PackedAuthPackSize, SignedAuthPack, &SignedAuthPackSize );
if (Status != STATUS_SUCCESS) { DebugLog((DEB_ERROR,"Failed to sign pkcs message: 0x%x\n",Status)); goto Cleanup; }
Request.signed_auth_pack.value = SignedAuthPack; Request.signed_auth_pack.length = SignedAuthPackSize;
//
// Marshall the request
//
if (!KERB_SUCCESS(KerbPackData( &Request, KERB_PA_PK_AS_REQ_PDU, &PackedRequestSize, &PackedRequest))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
ListElement = (PKERB_PA_DATA_LIST) KerbAllocate(sizeof(KERB_PA_DATA_LIST)); if (ListElement == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
ListElement->value.preauth_data_type = KRB5_PADATA_PK_AS_REP; ListElement->value.preauth_data.value = PackedRequest; ListElement->value.preauth_data.length = PackedRequestSize; PackedRequest = NULL;
ListElement->next = *PreAuthData; *PreAuthData = ListElement; ListElement = NULL;
Cleanup: KerbFreeRealm( &AuthPack.pk_authenticator.kdc_realm ); KerbFreePrincipalName( &AuthPack.pk_authenticator.kdc_name );
if (ListElement != NULL) { KerbFree(ListElement); } if (PackedRequest != NULL) { MIDL_user_free(PackedRequest); } if (PackedAuthPack != NULL) { MIDL_user_free(PackedAuthPack); } if (SignedAuthPack != NULL) { MIDL_user_free(SignedAuthPack); }
if ( Impersonating ) { RevertToSelf(); CloseHandle(ClientTokenHandle); }
if (InitializedPkCreds) { KerbReleasePkCreds( NULL, Credentials->PublicKeyCreds ); }
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbCreateSmartCardLogonSessionFromCertContext
//
// Synopsis: Creats a logon session from the cert context and passed in
// data. Retrieves the email name from the certificate.
//
// This function is for use with LogonUser when a marshalled
// smart card cert is passed in the user name and the PIN is
// passed as the password.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbCreateSmartCardLogonSessionFromCertContext( IN PCERT_CONTEXT *ppCertContext, IN PLUID pLogonId, IN PUNICODE_STRING pAuthorityName, IN PUNICODE_STRING pPin, IN PUCHAR pCspData, IN ULONG CspDataLength, OUT PKERB_LOGON_SESSION *ppLogonSession, OUT PUNICODE_STRING pAccountName ) { PKERB_LOGON_SESSION pLogonSession = NULL; PKERB_PUBLIC_KEY_CREDENTIALS PkCredentials = NULL; ULONG cbPkCreds = 0; NTSTATUS Status = STATUS_SUCCESS;
//
// Get the client name from the cert.
// Place it in the return location
//
Status = KerbGetPrincipalNameFromCertificate(*ppCertContext, pAccountName); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Create a normal logon session. We willa add the public-key information
// later
//
Status = KerbCreateLogonSession( pLogonId, pAccountName, pAuthorityName, NULL, // no password
NULL, // no old password
0, // no flags
Interactive, // logon type
&pLogonSession ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Now create the public key credentials to be put in the logon
// session.
//
cbPkCreds = sizeof(KERB_PUBLIC_KEY_CREDENTIALS); if ((NULL != pCspData) && (0 != CspDataLength)) { cbPkCreds += CspDataLength; }
PkCredentials = (PKERB_PUBLIC_KEY_CREDENTIALS) KerbAllocate(cbPkCreds); if (PkCredentials == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
PkCredentials->CertContext = *ppCertContext; *ppCertContext = NULL;
Status = KerbDuplicateString( &PkCredentials->Pin, pPin ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Copy in the CSP data for later use
//
if ((NULL != pCspData) && (0 != CspDataLength)) { PkCredentials->CspDataLength = CspDataLength; RtlCopyMemory( PkCredentials->CspData, pCspData, CspDataLength ); PkCredentials->InitializationInfo |= CSP_DATA_INITIALIZED; } else { PkCredentials->InitializationInfo |= CSP_DATA_INITIALIZED | CONTEXT_INITIALIZED_WITH_ACH; }
KerbWriteLockLogonSessions(pLogonSession); pLogonSession->PrimaryCredentials.PublicKeyCreds = PkCredentials; pLogonSession->LogonSessionFlags |= KERB_LOGON_SMARTCARD; PkCredentials = NULL; KerbUnlockLogonSessions(pLogonSession);
*ppLogonSession = pLogonSession; pLogonSession = NULL; Cleanup: if (*ppCertContext != NULL) { CertFreeCertificateContext(*ppCertContext); }
KerbFreePKCreds(PkCredentials);
if (pLogonSession != NULL) { KerbReferenceLogonSessionByPointer(pLogonSession, TRUE); KerbDereferenceLogonSession(pLogonSession); KerbDereferenceLogonSession(pLogonSession); }
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbMapCertChainError
//
// Synopsis: We don't have good winerrors for chaining //
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbMapClientCertChainError(ULONG ChainStatus) {
NTSTATUS Status;
switch(ChainStatus) {
case CRYPT_E_REVOKED: Status = STATUS_SMARTCARD_CERT_REVOKED; break; case CERT_E_EXPIRED: Status = STATUS_SMARTCARD_CERT_EXPIRED; break; case CERT_E_UNTRUSTEDCA: case CERT_E_UNTRUSTEDROOT: Status = STATUS_ISSUING_CA_UNTRUSTED; break;
case CRYPT_E_REVOCATION_OFFLINE: Status = STATUS_REVOCATION_OFFLINE_C; break;
// W2k or old whistler DC
case ERROR_NOT_SUPPORTED: default: Status = STATUS_PKINIT_CLIENT_FAILURE; }
return Status; }
//+-------------------------------------------------------------------------
//
// Function: KerbCreateSmardCardLogonSession
//
// Synopsis: Creats a logon session from the smart card logon info. It
// creates a certificate context from the logon information,
// retrieves the email name from the certificate, and then
// uses that to create a context.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbCreateSmartCardLogonSession( IN PVOID ProtocolSubmitBuffer, IN PVOID ClientBufferBase, IN ULONG SubmitBufferSize, IN SECURITY_LOGON_TYPE LogonType, OUT PKERB_LOGON_SESSION *ReturnedLogonSession, OUT PLUID ReturnedLogonId, OUT PUNICODE_STRING AccountName, OUT PUNICODE_STRING AuthorityName ) { PCERT_CONTEXT CertContext = NULL; NTSTATUS Status = STATUS_SUCCESS; PKERB_SMART_CARD_LOGON LogonInfo = (PKERB_SMART_CARD_LOGON) ProtocolSubmitBuffer; LUID LogonId = {0}; BOOLEAN InitializedContext = FALSE;
//
// We were passed a blob of data. First we need to update the pointers
// to be in this address space
//
RELOCATE_ONE(&LogonInfo->Pin);
LogonInfo->CspData = LogonInfo->CspData - (ULONG_PTR) ClientBufferBase + (ULONG_PTR) LogonInfo;
//
// Make sure it all fits in our address space
//
if ((LogonInfo->CspDataLength + LogonInfo->CspData) > ((PUCHAR) ProtocolSubmitBuffer + SubmitBufferSize)) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
//
// First, initialize the crypt context
//
Status = __ScHelperInitializeContext( LogonInfo->CspData, LogonInfo->CspDataLength ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to initialize context from csp data: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } InitializedContext = TRUE;
//
// The first thing to do is to convert the CSP data into a certificate
// context
//
Status = __ScHelperGetCertFromLogonInfo( LogonInfo->CspData, &LogonInfo->Pin, (PCCERT_CONTEXT*)&CertContext ); if (Status != STATUS_SUCCESS) { DebugLog((DEB_ERROR,"Failed to get cert from logon info: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); if (NT_SUCCESS(Status)) { Status = STATUS_LOGON_FAILURE; } goto Cleanup; }
RtlInitUnicodeString( AuthorityName, NULL );
//
// Now we have just about everything to create a logon session
//
Status = NtAllocateLocallyUniqueId( &LogonId );
if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to allocate locally unique ID: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
//
// For win95, if there is a logon session in our list, remove it.
// This is generated from the logon session dumped in the registry.
// But, we are about to do a new logon. Get rid of the old logon.
// If the new one does not succeed, too bad. But, that's by design.
//
#ifdef WIN32_CHICAGO
LsaApLogonTerminated(&LogonId); #endif // WIN32_CHICAGO
Status = KerbCreateSmartCardLogonSessionFromCertContext( &CertContext, &LogonId, AuthorityName, &LogonInfo->Pin, LogonInfo->CspData, LogonInfo->CspDataLength, ReturnedLogonSession, AccountName ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
LogonInfo->CspDataLength = 0;
*ReturnedLogonId = LogonId; Cleanup: if (InitializedContext && LogonInfo->CspDataLength != 0) { __ScHelperRelease( LogonInfo->CspData ); } return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbGetCertificateName
//
// Synopsis: Gets a name from a certificate name blob. The name is:
// subject@issuer
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbGetCertificateName( OUT PUNICODE_STRING Name, IN PCERT_INFO Certificate ) { NTSTATUS Status = STATUS_SUCCESS; ULONG IssuerLength; ULONG SubjectLength;
RtlInitUnicodeString( Name, NULL );
//
// First find the size of the name. The lengths include the
// null terminators.
//
SubjectLength = CertNameToStr( X509_ASN_ENCODING, &Certificate->Subject, CERT_X500_NAME_STR, NULL, 0 ); if (SubjectLength == 0) { DebugLog((DEB_ERROR,"Failed to convert name: %0x%x. %ws, line %d\n",GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_PKINIT_FAILURE; goto Cleanup; }
IssuerLength = CertNameToStr( X509_ASN_ENCODING, &Certificate->Issuer, CERT_X500_NAME_STR, NULL, 0 ); if (IssuerLength == 0) { DebugLog((DEB_ERROR,"Failed to convert name: %0x%x. %ws, line %d\n",GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_PKINIT_FAILURE; goto Cleanup; }
//
// Remove the null terminator from one name, but leave space for a
// ":" in the middle
//
Name->Buffer = (LPWSTR) KerbAllocate((SubjectLength + IssuerLength) * sizeof(WCHAR)); if (Name->Buffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Now get the name itself
//
SubjectLength = CertNameToStr( X509_ASN_ENCODING, &Certificate->Subject, CERT_X500_NAME_STR, Name->Buffer, SubjectLength ); if (SubjectLength == 0) { DebugLog((DEB_ERROR,"Failed to convert name: %0x%x. %ws, line %d\n",GetLastError(), THIS_FILE, __LINE__)); KerbFree(Name->Buffer); Name->Buffer = NULL; Status = STATUS_PKINIT_FAILURE; goto Cleanup; }
//
// Put an "@" in the middle so it is recognized by MSV as a UPN (just in case)
//
Name->Buffer[SubjectLength-1] = L'@';
IssuerLength = CertNameToStr( X509_ASN_ENCODING, &Certificate->Issuer, CERT_X500_NAME_STR, Name->Buffer + SubjectLength, IssuerLength ); if (IssuerLength == 0) { DebugLog((DEB_ERROR,"Failed to convert name: %0x%x. %ws, line %d\n",GetLastError(), THIS_FILE, __LINE__)); KerbFree(Name->Buffer); Name->Buffer = NULL; Status = STATUS_PKINIT_FAILURE; goto Cleanup; }
RtlInitUnicodeString( Name, Name->Buffer );
Cleanup: return(Status);
}
NTSTATUS KerbGetCertificateHash( OUT LPBYTE pCertHash, IN ULONG cbCertHash, IN PCCERT_CONTEXT pCertContext ) { ULONG cbHash = cbCertHash;
if ( CertGetCertificateContextProperty( pCertContext, CERT_SHA1_HASH_PROP_ID, pCertHash, &cbHash ) == FALSE ) { return( STATUS_PKINIT_FAILURE ); }
return( STATUS_SUCCESS ); }
//+-------------------------------------------------------------------------
//
// Function: KerbLookupSmartCardCachedLogon
//
// Synopsis: Looks up a cached smart card logon in the MSV cache
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes: Free ValidationInfor with LocalFree
//
//
//--------------------------------------------------------------------------
BOOLEAN KerbLookupSmartCardCachedLogon( IN PCCERT_CONTEXT Certificate, OUT PNETLOGON_VALIDATION_SAM_INFO4 * ValidationInfo, OUT PKERB_MESSAGE_BUFFER SupplementalCreds ) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING IssuerName = {0}; PMSV1_0_CACHE_LOOKUP_REQUEST CacheRequest = NULL; PMSV1_0_CACHE_LOOKUP_RESPONSE CacheResponse = NULL; UNICODE_STRING MsvPackageName = CONSTANT_UNICODE_STRING(TEXT(MSV1_0_PACKAGE_NAME)); NTSTATUS SubStatus = STATUS_SUCCESS; ULONG OutputBufferSize = 0; ULONG RequestSize = 0; BOOLEAN Result = FALSE;
SupplementalCreds->BufferSize = 0; SupplementalCreds->Buffer = NULL;
RequestSize = sizeof( MSV1_0_CACHE_LOOKUP_REQUEST ) + SHA1DIGESTLEN - sizeof( UCHAR );
CacheRequest = (PMSV1_0_CACHE_LOOKUP_REQUEST) KerbAllocate( RequestSize );
if ( CacheRequest == NULL ) { return( FALSE ); }
*ValidationInfo = NULL;
//
// Get the issuer & subject name from the cert. These will be used as
// user name & domain name for the lookup
//
Status = KerbGetCertificateName( &IssuerName, Certificate->pCertInfo );
if (NT_SUCCESS(Status)) { Status = KerbGetCertificateHash( CacheRequest->CredentialSubmitBuffer, SHA1DIGESTLEN, Certificate ); }
if (!NT_SUCCESS(Status)) { goto Cleanup; }
CacheRequest->MessageType = MsV1_0CacheLookup; CacheRequest->UserName = IssuerName; CacheRequest->CredentialType = MSV1_0_CACHE_LOOKUP_CREDTYPE_RAW; CacheRequest->CredentialInfoLength = SHA1DIGESTLEN;
//
// Leave the domain name portion blank.
//
//
// Call MSV1_0 to do the work
//
Status = LsaFunctions->CallPackage( &MsvPackageName, CacheRequest, RequestSize, (PVOID *) &CacheResponse, &OutputBufferSize, &SubStatus ); if (!NT_SUCCESS(Status) || !NT_SUCCESS(SubStatus)) { DebugLog((DEB_ERROR,"Failed to lookup cache credentials: 0x%x, 0x%x. %ws, line %d\n",Status, SubStatus, THIS_FILE, __LINE__)); goto Cleanup; }
if (OutputBufferSize < sizeof(MSV1_0_CACHE_LOOKUP_RESPONSE)) { DebugLog((DEB_ERROR,"Invalid response from cache lookup - return too small: %d bytes. %ws, line %d\n", OutputBufferSize, THIS_FILE, __LINE__ )); //
// Free it here so we don't do too much freeing in the cleanup portion
//
//
// BUG 455634: Do we need to free the internals as well (we do in cleanup)?
//
LsaFunctions->FreeReturnBuffer(CacheResponse); CacheResponse = NULL; goto Cleanup; } if (CacheResponse->MessageType != MsV1_0CacheLookup) { DebugLog((DEB_ERROR,"Wrong message type from cache lookup: %d. %ws, line %d\n", CacheResponse->MessageType, THIS_FILE, __LINE__ )); //
// Free it here so we don't do too much freeing in the cleanup portion
//
//
// BUG 455634: Do we need to free the internals as well (we do in cleanup)?
//
LsaFunctions->FreeReturnBuffer(CacheResponse); CacheResponse = NULL; goto Cleanup; }
*ValidationInfo = (PNETLOGON_VALIDATION_SAM_INFO4) CacheResponse->ValidationInformation; CacheResponse->ValidationInformation = NULL;
SupplementalCreds->Buffer = (PBYTE) CacheResponse->SupplementalCacheData; SupplementalCreds->BufferSize = CacheResponse->SupplementalCacheDataLength; CacheResponse->SupplementalCacheData = NULL; Result = TRUE;
Cleanup: if (CacheRequest != NULL) { KerbFree(CacheRequest); }
if (CacheResponse != NULL) {
//
// At this point we know it was a valid cache response, so we can
// free the validation info if it is present.
//
//
// BUG 455634: Why do we use LocalFree for the internal stuff?
//
if (CacheResponse->ValidationInformation != NULL) { LocalFree(CacheResponse->ValidationInformation); } if (CacheResponse->SupplementalCacheData != NULL) { LocalFree(CacheResponse->SupplementalCacheData); }
LsaFunctions->FreeReturnBuffer(CacheResponse); } KerbFreeString(&IssuerName);
return(Result);
}
//+-------------------------------------------------------------------------
//
// Function: KerbDoLocalSmartCardLogon
//
// Synopsis: Performs a local logon with the smart card by validating the
// card and PIN & then trying to map the name locally
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbDoLocalSmartCardLogon( IN PKERB_LOGON_SESSION LogonSession, OUT PLSA_TOKEN_INFORMATION_TYPE TokenInformationType, OUT PVOID *NewTokenInformation, OUT PULONG ProfileBufferLength, OUT PVOID * ProfileBuffer, OUT PSECPKG_PRIMARY_CRED PrimaryCredentials, OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * CachedCredentials, IN OUT PNETLOGON_VALIDATION_SAM_INFO4 * Validation4 ) { NTSTATUS Status = STATUS_SUCCESS; #ifndef WIN32_CHICAGO
PPACTYPE Pac = NULL; PPAC_INFO_BUFFER LogonInfo = NULL; PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL; PNETLOGON_VALIDATION_SAM_INFO4 MsvValidationInfo = NULL; PNETLOGON_VALIDATION_SAM_INFO3 PacValidationInfo = NULL; PLSA_TOKEN_INFORMATION_V2 TokenInformation = NULL; KERB_MESSAGE_BUFFER SupplementalCreds = {0}; #endif // !WIN32_CHICAGO
PKERB_INTERNAL_NAME ClientName = NULL; PKERB_PUBLIC_KEY_CREDENTIALS PkCreds; PBYTE DecryptedCreds = NULL; ULONG DecryptedCredSize = 0;
*Validation4 = NULL;
PkCreds = LogonSession->PrimaryCredentials.PublicKeyCreds;
//
// First, verify the card. This will verify the certificate as well
// as verify the PIN & that the ceritifcate matches the private key on
// the card.
//
if ((PkCreds->InitializationInfo & CSP_DATA_INITIALIZED) == 0) { Status = KerbInitializePkCreds( PkCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
//
// Now build a PAC for the user
//
if (!KERB_SUCCESS(KerbConvertStringToKdcName( &ClientName, &LogonSession->PrimaryCredentials.UserName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
#ifndef WIN32_CHICAGO
//
// First check for a cached logon entry
//
if (KerbLookupSmartCardCachedLogon( PkCreds->CertContext, &MsvValidationInfo, &SupplementalCreds)) { ValidationInfo = (PNETLOGON_VALIDATION_SAM_INFO3) MsvValidationInfo; ValidationInfo->UserFlags |= LOGON_CACHED_ACCOUNT;
//
// Strip the domain postfix
//
if (ValidationInfo->LogonDomainName.Length >= KERB_SCLOGON_DOMAIN_SUFFIX_SIZE) { ValidationInfo->LogonDomainName.Length -= KERB_SCLOGON_DOMAIN_SUFFIX_SIZE; }
if ((SupplementalCreds.Buffer != NULL) && (SupplementalCreds.BufferSize != 0)) { DecryptedCredSize = SupplementalCreds.BufferSize; DecryptedCreds = (PBYTE) MIDL_user_allocate(DecryptedCredSize); if (DecryptedCreds == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } } } else { //
// Look for a name mapping
//
Status = KerbCreatePacForKerbClient( &Pac, ClientName, &LogonSession->PrimaryCredentials.DomainName, NULL ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
//
// Find the SAM validation info
//
LogonInfo = PAC_Find( Pac, PAC_LOGON_INFO, NULL ); if (LogonInfo == NULL) { DebugLog((DEB_ERROR,"Failed to find logon info! %ws, line %d\n", THIS_FILE, __LINE__)); Status = STATUS_INVALID_PARAMETER; goto Cleanup; }
//
// Now unmarshall the validation info
//
Status = PAC_UnmarshallValidationInfo( &PacValidationInfo, LogonInfo->Data, LogonInfo->cbBufferSize ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to unmarshall validation info: 0x%x. %ws, line %d\n", Status, THIS_FILE, __LINE__)); goto Cleanup; } ValidationInfo = PacValidationInfo; }
Status = __ScHelperVerifyCardAndCreds( &PkCreds->Pin, PkCreds->CertContext, PkCreds->CspData, SupplementalCreds.Buffer, SupplementalCreds.BufferSize, DecryptedCreds, &DecryptedCredSize ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to verify card: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; }
//
// If we have any encrypted credentials, decode them here for return.
//
if (DecryptedCredSize != 0) { Status = PAC_UnmarshallCredentials( CachedCredentials, DecryptedCreds, DecryptedCredSize ); if (!NT_SUCCESS(Status)) { goto Cleanup; } }
//
// Check to see if this is a non-user account. If so, don't allow the logon
//
if ((ValidationInfo->ExpansionRoom[SAMINFO_USER_ACCOUNT_CONTROL] & USER_MACHINE_ACCOUNT_MASK) != 0) { DebugLog((DEB_ERROR,"Logons to non-user accounts not allowed. UserAccountControl = 0x%x\n", ValidationInfo->ExpansionRoom[SAMINFO_USER_ACCOUNT_CONTROL] )); Status = STATUS_LOGON_TYPE_NOT_GRANTED; goto Cleanup; }
//
// Now we need to build a LSA_TOKEN_INFORMATION_V2 from the validation
// information
//
Status = KerbMakeTokenInformationV2( ValidationInfo, FALSE, // not local system
&TokenInformation ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to make token informatin v2: 0x%x\n", Status)); goto Cleanup; }
//
// Allocate the client profile
//
Status = KerbAllocateInteractiveProfile( (PKERB_INTERACTIVE_PROFILE *) ProfileBuffer, ProfileBufferLength, ValidationInfo, LogonSession, NULL, NULL ); if (!KERB_SUCCESS(Status)) { goto Cleanup; }
//
// Build the primary credential. We let someone else fill in the
// password.
//
PrimaryCredentials->LogonId = LogonSession->LogonId; Status = KerbDuplicateString( &PrimaryCredentials->DownlevelName, &ValidationInfo->EffectiveName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbDuplicateString( &PrimaryCredentials->DomainName, &ValidationInfo->LogonDomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbDuplicateSid( &PrimaryCredentials->UserSid, TokenInformation->User.User.Sid ); if (!NT_SUCCESS(Status)) { goto Cleanup; }
PrimaryCredentials->Flags = 0; *Validation4 = MsvValidationInfo; MsvValidationInfo = NULL;
*NewTokenInformation = TokenInformation; *TokenInformationType = LsaTokenInformationV2;
#endif // !WIN32_CHICAGO
Cleanup:
if (PacValidationInfo != NULL) { MIDL_user_free(PacValidationInfo); } KerbFreeKdcName( &ClientName );
if (MsvValidationInfo != NULL) { LocalFree(MsvValidationInfo); }
if (SupplementalCreds.Buffer != NULL) { //
// BUG 455634: this should be freed a better way
//
LocalFree(SupplementalCreds.Buffer); } #ifndef WIN32_CHICAGO
if (Pac != NULL) { MIDL_user_free(Pac); } if (!NT_SUCCESS(Status)) { if (TokenInformation != NULL) { KerbFree( TokenInformation ); } if (*ProfileBuffer != NULL) { LsaFunctions->FreeClientBuffer(NULL, *ProfileBuffer); *ProfileBuffer = NULL; } KerbFreeString( &PrimaryCredentials->DownlevelName ); KerbFreeString( &PrimaryCredentials->DomainName ); if (PrimaryCredentials->UserSid != NULL) { KerbFree(PrimaryCredentials->UserSid); PrimaryCredentials->UserSid = NULL; } } #endif // WIN32_CHICAGO
return(Status); }
//+-------------------------------------------------------------------------
//
// Function: KerbCacheSmartCardLogon
//
// Synopsis:
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID KerbCacheSmartCardLogon( IN PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo, IN OPTIONAL PUNICODE_STRING DnsDomainName, IN OPTIONAL PUNICODE_STRING UPN, IN PKERB_LOGON_SESSION LogonSession, IN OPTIONAL PSECPKG_SUPPLEMENTAL_CRED_ARRAY CachedCredentials ) { NTSTATUS Status; UNICODE_STRING IssuerName = {0}; UNICODE_STRING DomainName = {0}; UNICODE_STRING TempLogonDomainName = {0}; UNICODE_STRING LogonDomainName = {0}; BYTE CertificateHash[ SHA1DIGESTLEN ]; UNICODE_STRING CertificateHashString; ULONG EncodedCredSize = 0; PBYTE EncodedCreds = NULL; ULONG EncryptedCredSize = 0; PBYTE EncryptedCreds = NULL; BOOLEAN LogonSessionLocked = FALSE; BOOLEAN InitializedPkCreds = FALSE;
//
// Build the temporary logon domain name that indicates this is a
// smart card logon.
//
TempLogonDomainName.MaximumLength = TempLogonDomainName.Length = ValidationInfo->LogonDomainName.Length + KERB_SCLOGON_DOMAIN_SUFFIX_SIZE;
TempLogonDomainName.Buffer = (LPWSTR) MIDL_user_allocate(TempLogonDomainName.Length); if (TempLogonDomainName.Buffer == NULL) { goto Cleanup; }
//
// Create the new name
//
RtlCopyMemory( TempLogonDomainName.Buffer, ValidationInfo->LogonDomainName.Buffer, ValidationInfo->LogonDomainName.Length );
RtlCopyMemory( ((PUCHAR) TempLogonDomainName.Buffer) + ValidationInfo->LogonDomainName.Length, KERB_SCLOGON_DOMAIN_SUFFIX, KERB_SCLOGON_DOMAIN_SUFFIX_SIZE );
LogonDomainName = ValidationInfo->LogonDomainName; ValidationInfo->LogonDomainName = TempLogonDomainName;
//
// Get the name under which to store this.
//
KerbReadLockLogonSessions(LogonSession);
LogonSessionLocked = TRUE;
Status = KerbGetCertificateName( &IssuerName, LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext->pCertInfo );
if ( Status == STATUS_SUCCESS ) { Status = KerbGetCertificateHash( CertificateHash, SHA1DIGESTLEN, LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext ); }
if (!NT_SUCCESS(Status)) { goto Cleanup; }
CertificateHashString.Length = SHA1DIGESTLEN; CertificateHashString.Buffer = (LPWSTR)CertificateHash; CertificateHashString.MaximumLength = SHA1DIGESTLEN;
if (ARGUMENT_PRESENT(CachedCredentials)) { ScHelper_RandomCredBits RandomBits;
Status = PAC_EncodeCredentialData( CachedCredentials, &EncodedCreds, &EncodedCredSize ); if (!NT_SUCCESS(Status)) { goto Cleanup; } if ((LogonSession->PrimaryCredentials.PublicKeyCreds->InitializationInfo & CSP_DATA_INITIALIZED) == 0) { Status = KerbInitializePkCreds( LogonSession->PrimaryCredentials.PublicKeyCreds ); if (!NT_SUCCESS(Status)) { goto Cleanup; } InitializedPkCreds = TRUE;
}
Status = __ScHelperGenRandBits( LogonSession->PrimaryCredentials.PublicKeyCreds->CspData, &RandomBits ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR,"Failed to generate random bits: 0x%x\n",Status)); goto Cleanup; }
Status = __ScHelperEncryptCredentials( &LogonSession->PrimaryCredentials.PublicKeyCreds->Pin, LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext, &RandomBits, LogonSession->PrimaryCredentials.PublicKeyCreds->CspData, EncodedCreds, EncodedCredSize, NULL, &EncryptedCredSize ); if ((Status != STATUS_SUCCESS) && (Status != STATUS_BUFFER_TOO_SMALL)) { DebugLog((DEB_ERROR,"Failed to encrypt creds: 0x%x\n",Status)); goto Cleanup; }
EncryptedCreds = (PBYTE) KerbAllocate(EncryptedCredSize); if (EncryptedCreds == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; }
//
// Do the real encryption
//
Status = __ScHelperEncryptCredentials( &LogonSession->PrimaryCredentials.PublicKeyCreds->Pin, LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext, &RandomBits, LogonSession->PrimaryCredentials.PublicKeyCreds->CspData, EncodedCreds, EncodedCredSize, EncryptedCreds, &EncryptedCredSize ); if (Status != STATUS_SUCCESS) { DebugLog((DEB_ERROR,"Failed to encrypt creds: 0x%x\n",Status)); goto Cleanup; } } KerbUnlockLogonSessions(LogonSession); LogonSessionLocked = FALSE;
KerbCacheLogonInformation( &IssuerName, // used as username
&DomainName, // blank - no domain
&CertificateHashString, // password is certificate hash,
DnsDomainName, NULL, //UPN,
FALSE, 0, // no flags
ValidationInfo, EncryptedCreds, EncryptedCredSize );
Cleanup:
if (InitializedPkCreds) { KerbFreePKCreds( LogonSession->PrimaryCredentials.PublicKeyCreds );
}
if (LogonSessionLocked) { KerbUnlockLogonSessions(LogonSession); } KerbFreeString(&IssuerName); KerbFreeString(&TempLogonDomainName);
//
// Restore the original logon domain name
//
if (LogonDomainName.Buffer != NULL) { ValidationInfo->LogonDomainName = LogonDomainName; } if (EncodedCreds != NULL) { MIDL_user_free(EncodedCreds); }
}
//+-------------------------------------------------------------------------
//
// Function: KerbInitializePkinit
//
// Synopsis: Inializes structures needed for PKINIT
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS KerbInitializePkinit( VOID ) { ULONG Index; LPSTR StringCopy = NULL, TempString = NULL,EndPtr = NULL;
//
// Initialize the object IDs
//
Index = 0;
StringCopy = (LPSTR) KerbAllocate((ULONG) strlen(KERB_PKINIT_SIGNATURE_OID)+1); if (StringCopy == NULL) { return( STATUS_INSUFFICIENT_RESOURCES);
}
//
// 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; }
if (0 == sscanf(TempString,"%u",&Temp)) { return STATUS_INSUFFICIENT_RESOURCES; } KerbSignatureAlg[Index].value = (USHORT) Temp; KerbSignatureAlg[Index].next = &KerbSignatureAlg[Index+1]; Index++;
TempString = EndPtr;
} DsysAssert(Index != 0); KerbSignatureAlg[Index-1].next = NULL; KerbFree(StringCopy); TempString = NULL;
return(STATUS_SUCCESS);
}
|