//+----------------------------------------------------------------------- // // 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 #include //#ifndef WIN32_CHICAGO extern "C" { #include #include } //#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 = KerbMapCertChainError(PolicyStatus.dwError, FALSE); } } 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->KerbHProv, 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; } SafeAllocaAllocate(EncodedKeyPackage, KeyPackageSize); if (EncodedKeyPackage == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } Status = __ScHelperDecryptMessage( &Credentials->PublicKeyCreds->Pin, Credentials->PublicKeyCreds->CspData, Credentials->PublicKeyCreds->KerbHProv, 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 ); 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: // // re-impersonate // if( ImpersonationToken != NULL ) { // // put the thread token back if we were impersonating. // SetThreadToken( NULL, ImpersonationToken ); NtClose( ImpersonationToken ); } // // If we initialized these, reset them // if (InitializedPkCreds) { KerbReleasePkCreds( NULL, Credentials->PublicKeyCreds, FALSE ); } 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 ); } SafeAllocaFree(EncodedKeyPackage); return(Status); } //+------------------------------------------------------------------------- // // Function: KerbFreePKCreds // // Synopsis: Frees the public key creds // // Effects: // // Arguments: OkForReuse - Allows us to release info which may not be valid, // but that we can reuse to reaquire a good ScHelperHandle. // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID KerbFreePKCreds( IN PKERB_PUBLIC_KEY_CREDENTIALS PkCreds, IN BOOLEAN OkForReuse ) { 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->KerbHProv != NULL ) { __ScHelper_CryptReleaseContext( PkCreds->KerbHProv ); PkCreds->KerbHProv = NULL; } if ( PkCreds->CertContext != NULL ) { CertFreeCertificateContext( PkCreds->CertContext ); PkCreds->CertContext = NULL; } if ( !OkForReuse ) { RtlSecureZeroMemory(PkCreds->Pin.Buffer, PkCreds->Pin.MaximumLength); 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; DWORD dw; if (PkCreds->KerbHProv != NULL) { goto Cleanup; } // // This function validates that the cert matches the key on the smartcard. // Status = __ScHelper_CryptAcquireCertificatePrivateKey( PkCreds->CertContext, &PkCreds->KerbHProv, &dw); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "CryptAcquireCertificatePrivateKey failed - %x\n", Status)); 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; } SafeAllocaAllocate(pwszPin, 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); SafeAllocaAllocate(pszPin, (cPin + 1) * sizeof(CHAR)); if (NULL == pszPin) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } cPin = WideCharToMultiByte( GetACP(), 0, pwszPin, -1, pszPin, cPin, NULL, NULL); Status = __ScHelper_CryptSetProvParam( PkCreds->KerbHProv, pszPin, &dw ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "CryptSetProvParam failed %x\n", Status)); goto Cleanup; } } Cleanup: SafeAllocaFree(pwszPin); SafeAllocaFree(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) { // // Credman and explicit creds // if (((PkCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_CRED_MAN_CREDS) == 0) && ((PkCreds->InitializationInfo & CONTEXT_INITIALIZED_WITH_ACH) == 0)) { // // This is the default logon case - we're using data just handed to us // from LsaLogonUser(). // 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, IN BOOLEAN OkForReuse ) { if (ARGUMENT_PRESENT(LogonSession)) { KerbWriteLockLogonSessions( LogonSession ); PkCreds = LogonSession->PrimaryCredentials.PublicKeyCreds; } KerbFreePKCreds(PkCreds, OkForReuse); if (ARGUMENT_PRESENT(LogonSession)) { if (!OkForReuse) { LogonSession->PrimaryCredentials.PublicKeyCreds = NULL; } else { LogonSession->PrimaryCredentials.PublicKeyCreds->InitializationInfo |= CSP_DATA_REUSED; } 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->KerbHProv, 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, FALSE ); } if (PackedAuthenticator != NULL) { MIDL_user_free(PackedAuthenticator); } return(Status); } NTSTATUS KerbGetProvParamWrapper( IN PUNICODE_STRING pPin, IN PBYTE pbLogonInfo, IN OPTIONAL ULONG_PTR KerbHProv, DWORD dwParam, BYTE*pbData, DWORD *pdwDataLen, DWORD dwFlags ) { NTSTATUS Status = STATUS_SUCCESS; Status = __ScHelperGetProvParam( pPin, pbLogonInfo, KerbHProv, dwParam, pbData, pdwDataLen, dwFlags ); if (!NT_SUCCESS(Status)) { DebugLog((DEB_ERROR, "Failure in SC subsytem - %x\n",Status)); } 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->KerbHProv, 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, FALSE ); 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 FreePkCreds = FALSE; CRYPT_ALGORITHM_IDENTIFIER CryptAlg = {0}; PUNICODE_STRING TmpPin = NULL; BOOLEAN CleartextPin = FALSE; // // For the duration of this function, reveal the pin from // its encrypted form. // if ( Credentials->PublicKeyCreds->Pin.Buffer ) { KerbRevealPassword(&Credentials->PublicKeyCreds->Pin); CleartextPin = 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; } // // In some cases, we free up the cspdata, then reacquire it later. This // is a hack for TS cases, where the SC rpc server dies when the temp // winlogon session goes away, thus invalidating the SChelper handle. But, // once we've reacquired it, we should hold onto it. // if ((Credentials->PublicKeyCreds->InitializationInfo & CSP_DATA_REUSED) == 0) { FreePkCreds = 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->KerbHProv, 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->KerbHProv, 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: if (CleartextPin) { KerbHidePassword( &Credentials->PublicKeyCreds->Pin ); } 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 ( FreePkCreds ) { KerbReleasePkCreds( NULL, Credentials->PublicKeyCreds, FALSE ); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbRetrieveDomainFromDn // // Synopsis: Looks for a subject DN in a smartcard, then calls DsCrackName // (local, no wire traffic) to determine the domain name. // // Effects: Allocates a unicode string // // Arguments: IN PCERT_CONTEXT smartcard cert // IN OUT PUNICODE_STRING CrackedName (free w/ KerbFreeString) // // Requires: // // Returns: // // Notes: Returns STATUS_NOT_FOUND if the name is missing // // //-------------------------------------------------------------------------- BOOLEAN KerbRetrieveDomainFromDn( IN PCCERT_CONTEXT Cert, IN OUT PUNICODE_STRING CrackedDomain ) { LPWSTR RDN = NULL; NTSTATUS Status; PCERT_NAME_INFO NameInfo = NULL; DWORD Result, NameInfoLength = 0; LONG i, LastPart = 0, FirstPart = 0xFFFFFFFF; PWCHAR tmp; UNICODE_STRING TmpString; BOOLEAN fRet = FALSE; if (!CryptDecodeObjectEx( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_UNICODE_NAME, Cert->pCertInfo->Subject.pbData, Cert->pCertInfo->Subject.cbData, CRYPT_DECODE_ALLOC_FLAG, NULL, &NameInfo, &NameInfoLength )) { Result = GetLastError(); DebugLog((DEB_ERROR, "CryptDecodeObject failed %x\n", Result)); goto Cleanup; } // // Now walk through the NameInfoStruct, looking for DC= // NameInfoLength = 0; for ( i = 0; i < (LONG) NameInfo->cRDN ; i ++) { if (strcmp(NameInfo->rgRDN[i].rgRDNAttr->pszObjId,szOID_DOMAIN_COMPONENT)) { continue; } if (FirstPart == 0xFFFFFFFF) { FirstPart = i; } NameInfoLength += NameInfo->rgRDN[i].rgRDNAttr->Value.cbData + sizeof(WCHAR); LastPart = i; } if (FirstPart == 0xFFFFFFFF) { DebugLog((DEB_ERROR, "No DC component in RDN\n")); KerbReportMissingRDN(); DsysAssert(FALSE); goto Cleanup; } SafeAllocaAllocate(RDN, NameInfoLength); if ( RDN == NULL ) { goto Cleanup; } // // The names are in reverse order, e.g. DC=COM, DC=MICROSOFT, DC=NTDEV // tmp = (PWCHAR) RDN; for ( i = LastPart ; i >= 0 ; i-- ) { RtlCopyMemory( tmp, NameInfo->rgRDN[i].rgRDNAttr->Value.pbData, NameInfo->rgRDN[i].rgRDNAttr->Value.cbData ); tmp += ( NameInfo->rgRDN[i].rgRDNAttr->Value.cbData / sizeof(WCHAR) ); if (i != FirstPart) { *tmp = L'.'; tmp++; } else { *tmp = L'\0'; } } RtlInitUnicodeString( &TmpString, RDN ); Status = KerbDuplicateStringEx( CrackedDomain, &TmpString, FALSE ); if (!NT_SUCCESS(Status)) { goto Cleanup; } fRet = TRUE; Cleanup: if ( NameInfo ) { LocalFree( NameInfo ); } SafeAllocaFree(RDN); return (fRet); } //+------------------------------------------------------------------------- // // 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 KERB_LOGON_SMARTCARD, FALSE, // don't allow dup. This is a primary logon. &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; } KerbHidePassword(&PkCredentials->Pin); // // 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; PkCredentials = NULL; KerbUnlockLogonSessions(pLogonSession); *ppLogonSession = pLogonSession; pLogonSession = NULL; Cleanup: if (*ppCertContext != NULL) { CertFreeCertificateContext(*ppCertContext); } KerbFreePKCreds(PkCredentials, FALSE); 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 KerbMapCertChainError(ULONG ChainStatus, BOOLEAN Client) { NTSTATUS Status; switch(ChainStatus) { case CRYPT_E_REVOKED: Status = (Client ? STATUS_SMARTCARD_CERT_REVOKED : STATUS_KDC_CERT_REVOKED); break; case CERT_E_EXPIRED: Status = (Client ? STATUS_SMARTCARD_CERT_EXPIRED : STATUS_KDC_CERT_EXPIRED); break; case CERT_E_UNTRUSTEDCA: case CERT_E_UNTRUSTEDROOT: Status = (Client ? STATUS_ISSUING_CA_UNTRUSTED : STATUS_ISSUING_CA_UNTRUSTED_KDC); break; case CRYPT_E_REVOCATION_OFFLINE: Status = (Client ? STATUS_REVOCATION_OFFLINE_C : STATUS_REVOCATION_OFFLINE_KDC); break; // W2k or old whistler DC case ERROR_NOT_SUPPORTED: default: Status = (Client ? STATUS_PKINIT_CLIENT_FAILURE : STATUS_PKINIT_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); } //+------------------------------------------------------------------------- // // 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 ); SafeAllocaAllocate(CacheRequest, RequestSize); if ( CacheRequest == NULL ) { return( FALSE ); } RtlZeroMemory(CacheRequest, RequestSize); *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. // Don't free the internals as this is pretty bad. // 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. // Don't free the internals as this is pretty bad. // 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: SafeAllocaFree(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. NTLM uses // MIDL_user_allocate to allocate these. // if (CacheResponse->ValidationInformation != NULL) { MIDL_user_free(CacheResponse->ValidationInformation); } if (CacheResponse->SupplementalCacheData != NULL) { MIDL_user_free(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; NETLOGON_VALIDATION_SAM_INFO3 ValidationInfo3 = {0}; *Validation4 = NULL; PrimaryCredentials->Flags = 0; 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)) { MsvValidationInfo->UserFlags |= LOGON_CACHED_ACCOUNT; PrimaryCredentials->Flags |= PRIMARY_CRED_CACHED_LOGON; // // Strip the domain postfix // if (MsvValidationInfo->LogonDomainName.Length >= KERB_SCLOGON_DOMAIN_SUFFIX_SIZE) { MsvValidationInfo->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; } } // // NOTE: this makes use of the fact that _INFO3 and _INFO4 structures // expand the _INFO2 structure in different ways, so we're keeping the // common portion. The rest of _INFO3 is zeroed out in the declaration, // so no ZeroMemory() call is necessary here // RtlCopyMemory( &ValidationInfo3, MsvValidationInfo, sizeof( NETLOGON_VALIDATION_SAM_INFO2 ) ); ValidationInfo = &ValidationInfo3; } 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; } KerbRevealPassword(&PkCreds->Pin); Status = __ScHelperVerifyCardAndCreds( &PkCreds->Pin, PkCreds->CertContext, PkCreds->CspData, SupplementalCreds.Buffer, SupplementalCreds.BufferSize, DecryptedCreds, &DecryptedCredSize ); KerbHidePassword(&PkCreds->Pin); 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; } *Validation4 = MsvValidationInfo; MsvValidationInfo = NULL; *NewTokenInformation = TokenInformation; *TokenInformationType = LsaTokenInformationV2; #endif // !WIN32_CHICAGO Cleanup: if (PacValidationInfo != NULL) { MIDL_user_free(PacValidationInfo); } KerbFreeKdcName( &ClientName ); if (MsvValidationInfo != NULL) { MIDL_user_free(MsvValidationInfo); MsvValidationInfo = NULL; } if (SupplementalCreds.Buffer != NULL) { MIDL_user_free(SupplementalCreds.Buffer); SupplementalCreds.Buffer = NULL; } #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; BOOLEAN CleartextPin = 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. // DsysAssert( !LogonSessionLocked ); 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; } KerbRevealPassword( &LogonSession->PrimaryCredentials.PublicKeyCreds->Pin ); CleartextPin = TRUE; 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; } } DsysAssert( LogonSessionLocked ); KerbUnlockLogonSessions(LogonSession); LogonSessionLocked = FALSE; KerbCacheLogonInformation( &IssuerName, // used as username &DomainName, // blank - no domain &CertificateHashString, // password is certificate hash, DnsDomainName, NULL, // UPN, NULL, // not MIT realm logon, do not need to pass in LogonSession MSV1_0_CACHE_LOGON_REQUEST_SMARTCARD_ONLY, // smartcard only ValidationInfo, EncryptedCreds, EncryptedCredSize ); Cleanup: if (CleartextPin) { KerbHidePassword(&LogonSession->PrimaryCredentials.PublicKeyCreds->Pin); } if (InitializedPkCreds) { KerbFreePKCreds( LogonSession->PrimaryCredentials.PublicKeyCreds, FALSE ); } 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; SafeAllocaAllocate(StringCopy, (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; SafeAllocaFree(StringCopy); TempString = NULL; return(STATUS_SUCCESS); }