2537 lines
66 KiB
C
2537 lines
66 KiB
C
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1995.
|
|
//
|
|
// File: mapper.c
|
|
//
|
|
// Contents: Implements the DS Mapping Layer
|
|
//
|
|
// Classes:
|
|
//
|
|
// Functions:
|
|
//
|
|
// History: 10-15-96 RichardW Created
|
|
//
|
|
// Notes: The code here has two forks. One, the direct path, for when
|
|
// the DLL is running on a DC, and the second, for when we're
|
|
// running elsewhere and remoting through the generic channel
|
|
// to the DC.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include "sslp.h"
|
|
#include <crypt.h>
|
|
#include <lmcons.h>
|
|
#include <ntsam.h>
|
|
#include <samrpc.h>
|
|
#include <samisrv.h>
|
|
#include <lsarpc.h>
|
|
#include <lsaisrv.h>
|
|
#include <dnsapi.h>
|
|
#include <certmap.h>
|
|
#include <align.h>
|
|
#include <ntmsv1_0.h>
|
|
#include <ntdsapi.h>
|
|
#include <ntdsapip.h>
|
|
#include "mapsam.h"
|
|
#include "wincrypt.h"
|
|
#include <msaudite.h>
|
|
|
|
|
|
LONG WINAPI
|
|
SslLocalRefMapper(
|
|
PHMAPPER Mapper);
|
|
|
|
LONG WINAPI
|
|
SslLocalDerefMapper(
|
|
PHMAPPER Mapper);
|
|
|
|
DWORD WINAPI
|
|
SslLocalGetIssuerList(
|
|
IN PHMAPPER Mapper,
|
|
IN PVOID Reserved,
|
|
OUT PBYTE pIssuerList,
|
|
OUT PDWORD IssuerListLength);
|
|
|
|
DWORD WINAPI
|
|
SslLocalGetChallenge(
|
|
IN PHMAPPER Mapper,
|
|
IN PUCHAR AuthenticatorId,
|
|
IN DWORD AuthenticatorIdLength,
|
|
OUT PUCHAR Challenge,
|
|
OUT DWORD * ChallengeLength);
|
|
|
|
DWORD WINAPI
|
|
SslLocalMapCredential(
|
|
IN PHMAPPER Mapper,
|
|
IN DWORD CredentialType,
|
|
VOID const *pCredential,
|
|
VOID const *pAuthority,
|
|
OUT HLOCATOR * phLocator);
|
|
|
|
DWORD WINAPI
|
|
SslRemoteMapCredential(
|
|
IN PHMAPPER Mapper,
|
|
IN DWORD CredentialType,
|
|
VOID const *pCredential,
|
|
VOID const *pAuthority,
|
|
OUT HLOCATOR * phLocator);
|
|
|
|
DWORD WINAPI
|
|
SslLocalCloseLocator(
|
|
IN PHMAPPER Mapper,
|
|
IN HLOCATOR Locator);
|
|
|
|
DWORD WINAPI
|
|
SslLocalGetAccessToken(
|
|
IN PHMAPPER Mapper,
|
|
IN HLOCATOR Locator,
|
|
OUT HANDLE *Token);
|
|
|
|
MAPPER_VTABLE SslLocalTable = { SslLocalRefMapper,
|
|
SslLocalDerefMapper,
|
|
SslLocalGetIssuerList,
|
|
SslLocalGetChallenge,
|
|
SslLocalMapCredential,
|
|
SslLocalGetAccessToken,
|
|
SslLocalCloseLocator
|
|
};
|
|
|
|
MAPPER_VTABLE SslRemoteTable = {SslLocalRefMapper,
|
|
SslLocalDerefMapper,
|
|
SslLocalGetIssuerList,
|
|
SslLocalGetChallenge,
|
|
SslRemoteMapCredential,
|
|
SslLocalGetAccessToken,
|
|
SslLocalCloseLocator
|
|
};
|
|
|
|
|
|
MAPPER_VTABLE SslUserModeTable = {
|
|
SslLocalRefMapper,
|
|
SslLocalDerefMapper,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
SslLocalGetAccessToken,
|
|
SslLocalCloseLocator
|
|
};
|
|
|
|
|
|
typedef struct _SSL_MAPPER_CONTEXT {
|
|
HMAPPER Mapper ;
|
|
LONG Ref ;
|
|
|
|
} SSL_MAPPER_CONTEXT, * PSSL_MAPPER_CONTEXT ;
|
|
|
|
|
|
NTSTATUS
|
|
WINAPI
|
|
SslBuildCertLogonRequest(
|
|
PCCERT_CHAIN_CONTEXT pChainContext,
|
|
DWORD dwMethods,
|
|
PSSL_CERT_LOGON_REQ *ppRequest,
|
|
PDWORD pcbRequest);
|
|
|
|
NTSTATUS
|
|
WINAPI
|
|
SslMapCertAtDC(
|
|
PUNICODE_STRING DomainName,
|
|
VOID const *pCredential,
|
|
DWORD cbCredential,
|
|
PMSV1_0_PASSTHROUGH_RESPONSE * DcResponse
|
|
);
|
|
|
|
PWSTR SslDnsDomainName ;
|
|
SECURITY_STRING SslNullString = { 0, sizeof( WCHAR ), L"" };
|
|
|
|
|
|
PHMAPPER
|
|
SslGetMapper(
|
|
BOOL Ignored
|
|
)
|
|
{
|
|
PSSL_MAPPER_CONTEXT Context ;
|
|
NTSTATUS Status ;
|
|
NT_PRODUCT_TYPE ProductType ;
|
|
BOOL DC ;
|
|
|
|
if ( RtlGetNtProductType( &ProductType ) )
|
|
{
|
|
DC = (ProductType == NtProductLanManNt );
|
|
}
|
|
else
|
|
{
|
|
return NULL ;
|
|
}
|
|
|
|
Context = (PSSL_MAPPER_CONTEXT) SPExternalAlloc( sizeof( SSL_MAPPER_CONTEXT ) );
|
|
|
|
if ( Context )
|
|
{
|
|
Context->Mapper.m_dwMapperVersion = MAPPER_INTERFACE_VER ;
|
|
Context->Mapper.m_dwFlags = SCH_FLAG_SYSTEM_MAPPER ;
|
|
Context->Mapper.m_Reserved1 = NULL ;
|
|
|
|
Context->Ref = 0;
|
|
|
|
|
|
if ( DC )
|
|
{
|
|
Context->Mapper.m_vtable = &SslLocalTable ;
|
|
}
|
|
else
|
|
{
|
|
Context->Mapper.m_vtable = &SslRemoteTable ;
|
|
}
|
|
|
|
return &Context->Mapper ;
|
|
}
|
|
else
|
|
{
|
|
return NULL ;
|
|
}
|
|
}
|
|
|
|
|
|
LONG
|
|
WINAPI
|
|
SslLocalRefMapper(
|
|
PHMAPPER Mapper
|
|
)
|
|
{
|
|
PSSL_MAPPER_CONTEXT Context ;
|
|
|
|
Context = (PSSL_MAPPER_CONTEXT) Mapper ;
|
|
|
|
if ( Context == NULL )
|
|
{
|
|
return( -1 );
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Ref of Context %x\n", Mapper ));
|
|
|
|
return( InterlockedIncrement( &Context->Ref ) );
|
|
|
|
}
|
|
|
|
LONG
|
|
WINAPI
|
|
SslLocalDerefMapper(
|
|
PHMAPPER Mapper
|
|
)
|
|
{
|
|
PSSL_MAPPER_CONTEXT Context ;
|
|
DWORD RefCount;
|
|
|
|
Context = (PSSL_MAPPER_CONTEXT) Mapper ;
|
|
|
|
if ( Context == NULL )
|
|
{
|
|
return( -1 );
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Deref of Context %x\n", Mapper ));
|
|
|
|
RefCount = InterlockedDecrement( &Context->Ref );
|
|
|
|
if(RefCount == 0)
|
|
{
|
|
SPExternalFree(Context);
|
|
}
|
|
|
|
return RefCount;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalGetIssuerList(
|
|
IN PHMAPPER Mapper,
|
|
IN PVOID Reserved,
|
|
OUT PBYTE pIssuerList,
|
|
OUT PDWORD IssuerListLength
|
|
)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "GetIssuerList, context %x\n", Mapper ));
|
|
|
|
return( GetDefaultIssuers(pIssuerList, IssuerListLength) );
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalGetChallenge(
|
|
IN PHMAPPER Mapper,
|
|
IN PUCHAR AuthenticatorId,
|
|
IN DWORD AuthenticatorIdLength,
|
|
OUT PUCHAR Challenge,
|
|
OUT DWORD * ChallengeLength
|
|
)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "GetChallenge, context %x\n", Mapper ));
|
|
|
|
return SEC_E_UNSUPPORTED_FUNCTION;
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SslCreateTokenFromPac(
|
|
PUCHAR AuthInfo,
|
|
ULONG AuthInfoLength,
|
|
PUNICODE_STRING Domain,
|
|
PLUID NewLogonId,
|
|
PHANDLE NewToken
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
PVOID TokenInfo ;
|
|
LSA_TOKEN_INFORMATION_TYPE TokenInfoType ;
|
|
PLSA_TOKEN_INFORMATION_V1 TokenV1 ;
|
|
PLSA_TOKEN_INFORMATION_NULL TokenNull ;
|
|
LUID LogonId ;
|
|
HANDLE TokenHandle ;
|
|
NTSTATUS SubStatus ;
|
|
SECURITY_STRING PacUserName ;
|
|
|
|
//
|
|
// Get the marshalled blob into a more useful form:
|
|
//
|
|
|
|
PacUserName.Buffer = NULL ;
|
|
|
|
Status = LsaTable->ConvertAuthDataToToken(
|
|
AuthInfo,
|
|
AuthInfoLength,
|
|
SecurityImpersonation,
|
|
&SslTokenSource,
|
|
Network,
|
|
Domain,
|
|
&TokenHandle,
|
|
&LogonId,
|
|
&PacUserName,
|
|
&SubStatus
|
|
);
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
LsaTable->AuditLogon(
|
|
STATUS_SUCCESS,
|
|
STATUS_SUCCESS,
|
|
&PacUserName,
|
|
Domain,
|
|
NULL,
|
|
NULL,
|
|
Network,
|
|
&SslTokenSource,
|
|
&LogonId );
|
|
}
|
|
else
|
|
{
|
|
LsaTable->AuditLogon(
|
|
Status,
|
|
Status,
|
|
&SslNullString,
|
|
&SslNullString,
|
|
NULL,
|
|
NULL,
|
|
Network,
|
|
&SslTokenSource,
|
|
&LogonId );
|
|
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
return Status ;
|
|
}
|
|
|
|
|
|
*NewToken = TokenHandle ;
|
|
*NewLogonId = LogonId ;
|
|
|
|
|
|
if ( PacUserName.Buffer )
|
|
{
|
|
LsaTable->FreeLsaHeap( PacUserName.Buffer );
|
|
}
|
|
|
|
return Status ;
|
|
}
|
|
|
|
#define ISSUER_HEADER L"<I>"
|
|
#define CCH_ISSUER_HEADER 3
|
|
#define SUBJECT_HEADER L"<S>"
|
|
#define CCH_SUBJECT_HEADER 3
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslGetNameFromCertificate
|
|
//
|
|
// Synopsis: Extracts the UPN name from the certificate
|
|
//
|
|
// Arguments: [pCert] --
|
|
// [ppszName] --
|
|
// [pfMachineCert] --
|
|
//
|
|
// History: 8-8-2000 jbanes Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslGetNameFromCertificate(
|
|
PCCERT_CONTEXT pCert,
|
|
PWSTR * ppszName,
|
|
BOOL * pfMachineCert)
|
|
{
|
|
ULONG ExtensionIndex;
|
|
PWSTR pszName;
|
|
DWORD cbName;
|
|
|
|
*pfMachineCert = FALSE;
|
|
|
|
//
|
|
// See if cert has UPN in AltSubjectName->otherName
|
|
//
|
|
|
|
pszName = NULL;
|
|
|
|
for(ExtensionIndex = 0;
|
|
ExtensionIndex < pCert->pCertInfo->cExtension;
|
|
ExtensionIndex++)
|
|
{
|
|
if(strcmp(pCert->pCertInfo->rgExtension[ExtensionIndex].pszObjId,
|
|
szOID_SUBJECT_ALT_NAME2) == 0)
|
|
{
|
|
PCERT_ALT_NAME_INFO AltName = NULL;
|
|
DWORD AltNameStructSize = 0;
|
|
ULONG CertAltNameIndex = 0;
|
|
|
|
if(CryptDecodeObjectEx(pCert->dwCertEncodingType,
|
|
X509_ALTERNATE_NAME,
|
|
pCert->pCertInfo->rgExtension[ExtensionIndex].Value.pbData,
|
|
pCert->pCertInfo->rgExtension[ExtensionIndex].Value.cbData,
|
|
CRYPT_DECODE_ALLOC_FLAG,
|
|
NULL,
|
|
(PVOID)&AltName,
|
|
&AltNameStructSize))
|
|
{
|
|
|
|
for(CertAltNameIndex = 0; CertAltNameIndex < AltName->cAltEntry; CertAltNameIndex++)
|
|
{
|
|
PCERT_ALT_NAME_ENTRY AltNameEntry = &AltName->rgAltEntry[CertAltNameIndex];
|
|
|
|
if((CERT_ALT_NAME_OTHER_NAME == AltNameEntry->dwAltNameChoice) &&
|
|
(NULL != AltNameEntry->pOtherName) &&
|
|
(0 == strcmp(szOID_NT_PRINCIPAL_NAME, AltNameEntry->pOtherName->pszObjId)))
|
|
{
|
|
PCERT_NAME_VALUE PrincipalNameBlob = NULL;
|
|
DWORD PrincipalNameBlobSize = 0;
|
|
|
|
// We found a UPN!
|
|
if(CryptDecodeObjectEx(pCert->dwCertEncodingType,
|
|
X509_UNICODE_ANY_STRING,
|
|
AltNameEntry->pOtherName->Value.pbData,
|
|
AltNameEntry->pOtherName->Value.cbData,
|
|
CRYPT_DECODE_ALLOC_FLAG,
|
|
NULL,
|
|
(PVOID)&PrincipalNameBlob,
|
|
&PrincipalNameBlobSize))
|
|
{
|
|
pszName = LocalAlloc(LPTR, PrincipalNameBlob->Value.cbData + sizeof(WCHAR));
|
|
if(pszName != NULL)
|
|
{
|
|
CopyMemory(pszName, PrincipalNameBlob->Value.pbData, PrincipalNameBlob->Value.cbData);
|
|
}
|
|
|
|
LocalFree(PrincipalNameBlob);
|
|
PrincipalNameBlob = NULL;
|
|
|
|
if(pszName == NULL)
|
|
{
|
|
LocalFree(AltName);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
}
|
|
if(pszName)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
LocalFree(AltName);
|
|
AltName = NULL;
|
|
|
|
if(pszName)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// See if cert has DNS in AltSubjectName->pwszDNSName
|
|
//
|
|
|
|
if(pszName == NULL)
|
|
{
|
|
for(ExtensionIndex = 0;
|
|
ExtensionIndex < pCert->pCertInfo->cExtension;
|
|
ExtensionIndex++)
|
|
{
|
|
if(strcmp(pCert->pCertInfo->rgExtension[ExtensionIndex].pszObjId,
|
|
szOID_SUBJECT_ALT_NAME2) == 0)
|
|
{
|
|
PCERT_ALT_NAME_INFO AltName = NULL;
|
|
DWORD AltNameStructSize = 0;
|
|
ULONG CertAltNameIndex = 0;
|
|
|
|
if(CryptDecodeObjectEx(pCert->dwCertEncodingType,
|
|
X509_ALTERNATE_NAME,
|
|
pCert->pCertInfo->rgExtension[ExtensionIndex].Value.pbData,
|
|
pCert->pCertInfo->rgExtension[ExtensionIndex].Value.cbData,
|
|
CRYPT_DECODE_ALLOC_FLAG,
|
|
NULL,
|
|
(PVOID)&AltName,
|
|
&AltNameStructSize))
|
|
{
|
|
|
|
for(CertAltNameIndex = 0; CertAltNameIndex < AltName->cAltEntry; CertAltNameIndex++)
|
|
{
|
|
PCERT_ALT_NAME_ENTRY AltNameEntry = &AltName->rgAltEntry[CertAltNameIndex];
|
|
|
|
if((CERT_ALT_NAME_DNS_NAME == AltNameEntry->dwAltNameChoice) &&
|
|
(NULL != AltNameEntry->pwszDNSName))
|
|
{
|
|
PCERT_NAME_VALUE PrincipalNameBlob = NULL;
|
|
DWORD PrincipalNameBlobSize = 0;
|
|
|
|
// We found a DNS!
|
|
cbName = (wcslen(AltNameEntry->pwszDNSName) + 1) * sizeof(WCHAR);
|
|
|
|
pszName = LocalAlloc(LPTR, cbName);
|
|
if(pszName == NULL)
|
|
{
|
|
LocalFree(AltName);
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
CopyMemory(pszName, AltNameEntry->pwszDNSName, cbName);
|
|
*pfMachineCert = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
LocalFree(AltName);
|
|
AltName = NULL;
|
|
|
|
if(pszName)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// There was no UPN in the AltSubjectName, so look for
|
|
// one in the Subject Name, in case this is a B3 compatability
|
|
// cert.
|
|
//
|
|
|
|
if(pszName == NULL)
|
|
{
|
|
ULONG Length;
|
|
|
|
Length = CertGetNameString( pCert,
|
|
CERT_NAME_ATTR_TYPE,
|
|
0,
|
|
szOID_COMMON_NAME,
|
|
NULL,
|
|
0 );
|
|
|
|
if(Length)
|
|
{
|
|
pszName = LocalAlloc(LPTR, Length * sizeof(WCHAR));
|
|
|
|
if(!pszName)
|
|
{
|
|
return STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
if ( !CertGetNameStringW(pCert,
|
|
CERT_NAME_ATTR_TYPE,
|
|
0,
|
|
szOID_COMMON_NAME,
|
|
pszName,
|
|
Length))
|
|
{
|
|
return GetLastError();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(pszName)
|
|
{
|
|
*ppszName = pszName;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslTryUpn
|
|
//
|
|
// Synopsis: Tries to find the user by UPN encoded in Cert
|
|
//
|
|
// Arguments: [User] --
|
|
// [AuthData] --
|
|
// [AuthDataLen] --
|
|
//
|
|
// History: 5-11-98 RichardW Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslTryUpn(
|
|
PCCERT_CONTEXT User,
|
|
PUCHAR * AuthData,
|
|
PULONG AuthDataLen,
|
|
PWSTR * ReferencedDomain
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
UNICODE_STRING Upn = {0, 0, NULL};
|
|
UNICODE_STRING Cracked = {0};
|
|
UNICODE_STRING DnsDomain = {0};
|
|
UNICODE_STRING FlatName = { 0 };
|
|
ULONG SubStatus ;
|
|
BOOL fMachineCert;
|
|
PWSTR pszName = NULL;
|
|
|
|
|
|
*ReferencedDomain = NULL ;
|
|
|
|
|
|
//
|
|
// Get the client name from the cert
|
|
//
|
|
|
|
Status = SslGetNameFromCertificate(User, &pszName, &fMachineCert);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// now, try and find this guy:
|
|
//
|
|
|
|
if(fMachineCert)
|
|
{
|
|
Status = STATUS_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
// Search for "username@foo.com".
|
|
Upn.Buffer = pszName;
|
|
Upn.Length = wcslen(Upn.Buffer) * sizeof(WCHAR);
|
|
Upn.MaximumLength = Upn.Length + sizeof(WCHAR);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Looking for UPN name %ws\n", Upn.Buffer ));
|
|
|
|
Status = LsaTable->GetAuthDataForUser( &Upn,
|
|
SecNameFlat,
|
|
NULL,
|
|
AuthData,
|
|
AuthDataLen,
|
|
&FlatName );
|
|
|
|
if ( FlatName.Length )
|
|
{
|
|
LsaTable->AuditAccountLogon(
|
|
SE_AUDITID_ACCOUNT_MAPPED,
|
|
(BOOLEAN) NT_SUCCESS( Status ),
|
|
&SslPackageName,
|
|
&Upn,
|
|
&FlatName,
|
|
Status );
|
|
|
|
LsaTable->FreeLsaHeap( FlatName.Buffer );
|
|
}
|
|
}
|
|
|
|
|
|
if ( Status == STATUS_NOT_FOUND )
|
|
{
|
|
//
|
|
// Do the hacky check of seeing if this is our own domain, and
|
|
// if so, try opening the user as a flat, SAM name.
|
|
//
|
|
|
|
if(fMachineCert)
|
|
{
|
|
PWSTR pPeriod;
|
|
WCHAR ch;
|
|
|
|
pPeriod = wcschr( pszName, L'.' );
|
|
|
|
if(pPeriod)
|
|
{
|
|
if ( DnsNameCompare_W( (pPeriod+1), SslDnsDomainName ) )
|
|
{
|
|
ch = *(pPeriod + 1);
|
|
|
|
*pPeriod = L'$';
|
|
*(pPeriod + 1) = L'\0';
|
|
|
|
Upn.Buffer = pszName;
|
|
Upn.Length = wcslen( Upn.Buffer ) * sizeof( WCHAR );
|
|
Upn.MaximumLength = Upn.Length + sizeof(WCHAR);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Looking for machine name %ws\n", Upn.Buffer ));
|
|
|
|
// Search for "computer$".
|
|
Status = LsaTable->GetAuthDataForUser( &Upn,
|
|
SecNameSamCompatible,
|
|
NULL,
|
|
AuthData,
|
|
AuthDataLen,
|
|
NULL );
|
|
|
|
*pPeriod = L'.';
|
|
*(pPeriod + 1) = ch;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PWSTR AtSign;
|
|
|
|
AtSign = wcschr( pszName, L'@' );
|
|
|
|
if ( AtSign )
|
|
{
|
|
if ( DnsNameCompare_W( (AtSign+1), SslDnsDomainName ) )
|
|
{
|
|
*AtSign = L'\0';
|
|
|
|
Upn.Buffer = pszName;
|
|
Upn.Length = wcslen( Upn.Buffer ) * sizeof( WCHAR );
|
|
Upn.MaximumLength = Upn.Length + sizeof(WCHAR);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Looking for user name %ws\n", Upn.Buffer ));
|
|
|
|
// Search for "username".
|
|
Status = LsaTable->GetAuthDataForUser( &Upn,
|
|
SecNameSamCompatible,
|
|
NULL,
|
|
AuthData,
|
|
AuthDataLen,
|
|
NULL );
|
|
|
|
*AtSign = L'@';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Status == STATUS_NOT_FOUND )
|
|
{
|
|
Upn.Buffer = pszName;
|
|
Upn.Length = wcslen(Upn.Buffer) * sizeof(WCHAR);
|
|
Upn.MaximumLength = Upn.Length + sizeof(WCHAR);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Cracking name %ws at GC\n", Upn.Buffer ));
|
|
|
|
Status = LsaTable->CrackSingleName(
|
|
DS_USER_PRINCIPAL_NAME,
|
|
TRUE,
|
|
&Upn,
|
|
NULL,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
&Cracked,
|
|
&DnsDomain,
|
|
&SubStatus );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( SubStatus == 0 )
|
|
{
|
|
*ReferencedDomain = DnsDomain.Buffer ;
|
|
DnsDomain.Buffer = NULL;
|
|
}
|
|
|
|
if(Cracked.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( Cracked.Buffer );
|
|
}
|
|
if(DnsDomain.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( DnsDomain.Buffer );
|
|
}
|
|
|
|
Status = STATUS_NOT_FOUND ;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(pszName)
|
|
{
|
|
LocalFree(pszName);
|
|
}
|
|
|
|
return Status ;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ConvertNameString(UNICODE_STRING *Name)
|
|
{
|
|
PWSTR Comma1, Comma2;
|
|
|
|
//
|
|
// Scan through the name, converting "\r\n" to ",". This should be
|
|
// done by the CertNameToStr APIs, but that won't happen for a while.
|
|
//
|
|
|
|
Comma1 = Comma2 = Name->Buffer ;
|
|
while ( *Comma2 )
|
|
{
|
|
*Comma1 = *Comma2 ;
|
|
|
|
if ( *Comma2 == L'\r' )
|
|
{
|
|
if ( *(Comma2 + 1) == L'\n' )
|
|
{
|
|
*Comma1 = L',';
|
|
Comma2++ ;
|
|
}
|
|
}
|
|
|
|
Comma1++;
|
|
Comma2++;
|
|
}
|
|
|
|
*Comma1 = L'\0';
|
|
|
|
Name->Length = wcslen( Name->Buffer ) * sizeof( WCHAR );
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslTryCompoundName
|
|
//
|
|
// Synopsis: Tries to find the user by concatenating the issuer and subject
|
|
// names, and looking for an AlternateSecurityId.
|
|
//
|
|
// Arguments: [User] --
|
|
// [AuthData] --
|
|
// [AuthDataLen] --
|
|
//
|
|
// History: 5-11-98 RichardW Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslTryCompoundName(
|
|
PCCERT_CONTEXT User,
|
|
PUCHAR * AuthData,
|
|
PULONG AuthDataLen,
|
|
PWSTR * ReferencedDomain
|
|
)
|
|
{
|
|
UNICODE_STRING CompoundName ;
|
|
ULONG Length ;
|
|
ULONG IssuerLength ;
|
|
NTSTATUS Status ;
|
|
PWSTR Current ;
|
|
PWSTR Comma1, Comma2 ;
|
|
UNICODE_STRING Cracked = {0};
|
|
UNICODE_STRING DnsDomain = {0};
|
|
UNICODE_STRING FlatName = { 0 };
|
|
ULONG SubStatus ;
|
|
const DWORD dwNameToStrFlags = CERT_X500_NAME_STR |
|
|
CERT_NAME_STR_NO_PLUS_FLAG |
|
|
CERT_NAME_STR_CRLF_FLAG;
|
|
|
|
*ReferencedDomain = NULL ;
|
|
|
|
IssuerLength = CertNameToStr( User->dwCertEncodingType,
|
|
&User->pCertInfo->Issuer,
|
|
dwNameToStrFlags,
|
|
NULL,
|
|
0 );
|
|
|
|
Length = CertNameToStr( User->dwCertEncodingType,
|
|
&User->pCertInfo->Subject,
|
|
dwNameToStrFlags,
|
|
NULL,
|
|
0 );
|
|
|
|
if ( ( IssuerLength == 0 ) ||
|
|
( Length == 0 ) )
|
|
{
|
|
return STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
CompoundName.MaximumLength = (USHORT) (Length + IssuerLength +
|
|
CCH_ISSUER_HEADER + CCH_SUBJECT_HEADER) *
|
|
sizeof( WCHAR ) ;
|
|
|
|
CompoundName.Buffer = LocalAlloc( LMEM_FIXED, CompoundName.MaximumLength );
|
|
|
|
if ( CompoundName.Buffer )
|
|
{
|
|
wcscpy( CompoundName.Buffer, ISSUER_HEADER );
|
|
Current = CompoundName.Buffer + CCH_ISSUER_HEADER ;
|
|
|
|
IssuerLength = CertNameToStrW( User->dwCertEncodingType,
|
|
&User->pCertInfo->Issuer,
|
|
dwNameToStrFlags,
|
|
Current,
|
|
IssuerLength );
|
|
|
|
Current += IssuerLength - 1 ;
|
|
wcscpy( Current, SUBJECT_HEADER );
|
|
Current += CCH_SUBJECT_HEADER ;
|
|
|
|
Length = CertNameToStrW( User->dwCertEncodingType,
|
|
&User->pCertInfo->Subject,
|
|
dwNameToStrFlags,
|
|
Current,
|
|
Length );
|
|
|
|
ConvertNameString(&CompoundName);
|
|
|
|
Status = LsaTable->GetAuthDataForUser( &CompoundName,
|
|
SecNameAlternateId,
|
|
&SslNamePrefix,
|
|
AuthData,
|
|
AuthDataLen,
|
|
&FlatName );
|
|
|
|
if ( FlatName.Length )
|
|
{
|
|
LsaTable->AuditAccountLogon(
|
|
SE_AUDITID_ACCOUNT_MAPPED,
|
|
(BOOLEAN) NT_SUCCESS( Status ),
|
|
&SslPackageName,
|
|
&CompoundName,
|
|
&FlatName,
|
|
Status );
|
|
|
|
LsaTable->FreeLsaHeap( FlatName.Buffer );
|
|
}
|
|
|
|
if ( Status == STATUS_NOT_FOUND )
|
|
{
|
|
Status = LsaTable->CrackSingleName(
|
|
DS_ALT_SECURITY_IDENTITIES_NAME,
|
|
TRUE,
|
|
&CompoundName,
|
|
&SslNamePrefix,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
&Cracked,
|
|
&DnsDomain,
|
|
&SubStatus );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( SubStatus == 0 )
|
|
{
|
|
*ReferencedDomain = DnsDomain.Buffer ;
|
|
DnsDomain.Buffer = NULL;
|
|
}
|
|
|
|
if(Cracked.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( Cracked.Buffer );
|
|
}
|
|
if(DnsDomain.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( DnsDomain.Buffer );
|
|
}
|
|
|
|
Status = STATUS_NOT_FOUND ;
|
|
}
|
|
|
|
}
|
|
|
|
LocalFree( CompoundName.Buffer );
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
return Status ;
|
|
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslTryIssuer
|
|
//
|
|
// Synopsis: Tries to find a user that has an issuer mapped to it.
|
|
//
|
|
// Arguments: [User] --
|
|
// [AuthData] --
|
|
// [AuthDataLen] --
|
|
//
|
|
// History: 5-11-98 RichardW Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslTryIssuer(
|
|
PBYTE pIssuer,
|
|
DWORD cbIssuer,
|
|
PUCHAR * AuthData,
|
|
PULONG AuthDataLen,
|
|
PWSTR * ReferencedDomain
|
|
)
|
|
{
|
|
UNICODE_STRING IssuerName ;
|
|
ULONG IssuerLength ;
|
|
NTSTATUS Status ;
|
|
UNICODE_STRING Cracked = { 0 };
|
|
UNICODE_STRING DnsDomain = { 0 };
|
|
UNICODE_STRING FlatName = { 0 };
|
|
ULONG SubStatus ;
|
|
const DWORD dwNameToStrFlags = CERT_X500_NAME_STR |
|
|
CERT_NAME_STR_NO_PLUS_FLAG |
|
|
CERT_NAME_STR_CRLF_FLAG;
|
|
CERT_NAME_BLOB Issuer;
|
|
|
|
*ReferencedDomain = NULL ;
|
|
|
|
Issuer.pbData = pIssuer;
|
|
Issuer.cbData = cbIssuer;
|
|
|
|
IssuerLength = CertNameToStr( CRYPT_ASN_ENCODING,
|
|
&Issuer,
|
|
dwNameToStrFlags,
|
|
NULL,
|
|
0 );
|
|
|
|
if ( IssuerLength == 0 )
|
|
{
|
|
return STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
IssuerName.MaximumLength = (USHORT)(CCH_ISSUER_HEADER + IssuerLength) * sizeof( WCHAR ) ;
|
|
|
|
IssuerName.Buffer = LocalAlloc( LMEM_FIXED, IssuerName.MaximumLength );
|
|
|
|
if ( IssuerName.Buffer )
|
|
{
|
|
wcscpy( IssuerName.Buffer, ISSUER_HEADER );
|
|
|
|
IssuerLength = CertNameToStrW(CRYPT_ASN_ENCODING,
|
|
&Issuer,
|
|
dwNameToStrFlags,
|
|
IssuerName.Buffer + CCH_ISSUER_HEADER,
|
|
IssuerLength );
|
|
|
|
ConvertNameString(&IssuerName);
|
|
|
|
|
|
|
|
Status = LsaTable->GetAuthDataForUser( &IssuerName,
|
|
SecNameAlternateId,
|
|
&SslNamePrefix,
|
|
AuthData,
|
|
AuthDataLen,
|
|
&FlatName );
|
|
|
|
if ( FlatName.Length )
|
|
{
|
|
LsaTable->AuditAccountLogon(
|
|
SE_AUDITID_ACCOUNT_MAPPED,
|
|
(BOOLEAN) NT_SUCCESS( Status ),
|
|
&SslPackageName,
|
|
&IssuerName,
|
|
&FlatName,
|
|
Status );
|
|
|
|
LsaTable->FreeLsaHeap( FlatName.Buffer );
|
|
}
|
|
|
|
if ( Status == STATUS_NOT_FOUND )
|
|
{
|
|
Status = LsaTable->CrackSingleName(
|
|
DS_ALT_SECURITY_IDENTITIES_NAME,
|
|
TRUE,
|
|
&IssuerName,
|
|
&SslNamePrefix,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
&Cracked,
|
|
&DnsDomain,
|
|
&SubStatus );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( SubStatus == 0 )
|
|
{
|
|
*ReferencedDomain = DnsDomain.Buffer ;
|
|
DnsDomain.Buffer = NULL;
|
|
}
|
|
|
|
if(Cracked.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( Cracked.Buffer );
|
|
}
|
|
if(DnsDomain.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( DnsDomain.Buffer );
|
|
}
|
|
|
|
Status = STATUS_NOT_FOUND ;
|
|
}
|
|
|
|
}
|
|
|
|
LocalFree(IssuerName.Buffer);
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
return Status ;
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslMapCertToUserPac
|
|
//
|
|
// Synopsis: Maps a certificate to a user (hopefully) and the PAC,
|
|
//
|
|
// Arguments: [Request] --
|
|
// [UserPac] --
|
|
// [UserPacLen] --
|
|
//
|
|
// History: 5-11-98 RichardW Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslMapCertToUserPac(
|
|
IN PSSL_CERT_LOGON_REQ Request,
|
|
OUT PUCHAR * UserPac,
|
|
OUT PULONG UserPacLen,
|
|
OUT PWSTR * ReferencedDomain
|
|
)
|
|
{
|
|
PCCERT_CONTEXT User ;
|
|
NTSTATUS Status = STATUS_LOGON_FAILURE;
|
|
NTSTATUS SubStatus = STATUS_NOT_FOUND;
|
|
UNICODE_STRING UserName ;
|
|
HANDLE UserHandle ;
|
|
ULONG i;
|
|
|
|
*ReferencedDomain = NULL;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertToUserPac called\n" ));
|
|
|
|
User = CertCreateCertificateContext( X509_ASN_ENCODING,
|
|
(PBYTE)Request + Request->OffsetCertificate,
|
|
Request->CertLength );
|
|
|
|
if ( !User )
|
|
{
|
|
Status = STATUS_NO_MEMORY ;
|
|
|
|
goto Cleanup ;
|
|
}
|
|
|
|
|
|
//
|
|
// First, try the UPN
|
|
//
|
|
|
|
|
|
if(Request->Flags & REQ_UPN_MAPPING)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Trying UPN mapping\n" ));
|
|
|
|
Status = SslTryUpn( User,
|
|
UserPac,
|
|
UserPacLen,
|
|
ReferencedDomain );
|
|
|
|
if ( NT_SUCCESS( Status ) ||
|
|
( *ReferencedDomain ) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Failed with error 0x%x\n", Status ));
|
|
}
|
|
|
|
|
|
//
|
|
// Swing and a miss. Try the constructed issuer+subject name
|
|
//
|
|
|
|
|
|
if(Request->Flags & REQ_SUBJECT_MAPPING)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Trying Subject mapping\n" ));
|
|
|
|
Status = SslTryCompoundName( User,
|
|
UserPac,
|
|
UserPacLen,
|
|
ReferencedDomain );
|
|
|
|
if ( NT_SUCCESS( Status ) ||
|
|
( *ReferencedDomain ) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Failed with error 0x%x\n", Status ));
|
|
|
|
// Return error code from issuer+subject name mapping
|
|
// in preference to the error code from many-to-one mapping.
|
|
SubStatus = Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Strike two. Try the issuer for a many-to-one mapping:
|
|
//
|
|
|
|
if(Request->Flags & REQ_ISSUER_MAPPING)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Trying issuer mapping\n" ));
|
|
|
|
if(Request->Flags & REQ_ISSUER_CHAIN_MAPPING)
|
|
{
|
|
// Loop through each issuer in the certificate chain.
|
|
for(i = 0; i < Request->CertCount; i++)
|
|
{
|
|
Status = SslTryIssuer( (PBYTE)Request + Request->NameInfo[i].IssuerOffset,
|
|
Request->NameInfo[i].IssuerLength,
|
|
UserPac,
|
|
UserPacLen,
|
|
ReferencedDomain );
|
|
|
|
if ( NT_SUCCESS( Status ) ||
|
|
( *ReferencedDomain ) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Extract the issuer name from the certificate and try
|
|
// to map it.
|
|
Status = SslTryIssuer( User->pCertInfo->Issuer.pbData,
|
|
User->pCertInfo->Issuer.cbData,
|
|
UserPac,
|
|
UserPacLen,
|
|
ReferencedDomain );
|
|
|
|
if ( NT_SUCCESS( Status ) ||
|
|
( *ReferencedDomain ) )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Failed with error 0x%x\n", Status ));
|
|
}
|
|
|
|
|
|
//
|
|
// Certificate mapping failed. Decide what error code to return.
|
|
//
|
|
|
|
if(Status == STATUS_OBJECT_NAME_COLLISION ||
|
|
SubStatus == STATUS_OBJECT_NAME_COLLISION)
|
|
{
|
|
Status = SEC_E_MULTIPLE_ACCOUNTS;
|
|
}
|
|
else if(Status != STATUS_NO_MEMORY)
|
|
{
|
|
Status = STATUS_LOGON_FAILURE ;
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
|
|
if ( User )
|
|
{
|
|
CertFreeCertificateContext( User );
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertToUserPac returned 0x%x\n", Status ));
|
|
|
|
return Status ;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
MapperVerifyClientChain(
|
|
PCCERT_CONTEXT pCertContext,
|
|
DWORD dwMapperFlags,
|
|
DWORD * pdwMethods,
|
|
NTSTATUS * pVerifyStatus,
|
|
PCCERT_CHAIN_CONTEXT *ppChainContext) // optional
|
|
{
|
|
DWORD dwCertFlags = 0;
|
|
DWORD dwIgnoreErrors = 0;
|
|
NTSTATUS Status;
|
|
|
|
*pdwMethods = 0;
|
|
*pVerifyStatus = STATUS_SUCCESS;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Checking to see if cert is verified.\n" ));
|
|
|
|
if(dwMapperFlags & SCH_FLAG_REVCHECK_END_CERT)
|
|
dwCertFlags |= CERT_CHAIN_REVOCATION_CHECK_END_CERT;
|
|
if(dwMapperFlags & SCH_FLAG_REVCHECK_CHAIN)
|
|
dwCertFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN;
|
|
if(dwMapperFlags & SCH_FLAG_REVCHECK_CHAIN_EXCLUDE_ROOT)
|
|
dwCertFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
|
|
if(dwMapperFlags & SCH_FLAG_IGNORE_NO_REVOCATION_CHECK)
|
|
dwIgnoreErrors |= CRED_FLAG_IGNORE_NO_REVOCATION_CHECK;
|
|
if(dwMapperFlags & SCH_FLAG_IGNORE_REVOCATION_OFFLINE)
|
|
dwIgnoreErrors |= CRED_FLAG_IGNORE_REVOCATION_OFFLINE;
|
|
|
|
|
|
// Check to see if the certificate chain is properly signed all the way
|
|
// up and that we trust the issuer of the root certificate.
|
|
Status = VerifyClientCertificate(pCertContext,
|
|
dwCertFlags,
|
|
dwIgnoreErrors,
|
|
CERT_CHAIN_POLICY_SSL,
|
|
ppChainContext);
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_WARN, "Client certificate failed to verify with SSL policy (0x%x)\n", Status));
|
|
LogBogusClientCertEvent(pCertContext, Status);
|
|
return Status;
|
|
}
|
|
|
|
// Turn on Subject and Issuer mapping.
|
|
*pdwMethods |= REQ_SUBJECT_MAPPING | REQ_ISSUER_MAPPING;
|
|
|
|
|
|
// Check to see if the certificate chain is valid for UPN mapping.
|
|
Status = VerifyClientCertificate(pCertContext,
|
|
dwCertFlags,
|
|
dwIgnoreErrors,
|
|
CERT_CHAIN_POLICY_NT_AUTH,
|
|
NULL);
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Turn on UPN mapping.
|
|
*pdwMethods |= REQ_UPN_MAPPING;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_WARN, "Client certificate failed to verify with NT_AUTH policy (0x%x)\n", Status));
|
|
LogFastMappingFailureEvent(pCertContext, Status);
|
|
*pVerifyStatus = Status;
|
|
}
|
|
|
|
DebugLog((DEB_TRACE, "Client certificate verified with methods: 0x%x\n", *pdwMethods));
|
|
|
|
return SEC_E_OK;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalMapCredential(
|
|
IN PHMAPPER Mapper,
|
|
IN DWORD CredentialType,
|
|
VOID const *pCredential,
|
|
VOID const *pAuthority,
|
|
OUT HLOCATOR * phLocator
|
|
)
|
|
{
|
|
PCCERT_CONTEXT pCert = (PCERT_CONTEXT)pCredential;
|
|
PMSV1_0_PASSTHROUGH_RESPONSE Response = NULL ;
|
|
PSSL_CERT_LOGON_REQ pRequest = NULL;
|
|
PSSL_CERT_LOGON_RESP CertResp ;
|
|
DWORD cbRequest;
|
|
PUCHAR Pac = NULL ;
|
|
ULONG PacLength ;
|
|
PUCHAR ExpandedPac = NULL ;
|
|
ULONG ExpandedPacLength ;
|
|
NTSTATUS Status ;
|
|
NTSTATUS VerifyStatus ;
|
|
HANDLE Token ;
|
|
LUID LogonId ;
|
|
DWORD dwMethods ;
|
|
PWSTR ReferencedDomain ;
|
|
UNICODE_STRING DomainName ;
|
|
UNICODE_STRING AccountDomain = { 0 };
|
|
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslLocalMapCredential, context %x\n", Mapper ));
|
|
|
|
if ( CredentialType != X509_ASN_CHAIN )
|
|
{
|
|
return( SEC_E_UNKNOWN_CREDENTIALS );
|
|
}
|
|
|
|
//
|
|
// Validate client certificate, and obtain pointer to
|
|
// entire certificate chain.
|
|
//
|
|
|
|
Status = MapperVerifyClientChain(pCert,
|
|
Mapper->m_dwFlags,
|
|
&dwMethods,
|
|
&VerifyStatus,
|
|
&pChainContext);
|
|
if(FAILED(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Build the logon request.
|
|
//
|
|
|
|
Status = SslBuildCertLogonRequest(pChainContext,
|
|
dwMethods,
|
|
&pRequest,
|
|
&cbRequest);
|
|
|
|
CertFreeCertificateChain(pChainContext);
|
|
pChainContext = NULL;
|
|
|
|
if(FAILED(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
Status = SslMapCertToUserPac(
|
|
pRequest,
|
|
&Pac,
|
|
&PacLength,
|
|
&ReferencedDomain );
|
|
|
|
if ( !NT_SUCCESS( Status ) &&
|
|
( ReferencedDomain != NULL ) )
|
|
{
|
|
//
|
|
// Didn't find it at this DC, but another domain appears to
|
|
// have the mapping. Forward it there:
|
|
//
|
|
|
|
RtlInitUnicodeString( &DomainName, ReferencedDomain );
|
|
|
|
Status = SslMapCertAtDC(
|
|
&DomainName,
|
|
pRequest,
|
|
pRequest->Length,
|
|
&Response );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
CertResp = (PSSL_CERT_LOGON_RESP) Response->ValidationData ;
|
|
Pac = (((PUCHAR) CertResp) + CertResp->OffsetAuthData);
|
|
PacLength = CertResp->AuthDataLength ;
|
|
|
|
//
|
|
// older servers (pre 2010 or so) won't return the full structure,
|
|
// so we need to examine it carefully.
|
|
|
|
if ( CertResp->Length - CertResp->AuthDataLength <= sizeof( SSL_CERT_LOGON_RESP ))
|
|
{
|
|
AccountDomain = SslDomainName ;
|
|
}
|
|
else
|
|
{
|
|
if ( CertResp->DomainLength < 65536 )
|
|
{
|
|
AccountDomain.Length = (USHORT) CertResp->DomainLength ;
|
|
AccountDomain.MaximumLength = AccountDomain.Length ;
|
|
AccountDomain.Buffer = (PWSTR) (((PUCHAR) CertResp) + CertResp->OffsetDomain );
|
|
}
|
|
else
|
|
{
|
|
AccountDomain = SslDomainName ;
|
|
}
|
|
}
|
|
}
|
|
|
|
LsaTable->FreeLsaHeap( ReferencedDomain );
|
|
|
|
}
|
|
else
|
|
{
|
|
AccountDomain = SslDomainName ;
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Status = LsaTable->ExpandAuthDataForDomain(
|
|
Pac,
|
|
PacLength,
|
|
NULL,
|
|
&ExpandedPac,
|
|
&ExpandedPacLength );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
//
|
|
// Since we're about to replace the PAC
|
|
// pointer, determine if we got it indirectly
|
|
// (in the response from a remote server)
|
|
// or directly from this DC
|
|
//
|
|
|
|
if ( Response == NULL )
|
|
{
|
|
//
|
|
// This is a direct one. Free the old
|
|
// copy
|
|
//
|
|
|
|
LsaTable->FreeLsaHeap( Pac );
|
|
}
|
|
|
|
Pac = ExpandedPac ;
|
|
PacLength = ExpandedPacLength ;
|
|
|
|
if ( Response == NULL )
|
|
{
|
|
//
|
|
// If we don't have a response, then the pac
|
|
// will get freed. If it is out of the response
|
|
// then leave it set, and it will be caught in
|
|
// the cleanup.
|
|
//
|
|
|
|
ExpandedPac = NULL ;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
|
|
|
|
VerifyStatus = STATUS_SUCCESS;
|
|
|
|
Status = SslCreateTokenFromPac( Pac,
|
|
PacLength,
|
|
&AccountDomain,
|
|
&LogonId,
|
|
&Token );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
*phLocator = (HLOCATOR) Token ;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if(pRequest)
|
|
{
|
|
LocalFree(pRequest);
|
|
}
|
|
|
|
if ( Response )
|
|
{
|
|
LsaTable->FreeReturnBuffer( Response );
|
|
}
|
|
else
|
|
{
|
|
LsaTable->FreeLsaHeap( Pac );
|
|
}
|
|
|
|
if ( ExpandedPac )
|
|
{
|
|
LsaTable->FreeLsaHeap( ExpandedPac );
|
|
}
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_WARN, "Certificate mapping failed (0x%x)\n", Status));
|
|
|
|
LogCertMappingFailureEvent(Status);
|
|
|
|
if(!NT_SUCCESS(VerifyStatus))
|
|
{
|
|
// Return certificate validation error code, unless the mapper
|
|
// error has already been mapped to a proper sspi error code.
|
|
if(HRESULT_FACILITY(Status) != FACILITY_SECURITY)
|
|
{
|
|
Status = VerifyStatus;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
SslDoClientRequest(
|
|
IN PLSA_CLIENT_REQUEST ClientRequest,
|
|
IN PVOID ProtocolSubmitBuffer,
|
|
IN PVOID ClientBufferBase,
|
|
IN ULONG SubmitBufferLen,
|
|
OUT PVOID * ProtocolReturnBuffer,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PNTSTATUS ProtocolStatus
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
PSSL_CERT_LOGON_REQ Request ;
|
|
PSSL_CERT_LOGON_RESP Response, IndirectResponse ;
|
|
PUCHAR Pac = NULL ;
|
|
PUCHAR ExpandedPac = NULL ;
|
|
ULONG PacLength ;
|
|
ULONG ExpandedPacLength ;
|
|
PWSTR ReferencedDomain = NULL;
|
|
PWSTR FirstDot ;
|
|
UNICODE_STRING DomainName = { 0 };
|
|
PMSV1_0_PASSTHROUGH_RESPONSE MsvResponse = NULL ;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Handling request to do mapping\n" ));
|
|
|
|
if ( ARGUMENT_PRESENT( ProtocolReturnBuffer ) )
|
|
{
|
|
*ProtocolReturnBuffer = NULL ;
|
|
}
|
|
|
|
//
|
|
// Attempt to map the certificate locally.
|
|
//
|
|
|
|
Request = (PSSL_CERT_LOGON_REQ) ProtocolSubmitBuffer ;
|
|
|
|
Status = SslMapCertToUserPac(
|
|
Request,
|
|
&Pac,
|
|
&PacLength,
|
|
&ReferencedDomain );
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Local lookup returns %x\n", Status ));
|
|
|
|
if ( !NT_SUCCESS( Status ) &&
|
|
( ReferencedDomain != NULL ) )
|
|
{
|
|
//
|
|
// Didn't find it at this DC, but another domain appears to
|
|
// have the mapping. Forward it there:
|
|
//
|
|
|
|
RtlInitUnicodeString( &DomainName, ReferencedDomain );
|
|
|
|
DsysAssert( !DnsNameCompare_W( ReferencedDomain, SslDnsDomainName ) );
|
|
DsysAssert( !RtlEqualUnicodeString( &DomainName, &SslDomainName, TRUE ) );
|
|
|
|
if ( DnsNameCompare_W( ReferencedDomain, SslDnsDomainName ) ||
|
|
RtlEqualUnicodeString( &DomainName, &SslDomainName, TRUE ) )
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "GC is out of sync, bailing on this user\n" ));
|
|
Status = STATUS_LOGON_FAILURE ;
|
|
|
|
}
|
|
else
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Mapping certificate at DC for domain %ws\n",
|
|
ReferencedDomain ));
|
|
|
|
Status = SslMapCertAtDC(
|
|
&DomainName,
|
|
Request,
|
|
Request->Length,
|
|
&MsvResponse );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
IndirectResponse = (PSSL_CERT_LOGON_RESP) MsvResponse->ValidationData ;
|
|
|
|
Pac = (((PUCHAR) IndirectResponse) + IndirectResponse->OffsetAuthData);
|
|
|
|
PacLength = IndirectResponse->AuthDataLength ;
|
|
|
|
FirstDot = wcschr( ReferencedDomain, L'.' );
|
|
|
|
if ( FirstDot )
|
|
{
|
|
*FirstDot = L'\0';
|
|
RtlInitUnicodeString( &DomainName, ReferencedDomain );
|
|
}
|
|
|
|
if ( IndirectResponse->Length - IndirectResponse->AuthDataLength <= sizeof( SSL_CERT_LOGON_RESP ))
|
|
{
|
|
//
|
|
// use the first token from the referenced domain
|
|
//
|
|
|
|
NOTHING ;
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( IndirectResponse->DomainLength < 65536 )
|
|
{
|
|
DomainName.Length = (USHORT) IndirectResponse->DomainLength ;
|
|
DomainName.MaximumLength = DomainName.Length ;
|
|
DomainName.Buffer = (PWSTR) (((PUCHAR) IndirectResponse) + IndirectResponse->OffsetDomain );
|
|
}
|
|
else
|
|
{
|
|
NOTHING ;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
DomainName = SslDomainName ;
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
//
|
|
// expand resource groups
|
|
//
|
|
|
|
Status = LsaTable->ExpandAuthDataForDomain(
|
|
Pac,
|
|
PacLength,
|
|
NULL,
|
|
&ExpandedPac,
|
|
&ExpandedPacLength );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
//
|
|
// Careful manipulation of pointers to handle
|
|
// the free cases in the cleanup. This is
|
|
// better explained up one function where
|
|
// the expand call is also made.
|
|
//
|
|
if ( MsvResponse == NULL )
|
|
{
|
|
LsaTable->FreeLsaHeap( Pac );
|
|
}
|
|
|
|
Pac = ExpandedPac ;
|
|
PacLength = ExpandedPacLength ;
|
|
|
|
if ( MsvResponse == NULL )
|
|
{
|
|
ExpandedPac = NULL ;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
*ReturnBufferLength = 0;
|
|
*ProtocolStatus = Status ;
|
|
|
|
Status = STATUS_SUCCESS ;
|
|
|
|
goto Cleanup ;
|
|
|
|
}
|
|
|
|
//
|
|
// Construct the response blob:
|
|
//
|
|
|
|
Response = VirtualAlloc(
|
|
NULL,
|
|
sizeof( SSL_CERT_LOGON_RESP ) + PacLength + DomainName.Length,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE );
|
|
|
|
if ( Response )
|
|
{
|
|
Response->MessageType = SSL_LOOKUP_CERT_MESSAGE;
|
|
Response->Length = sizeof( SSL_CERT_LOGON_RESP ) +
|
|
PacLength + DomainName.Length ;
|
|
|
|
Response->OffsetAuthData = sizeof( SSL_CERT_LOGON_RESP );
|
|
Response->AuthDataLength = PacLength ;
|
|
|
|
RtlCopyMemory(
|
|
( Response + 1 ),
|
|
Pac,
|
|
PacLength );
|
|
|
|
Response->OffsetDomain = sizeof( SSL_CERT_LOGON_RESP ) + PacLength ;
|
|
Response->DomainLength = DomainName.Length ;
|
|
|
|
RtlCopyMemory( (PUCHAR) Response + Response->OffsetDomain,
|
|
DomainName.Buffer,
|
|
DomainName.Length );
|
|
|
|
*ProtocolReturnBuffer = Response ;
|
|
*ReturnBufferLength = Response->Length ;
|
|
*ProtocolStatus = STATUS_SUCCESS ;
|
|
|
|
Status = STATUS_SUCCESS ;
|
|
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if ( MsvResponse == NULL )
|
|
{
|
|
LsaTable->FreeLsaHeap( Pac );
|
|
}
|
|
else
|
|
{
|
|
LsaTable->FreeReturnBuffer( MsvResponse );
|
|
}
|
|
|
|
if ( ExpandedPac )
|
|
{
|
|
LsaTable->FreeLsaHeap( ExpandedPac );
|
|
}
|
|
|
|
if ( ReferencedDomain )
|
|
{
|
|
LsaTable->FreeLsaHeap( ReferencedDomain );
|
|
}
|
|
|
|
|
|
return Status ;
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslBuildCertLogonRequest
|
|
//
|
|
// Synopsis: Builds a certificate logon request to send to the server.
|
|
//
|
|
// Arguments: [pChainContext] --
|
|
// [dwMethods] --
|
|
// [ppRequest] --
|
|
// [pcbRequest] --
|
|
//
|
|
// History: 2-26-2001 Jbanes Created
|
|
//
|
|
// Notes: The certificate data that this function builds
|
|
// looks something like this:
|
|
//
|
|
// typedef struct _SSL_CERT_LOGON_REQ {
|
|
// ULONG MessageType ;
|
|
// ULONG Length ;
|
|
// ULONG OffsetCertficate ;
|
|
// ULONG CertLength ;
|
|
// ULONG Flags;
|
|
// ULONG CertCount;
|
|
// SSL_CERT_NAME_INFO NameInfo[1];
|
|
// } SSL_CERT_LOGON_REQ, * PSSL_CERT_LOGON_REQ ;
|
|
//
|
|
// <client certificate>
|
|
// <issuer #1 name>
|
|
// <issuer #2 name>
|
|
// ...
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
WINAPI
|
|
SslBuildCertLogonRequest(
|
|
PCCERT_CHAIN_CONTEXT pChainContext,
|
|
DWORD dwMethods,
|
|
PSSL_CERT_LOGON_REQ *ppRequest,
|
|
PDWORD pcbRequest)
|
|
{
|
|
PCERT_SIMPLE_CHAIN pSimpleChain;
|
|
PCCERT_CONTEXT pCert;
|
|
PCCERT_CONTEXT pCurrentCert;
|
|
PSSL_CERT_LOGON_REQ pCertReq = NULL;
|
|
DWORD Size;
|
|
DWORD Offset;
|
|
DWORD CertCount;
|
|
NTSTATUS Status;
|
|
ULONG i;
|
|
|
|
|
|
//
|
|
// Compute the request size.
|
|
//
|
|
|
|
pSimpleChain = pChainContext->rgpChain[0];
|
|
|
|
pCert = pSimpleChain->rgpElement[0]->pCertContext;
|
|
|
|
Size = sizeof(SSL_CERT_LOGON_REQ) +
|
|
pCert->cbCertEncoded;
|
|
|
|
CertCount = 0;
|
|
|
|
for(i = 0; i < pSimpleChain->cElement; i++)
|
|
{
|
|
pCurrentCert = pSimpleChain->rgpElement[i]->pCertContext;
|
|
|
|
if(i > 0)
|
|
{
|
|
// Verify that this is not a root certificate.
|
|
if(CertCompareCertificateName(pCurrentCert->dwCertEncodingType,
|
|
&pCurrentCert->pCertInfo->Issuer,
|
|
&pCurrentCert->pCertInfo->Subject))
|
|
{
|
|
break;
|
|
}
|
|
|
|
Size += sizeof(SSL_CERT_NAME_INFO);
|
|
}
|
|
|
|
Size += pCurrentCert->pCertInfo->Issuer.cbData;
|
|
CertCount++;
|
|
}
|
|
|
|
Size = ROUND_UP_COUNT( Size, ALIGN_DWORD );
|
|
|
|
|
|
//
|
|
// Build the request.
|
|
//
|
|
|
|
pCertReq = (PSSL_CERT_LOGON_REQ)LocalAlloc(LPTR, Size);
|
|
|
|
if ( !pCertReq )
|
|
{
|
|
return SEC_E_INSUFFICIENT_MEMORY ;
|
|
}
|
|
|
|
Offset = sizeof(SSL_CERT_LOGON_REQ) + (CertCount - 1) * sizeof(SSL_CERT_NAME_INFO);
|
|
|
|
pCertReq->MessageType = SSL_LOOKUP_CERT_MESSAGE;
|
|
pCertReq->Length = Size;
|
|
pCertReq->OffsetCertificate = Offset;
|
|
pCertReq->CertLength = pCert->cbCertEncoded;
|
|
pCertReq->Flags = dwMethods | REQ_ISSUER_CHAIN_MAPPING;
|
|
|
|
RtlCopyMemory((PBYTE)pCertReq + Offset,
|
|
pCert->pbCertEncoded,
|
|
pCert->cbCertEncoded);
|
|
Offset += pCert->cbCertEncoded;
|
|
|
|
pCertReq->CertCount = CertCount;
|
|
|
|
for(i = 0; i < CertCount; i++)
|
|
{
|
|
pCurrentCert = pSimpleChain->rgpElement[i]->pCertContext;
|
|
|
|
pCertReq->NameInfo[i].IssuerOffset = Offset;
|
|
pCertReq->NameInfo[i].IssuerLength = pCurrentCert->pCertInfo->Issuer.cbData;
|
|
|
|
RtlCopyMemory((PBYTE)pCertReq + Offset,
|
|
pCurrentCert->pCertInfo->Issuer.pbData,
|
|
pCurrentCert->pCertInfo->Issuer.cbData);
|
|
Offset += pCurrentCert->pCertInfo->Issuer.cbData;
|
|
}
|
|
|
|
Offset = ROUND_UP_COUNT( Offset, ALIGN_DWORD );
|
|
|
|
#if DBG
|
|
DsysAssert(Offset == Size);
|
|
#endif
|
|
|
|
//
|
|
// Return completed request.
|
|
//
|
|
|
|
*ppRequest = pCertReq;
|
|
*pcbRequest = Size;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslMapCertAtDC
|
|
//
|
|
// Synopsis: Maps a certificate to a user (hopefully) and the PAC,
|
|
//
|
|
// Arguments: [DomainName] --
|
|
// [pCredential] --
|
|
// [cbCredential] --
|
|
// [DcResponse] --
|
|
//
|
|
// History: 5-11-1998 RichardW Created
|
|
// 2-26-2001 Jbanes Added certificate chaining support.
|
|
//
|
|
// Notes: The request that gets sent to the DC looks something
|
|
// like this:
|
|
//
|
|
// typedef struct _MSV1_0_PASSTHROUGH_REQUEST {
|
|
// MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType;
|
|
// UNICODE_STRING DomainName;
|
|
// UNICODE_STRING PackageName;
|
|
// ULONG DataLength;
|
|
// PUCHAR LogonData;
|
|
// ULONG Pad ;
|
|
// } MSV1_0_PASSTHROUGH_REQUEST, *PMSV1_0_PASSTHROUGH_REQUEST;
|
|
//
|
|
// <domain name>
|
|
// <package name>
|
|
// [ padding ]
|
|
//
|
|
// <credential>
|
|
// [ padding ]
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
WINAPI
|
|
SslMapCertAtDC(
|
|
PUNICODE_STRING DomainName,
|
|
VOID const *pCredential,
|
|
DWORD cbCredential,
|
|
PMSV1_0_PASSTHROUGH_RESPONSE * DcResponse
|
|
)
|
|
{
|
|
PUCHAR Pac ;
|
|
ULONG PacLength ;
|
|
NTSTATUS Status ;
|
|
PMSV1_0_PASSTHROUGH_REQUEST Request ;
|
|
PMSV1_0_PASSTHROUGH_RESPONSE Response ;
|
|
DWORD Size ;
|
|
DWORD RequestSize ;
|
|
DWORD ResponseSize ;
|
|
PUCHAR Where ;
|
|
NTSTATUS SubStatus ;
|
|
#if DBG
|
|
DWORD CheckSize2 ;
|
|
#endif
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Remote call to DC to do the mapping\n" ));
|
|
|
|
// Reality check size of certificate.
|
|
if(cbCredential > 0x4000)
|
|
{
|
|
return SEC_E_ILLEGAL_MESSAGE;
|
|
}
|
|
|
|
Size = cbCredential;
|
|
|
|
Size = ROUND_UP_COUNT( Size, ALIGN_DWORD );
|
|
|
|
RequestSize = DomainName->Length +
|
|
SslPackageName.Length ;
|
|
|
|
RequestSize = ROUND_UP_COUNT( RequestSize, ALIGN_DWORD );
|
|
|
|
#if DBG
|
|
CheckSize2 = RequestSize ;
|
|
#endif
|
|
|
|
RequestSize += sizeof( MSV1_0_PASSTHROUGH_REQUEST ) +
|
|
Size ;
|
|
|
|
|
|
Request = (PMSV1_0_PASSTHROUGH_REQUEST) LocalAlloc( LMEM_FIXED, RequestSize );
|
|
|
|
if ( !Request )
|
|
{
|
|
return SEC_E_INSUFFICIENT_MEMORY ;
|
|
}
|
|
|
|
Where = (PUCHAR) (Request + 1);
|
|
|
|
Request->MessageType = MsV1_0GenericPassthrough ;
|
|
Request->DomainName = *DomainName ;
|
|
Request->DomainName.Buffer = (LPWSTR) Where ;
|
|
|
|
RtlCopyMemory( Where,
|
|
DomainName->Buffer,
|
|
DomainName->Length );
|
|
|
|
Where += DomainName->Length ;
|
|
|
|
Request->PackageName = SslPackageName ;
|
|
Request->PackageName.Buffer = (LPWSTR) Where ;
|
|
RtlCopyMemory( Where,
|
|
SslPackageName.Buffer,
|
|
SslPackageName.Length );
|
|
|
|
Where += SslPackageName.Length ;
|
|
|
|
Where = ROUND_UP_POINTER( Where, ALIGN_DWORD );
|
|
|
|
#if DBG
|
|
DsysAssert( (((PUCHAR) Request) + CheckSize2 + sizeof( MSV1_0_PASSTHROUGH_REQUEST ) )
|
|
== (PUCHAR) Where );
|
|
#endif
|
|
|
|
Request->LogonData = Where ;
|
|
Request->DataLength = Size ;
|
|
|
|
RtlCopyMemory( Request->LogonData,
|
|
pCredential,
|
|
cbCredential );
|
|
|
|
|
|
//
|
|
// Now, call through to our DC:
|
|
//
|
|
|
|
|
|
Status = LsaTable->CallPackage(
|
|
&SslMsvName,
|
|
Request,
|
|
RequestSize,
|
|
&Response,
|
|
&ResponseSize,
|
|
&SubStatus );
|
|
|
|
LocalFree( Request );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertAtDC returned status 0x%x\n", Status ));
|
|
return Status ;
|
|
}
|
|
|
|
if ( !NT_SUCCESS( SubStatus ) )
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertAtDC returned sub-status 0x%x\n", SubStatus ));
|
|
return SubStatus ;
|
|
}
|
|
|
|
*DcResponse = Response ;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertAtDC returned 0x%x\n", STATUS_SUCCESS ));
|
|
|
|
return STATUS_SUCCESS ;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
SslMapExternalCredential(
|
|
IN PLSA_CLIENT_REQUEST ClientRequest,
|
|
IN PVOID ProtocolSubmitBuffer,
|
|
IN PVOID ClientBufferBase,
|
|
IN ULONG SubmitBufferLen,
|
|
OUT PVOID * ProtocolReturnBuffer,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PNTSTATUS ProtocolStatus
|
|
)
|
|
{
|
|
PSSL_EXTERNAL_CERT_LOGON_REQ Request;
|
|
PSSL_EXTERNAL_CERT_LOGON_RESP Response;
|
|
NT_PRODUCT_TYPE ProductType;
|
|
BOOL DC;
|
|
HMAPPER Mapper;
|
|
NTSTATUS Status;
|
|
HANDLE hUserToken = NULL;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapExternalCredential\n" ));
|
|
|
|
//
|
|
// Validate the input parameters.
|
|
//
|
|
|
|
if ( ARGUMENT_PRESENT( ProtocolReturnBuffer ) )
|
|
{
|
|
*ProtocolReturnBuffer = NULL ;
|
|
}
|
|
|
|
Request = (PSSL_EXTERNAL_CERT_LOGON_REQ) ProtocolSubmitBuffer ;
|
|
|
|
if(Request->Length != sizeof(SSL_EXTERNAL_CERT_LOGON_REQ))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
|
|
//
|
|
// Attempt to map the certificate.
|
|
//
|
|
|
|
if(RtlGetNtProductType(&ProductType))
|
|
{
|
|
DC = (ProductType == NtProductLanManNt);
|
|
}
|
|
else
|
|
{
|
|
return STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
memset(&Mapper, 0, sizeof(Mapper));
|
|
|
|
Mapper.m_dwFlags = SCH_FLAG_SYSTEM_MAPPER | Request->Flags;
|
|
|
|
if(DC)
|
|
{
|
|
Status = SslLocalMapCredential( &Mapper,
|
|
Request->CredentialType,
|
|
Request->Credential,
|
|
NULL,
|
|
(PHLOCATOR)&hUserToken);
|
|
}
|
|
else
|
|
{
|
|
Status = SslRemoteMapCredential(&Mapper,
|
|
Request->CredentialType,
|
|
Request->Credential,
|
|
NULL,
|
|
(PHLOCATOR)&hUserToken);
|
|
}
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
*ReturnBufferLength = 0;
|
|
*ProtocolStatus = Status;
|
|
Status = STATUS_SUCCESS;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Build the response.
|
|
//
|
|
|
|
Response = VirtualAlloc(
|
|
NULL,
|
|
sizeof(SSL_EXTERNAL_CERT_LOGON_RESP),
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE);
|
|
|
|
if ( Response )
|
|
{
|
|
Response->MessageType = SSL_LOOKUP_EXTERNAL_CERT_MESSAGE;
|
|
Response->Length = sizeof(SSL_EXTERNAL_CERT_LOGON_RESP);
|
|
Response->UserToken = hUserToken;
|
|
Response->Flags = 0;
|
|
|
|
*ProtocolReturnBuffer = Response;
|
|
*ReturnBufferLength = Response->Length;
|
|
*ProtocolStatus = STATUS_SUCCESS;
|
|
hUserToken = NULL;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
|
|
cleanup:
|
|
|
|
if(hUserToken)
|
|
{
|
|
CloseHandle(hUserToken);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslRemoteMapCredential(
|
|
IN PHMAPPER Mapper,
|
|
IN DWORD CredentialType,
|
|
VOID const *pCredential,
|
|
VOID const *pAuthority,
|
|
OUT HLOCATOR * phLocator
|
|
)
|
|
{
|
|
PCCERT_CONTEXT pCert = (PCERT_CONTEXT)pCredential;
|
|
PUCHAR Pac ;
|
|
ULONG PacLength ;
|
|
NTSTATUS Status ;
|
|
NTSTATUS VerifyStatus ;
|
|
HANDLE Token ;
|
|
LUID LogonId = { 0 };
|
|
PMSV1_0_PASSTHROUGH_RESPONSE Response ;
|
|
PSSL_CERT_LOGON_RESP CertResp ;
|
|
UNICODE_STRING AccountDomain ;
|
|
DWORD dwMethods;
|
|
PCCERT_CHAIN_CONTEXT pChainContext = NULL;
|
|
PSSL_CERT_LOGON_REQ pRequest = NULL;
|
|
DWORD cbRequest;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslRemoteMapCredential, context %x\n", Mapper ));
|
|
|
|
if ( CredentialType != X509_ASN_CHAIN )
|
|
{
|
|
return( SEC_E_UNKNOWN_CREDENTIALS );
|
|
}
|
|
|
|
|
|
//
|
|
// Validate client certificate, and obtain pointer to
|
|
// entire certificate chain.
|
|
//
|
|
|
|
Status = MapperVerifyClientChain(pCert,
|
|
Mapper->m_dwFlags,
|
|
&dwMethods,
|
|
&VerifyStatus,
|
|
&pChainContext);
|
|
if(FAILED(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Build the logon request.
|
|
//
|
|
|
|
Status = SslBuildCertLogonRequest(pChainContext,
|
|
dwMethods,
|
|
&pRequest,
|
|
&cbRequest);
|
|
|
|
CertFreeCertificateChain(pChainContext);
|
|
pChainContext = NULL;
|
|
|
|
if(FAILED(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Send the request to the DC.
|
|
//
|
|
|
|
Status = SslMapCertAtDC(
|
|
&SslDomainName,
|
|
pRequest,
|
|
cbRequest,
|
|
&Response );
|
|
|
|
LocalFree(pRequest);
|
|
pRequest = NULL;
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
LsaTable->AuditLogon(
|
|
Status,
|
|
VerifyStatus,
|
|
&SslNullString,
|
|
&SslNullString,
|
|
NULL,
|
|
NULL,
|
|
Network,
|
|
&SslTokenSource,
|
|
&LogonId );
|
|
|
|
LogCertMappingFailureEvent(Status);
|
|
|
|
if(!NT_SUCCESS(VerifyStatus))
|
|
{
|
|
// Return certificate validation error code, unless the mapper
|
|
// error has already been mapped to a proper sspi error code.
|
|
if(HRESULT_FACILITY(Status) != FACILITY_SECURITY)
|
|
{
|
|
Status = VerifyStatus;
|
|
}
|
|
}
|
|
|
|
return Status ;
|
|
}
|
|
|
|
//
|
|
// Ok, we got mapping data. Try to use it:
|
|
//
|
|
|
|
CertResp = (PSSL_CERT_LOGON_RESP) Response->ValidationData ;
|
|
|
|
//
|
|
// older servers (pre 2010 or so) won't return the full structure,
|
|
// so we need to examine it carefully.
|
|
|
|
if ( CertResp->Length - CertResp->AuthDataLength <= sizeof( SSL_CERT_LOGON_RESP ))
|
|
{
|
|
AccountDomain = SslDomainName ;
|
|
}
|
|
else
|
|
{
|
|
if ( CertResp->DomainLength < 65536 )
|
|
{
|
|
AccountDomain.Length = (USHORT) CertResp->DomainLength ;
|
|
AccountDomain.MaximumLength = AccountDomain.Length ;
|
|
AccountDomain.Buffer = (PWSTR) (((PUCHAR) CertResp) + CertResp->OffsetDomain );
|
|
}
|
|
else
|
|
{
|
|
AccountDomain = SslDomainName ;
|
|
}
|
|
}
|
|
|
|
Status = SslCreateTokenFromPac( (((PUCHAR) CertResp) + CertResp->OffsetAuthData),
|
|
CertResp->AuthDataLength,
|
|
&AccountDomain,
|
|
&LogonId,
|
|
&Token );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
*phLocator = (HLOCATOR) Token ;
|
|
}
|
|
else
|
|
{
|
|
LogCertMappingFailureEvent(Status);
|
|
}
|
|
|
|
LsaTable->FreeReturnBuffer( Response );
|
|
|
|
|
|
return ( Status );
|
|
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalCloseLocator(
|
|
IN PHMAPPER Mapper,
|
|
IN HLOCATOR Locator
|
|
)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "CloseLocator, context %x\n", Mapper ));
|
|
|
|
NtClose( (HANDLE) Locator );
|
|
|
|
return( SEC_E_OK );
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalGetAccessToken(
|
|
IN PHMAPPER Mapper,
|
|
IN HLOCATOR Locator,
|
|
OUT HANDLE *Token
|
|
)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "GetAccessToken, context %x\n", Mapper ));
|
|
|
|
*Token = (HANDLE) Locator ;
|
|
|
|
return( SEC_E_OK );
|
|
}
|
|
|
|
|
|
BOOL
|
|
SslRelocateToken(
|
|
IN HLOCATOR Locator,
|
|
OUT HLOCATOR * NewLocator)
|
|
{
|
|
NTSTATUS Status ;
|
|
|
|
Status = LsaTable->DuplicateHandle( (HANDLE) Locator,
|
|
(PHANDLE) NewLocator );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
return( TRUE );
|
|
}
|
|
|
|
return( FALSE );
|
|
|
|
}
|
|
|
|
#if 0
|
|
DWORD
|
|
TestExternalMapper(
|
|
PCCERT_CONTEXT pCertContext)
|
|
{
|
|
NTSTATUS Status;
|
|
NTSTATUS AuthPackageStatus;
|
|
SSL_EXTERNAL_CERT_LOGON_REQ Request;
|
|
PSSL_EXTERNAL_CERT_LOGON_RESP pResponse;
|
|
ULONG ResponseLength;
|
|
UNICODE_STRING PackageName;
|
|
|
|
//
|
|
// Build request.
|
|
//
|
|
|
|
memset(&Request, 0, sizeof(SSL_EXTERNAL_CERT_LOGON_REQ));
|
|
|
|
Request.MessageType = SSL_LOOKUP_EXTERNAL_CERT_MESSAGE;
|
|
Request.Length = sizeof(SSL_EXTERNAL_CERT_LOGON_REQ);
|
|
Request.CredentialType = X509_ASN_CHAIN;
|
|
Request.Credential = (PVOID)pCertContext;
|
|
|
|
|
|
//
|
|
// Call security package (must make call as local system).
|
|
//
|
|
|
|
RtlInitUnicodeString(&PackageName, L"Schannel");
|
|
|
|
Status = LsaICallPackage(
|
|
&PackageName,
|
|
&Request,
|
|
Request.Length,
|
|
(PVOID *)&pResponse,
|
|
&ResponseLength,
|
|
&AuthPackageStatus
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if(NT_SUCCESS(AuthPackageStatus))
|
|
{
|
|
//
|
|
// Mapping was successful.
|
|
//
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
LsaIFreeReturnBuffer( pResponse );
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
#endif
|