You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3482 lines
92 KiB
3482 lines
92 KiB
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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 "wincrypt.h"
|
|
#include <msaudite.h>
|
|
#include <mapper.h>
|
|
#include <kerberos.h>
|
|
#include <sidfilter.h>
|
|
#include <lsaitf.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
|
|
};
|
|
|
|
|
|
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(
|
|
IN PUNICODE_STRING DomainName,
|
|
IN VOID const *pCredential,
|
|
IN DWORD cbCredential,
|
|
IN BOOL IsDC,
|
|
OUT PUCHAR *UserPac,
|
|
OUT PULONG UserPacLen,
|
|
OUT PMSV1_0_PASSTHROUGH_RESPONSE * DcResponse);
|
|
|
|
SECURITY_STRING SslNullString = { 0, sizeof( WCHAR ), L"" };
|
|
|
|
HANDLE SslLogonHandle = NULL;
|
|
ULONG SslKerberosPackageId = 0;
|
|
ULONG SslMsvPackageId = 0;
|
|
TOKEN_GROUPS *SslPackageSid = 0;
|
|
|
|
NTSTATUS
|
|
SslInitSystemMapper(void)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG Dummy;
|
|
LSA_STRING Name;
|
|
|
|
//
|
|
// Get handle to Kerberos package.
|
|
//
|
|
|
|
Status = LsaRegisterLogonProcess(
|
|
&SslPackageNameA,
|
|
&SslLogonHandle,
|
|
&Dummy
|
|
);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return SP_LOG_RESULT(Status);
|
|
}
|
|
|
|
RtlInitString(&Name,
|
|
MICROSOFT_KERBEROS_NAME_A );
|
|
|
|
Status = LsaLookupAuthenticationPackage(
|
|
SslLogonHandle,
|
|
&Name,
|
|
&SslKerberosPackageId
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return SP_LOG_RESULT(Status);
|
|
}
|
|
|
|
|
|
//
|
|
// Get handle to NTLM package.
|
|
//
|
|
|
|
RtlInitString(&Name,
|
|
MSV1_0_PACKAGE_NAME );
|
|
|
|
Status = LsaLookupAuthenticationPackage(
|
|
SslLogonHandle,
|
|
&Name,
|
|
&SslMsvPackageId
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return SP_LOG_RESULT(Status);
|
|
}
|
|
|
|
|
|
//
|
|
// Build schannel package SID.
|
|
//
|
|
|
|
{
|
|
SID_IDENTIFIER_AUTHORITY PackageSidAuthority = SECURITY_NT_AUTHORITY;
|
|
BYTE PackageSidBuffer[ SECURITY_MAX_SID_SIZE ];
|
|
PSID PackageSid = (PSID)PackageSidBuffer;
|
|
ULONG Length;
|
|
|
|
RtlInitializeSid(PackageSid, &PackageSidAuthority, 2);
|
|
*(RtlSubAuthoritySid(PackageSid, 0)) = SECURITY_PACKAGE_BASE_RID;
|
|
*(RtlSubAuthoritySid(PackageSid, 1)) = UNISP_RPC_ID;
|
|
|
|
Length = RtlLengthSid(PackageSid);
|
|
|
|
SslPackageSid = LocalAlloc(LPTR, sizeof(TOKEN_GROUPS) + Length);
|
|
if(SslPackageSid == NULL)
|
|
{
|
|
return SP_LOG_RESULT(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
SslPackageSid->GroupCount = 1;
|
|
SslPackageSid->Groups[0].Sid = (PSID)&SslPackageSid->Groups[1];
|
|
SslPackageSid->Groups[0].Attributes = SE_GROUP_MANDATORY |
|
|
SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED;
|
|
|
|
RtlCopySid(Length, SslPackageSid->Groups[0].Sid, PackageSid);
|
|
}
|
|
|
|
g_SslS4U2SelfInitialized = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
PHMAPPER
|
|
SslGetMapper(
|
|
BOOL Ignored
|
|
)
|
|
{
|
|
PSSL_MAPPER_CONTEXT Context ;
|
|
NT_PRODUCT_TYPE ProductType ;
|
|
BOOL DC ;
|
|
|
|
UNREFERENCED_PARAMETER(Ignored);
|
|
|
|
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
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Mapper);
|
|
UNREFERENCED_PARAMETER(Reserved);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslLocalGetIssuerList\n" ));
|
|
|
|
return( (DWORD)GetDefaultIssuers(pIssuerList, IssuerListLength) );
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalGetChallenge(
|
|
IN PHMAPPER Mapper,
|
|
IN PUCHAR AuthenticatorId,
|
|
IN DWORD AuthenticatorIdLength,
|
|
OUT PUCHAR Challenge,
|
|
OUT DWORD * ChallengeLength
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Mapper);
|
|
UNREFERENCED_PARAMETER(AuthenticatorId);
|
|
UNREFERENCED_PARAMETER(AuthenticatorIdLength);
|
|
UNREFERENCED_PARAMETER(Challenge);
|
|
UNREFERENCED_PARAMETER(ChallengeLength);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslLocalGetChallenge\n" ));
|
|
|
|
return (DWORD)SEC_E_UNSUPPORTED_FUNCTION;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetTokenUserSid
|
|
//
|
|
// Synopsis: Obtain the user SID from the specified user token
|
|
//
|
|
// Arguments: [hUserToken] -- User token.
|
|
// [ppUserSid] -- Returned SID.
|
|
//
|
|
// History: 10-08-2001 jbanes Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOL
|
|
GetTokenUserSid(
|
|
IN HANDLE hUserToken, // token to query
|
|
IN OUT PSID *ppUserSid // resultant user sid
|
|
)
|
|
{
|
|
BYTE FastBuffer[256];
|
|
LPBYTE SlowBuffer = NULL;
|
|
PTOKEN_USER ptgUser;
|
|
DWORD cbBuffer;
|
|
BOOL fSuccess = FALSE;
|
|
|
|
*ppUserSid = NULL;
|
|
|
|
if(hUserToken == NULL)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// try querying based on a fast stack based buffer first.
|
|
//
|
|
|
|
ptgUser = (PTOKEN_USER)FastBuffer;
|
|
cbBuffer = sizeof(FastBuffer);
|
|
|
|
fSuccess = GetTokenInformation(
|
|
hUserToken,// identifies access token
|
|
TokenUser, // TokenUser info type
|
|
ptgUser, // retrieved info buffer
|
|
cbBuffer, // size of buffer passed-in
|
|
&cbBuffer // required buffer size
|
|
);
|
|
|
|
if(!fSuccess)
|
|
{
|
|
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
|
|
{
|
|
// try again with the specified buffer size
|
|
SlowBuffer = (LPBYTE)SPExternalAlloc(cbBuffer);
|
|
|
|
if(SlowBuffer != NULL)
|
|
{
|
|
ptgUser = (PTOKEN_USER)SlowBuffer;
|
|
|
|
fSuccess = GetTokenInformation(
|
|
hUserToken,// identifies access token
|
|
TokenUser, // TokenUser info type
|
|
ptgUser, // retrieved info buffer
|
|
cbBuffer, // size of buffer passed-in
|
|
&cbBuffer // required buffer size
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if we got the token info successfully, copy the
|
|
// relevant element for the caller.
|
|
//
|
|
|
|
if(fSuccess)
|
|
{
|
|
DWORD cbSid;
|
|
|
|
// reset to assume failure
|
|
fSuccess = FALSE;
|
|
|
|
cbSid = GetLengthSid(ptgUser->User.Sid);
|
|
|
|
*ppUserSid = SPExternalAlloc( cbSid );
|
|
|
|
if(*ppUserSid != NULL)
|
|
{
|
|
fSuccess = CopySid(cbSid, *ppUserSid, ptgUser->User.Sid);
|
|
}
|
|
else
|
|
{
|
|
fSuccess = FALSE;
|
|
}
|
|
}
|
|
|
|
if(!fSuccess)
|
|
{
|
|
if(*ppUserSid)
|
|
{
|
|
SPExternalFree(*ppUserSid);
|
|
*ppUserSid = NULL;
|
|
}
|
|
}
|
|
|
|
if(SlowBuffer)
|
|
{
|
|
SPExternalFree(SlowBuffer);
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
|
|
SECURITY_STATUS
|
|
SslCreateTokenFromPac(
|
|
PUCHAR AuthInfo,
|
|
ULONG AuthInfoLength,
|
|
PUNICODE_STRING Domain,
|
|
PLUID NewLogonId,
|
|
PHANDLE NewToken
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
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 ) )
|
|
{
|
|
PSID pSid;
|
|
|
|
if(!GetTokenUserSid(TokenHandle, &pSid))
|
|
{
|
|
pSid = NULL;
|
|
}
|
|
|
|
LsaTable->AuditLogon(
|
|
STATUS_SUCCESS,
|
|
STATUS_SUCCESS,
|
|
&PacUserName,
|
|
Domain,
|
|
NULL,
|
|
pSid,
|
|
Network,
|
|
&SslTokenSource,
|
|
&LogonId );
|
|
|
|
if(pSid)
|
|
{
|
|
SPExternalFree(pSid);
|
|
}
|
|
}
|
|
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))
|
|
{
|
|
// We found a DNS!
|
|
cbName = (lstrlen(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))
|
|
{
|
|
LocalFree(pszName);
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if(pszName)
|
|
{
|
|
*ppszName = pszName;
|
|
}
|
|
else
|
|
{
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslTryS4U2Self
|
|
//
|
|
// Synopsis: Creates a user token via the Kerberos S4U2Self mechanism.
|
|
// This should work even cross-forest, provided that all of the
|
|
// DC's are running Whistler. Pretty cool!
|
|
//
|
|
// Arguments: [pChainContext] --
|
|
// [UserToken] --
|
|
//
|
|
// History: 06-13-2002 jbanes Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslTryS4U2Self(
|
|
IN PCCERT_CHAIN_CONTEXT pChainContext,
|
|
OUT HANDLE *UserToken
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
NTSTATUS SubStatus;
|
|
|
|
BOOL fMachineCert;
|
|
PWSTR pszUserName = NULL;
|
|
PCERT_SIMPLE_CHAIN pSimpleChain;
|
|
PCCERT_CONTEXT pCert;
|
|
|
|
PKERB_S4U_LOGON LogonInfo = NULL;
|
|
ULONG LogonInfoSize = sizeof(KERB_S4U_LOGON);
|
|
PKERB_INTERACTIVE_PROFILE Profile = NULL;
|
|
ULONG ProfileSize;
|
|
LUID LogonId;
|
|
QUOTA_LIMITS Quotas;
|
|
|
|
|
|
*UserToken = NULL;
|
|
|
|
if(!g_SslS4U2SelfInitialized)
|
|
{
|
|
return SP_LOG_RESULT(STATUS_NOT_SUPPORTED);
|
|
}
|
|
|
|
|
|
//
|
|
// Get the client name from the cert
|
|
//
|
|
|
|
pSimpleChain = pChainContext->rgpChain[0];
|
|
|
|
pCert = pSimpleChain->rgpElement[0]->pCertContext;
|
|
|
|
Status = SslGetNameFromCertificate(pCert, &pszUserName, &fMachineCert);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
if(fMachineCert)
|
|
{
|
|
// S4U2Self doesn't work with machine accounts.
|
|
Status = STATUS_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Looking for UPN name %ws\n", pszUserName ));
|
|
|
|
|
|
//
|
|
// Build logon info structure.
|
|
//
|
|
|
|
LogonInfoSize = sizeof(KERB_S4U_LOGON) +
|
|
(lstrlen(pszUserName) + 1) * sizeof(WCHAR);
|
|
|
|
LogonInfo = (PKERB_S4U_LOGON) LocalAlloc(LPTR, LogonInfoSize);
|
|
if (NULL == LogonInfo)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
LogonInfo->MessageType = KerbS4ULogon;
|
|
|
|
LogonInfo->ClientUpn.Buffer = (LPWSTR)(LogonInfo + 1);
|
|
LogonInfo->ClientUpn.Length = (USHORT)(lstrlen(pszUserName)) * sizeof(WCHAR);
|
|
LogonInfo->ClientUpn.MaximumLength = LogonInfo->ClientUpn.Length + sizeof(WCHAR);
|
|
|
|
memcpy((PUCHAR)(LogonInfo + 1),
|
|
pszUserName,
|
|
LogonInfo->ClientUpn.MaximumLength);
|
|
|
|
|
|
//
|
|
// Attempt to log the user on.
|
|
//
|
|
|
|
Status = LsaLogonUser(
|
|
SslLogonHandle,
|
|
&SslPackageNameA,
|
|
Network,
|
|
SslKerberosPackageId,
|
|
LogonInfo,
|
|
LogonInfoSize,
|
|
SslPackageSid,
|
|
&SslTokenSource,
|
|
(PVOID *)&Profile,
|
|
&ProfileSize,
|
|
&LogonId,
|
|
UserToken,
|
|
&Quotas,
|
|
&SubStatus
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = SubStatus;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
Status = LsaISetTokenDacl(*UserToken);
|
|
|
|
cleanup:
|
|
|
|
if(Profile)
|
|
{
|
|
LsaFreeReturnBuffer(Profile);
|
|
}
|
|
|
|
if(LogonInfo)
|
|
{
|
|
LocalFree(LogonInfo);
|
|
}
|
|
|
|
if(pszUserName)
|
|
{
|
|
LocalFree(pszUserName);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// 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;
|
|
PWSTR pszServiceName = NULL;
|
|
DWORD cchServiceName;
|
|
|
|
|
|
*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)
|
|
{
|
|
// Search for "host/foo.com".
|
|
cchServiceName = lstrlenW(L"host/") + lstrlenW(pszName);
|
|
|
|
SafeAllocaAllocate(pszServiceName, (cchServiceName + 1) * sizeof(WCHAR));
|
|
|
|
if(pszServiceName == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
lstrcpyW(pszServiceName, L"host/");
|
|
lstrcatW(pszServiceName, pszName);
|
|
|
|
RtlInitUnicodeString(&Upn, pszServiceName);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Looking for SPN name %ws\n", Upn.Buffer ));
|
|
|
|
Status = LsaTable->GetAuthDataForUser( &Upn,
|
|
SecNameSPN,
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Search for "[email protected]".
|
|
RtlInitUnicodeString(&Upn, pszName);
|
|
|
|
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 )
|
|
{
|
|
UNICODE_STRING DomainName;
|
|
BOOL NameMatch;
|
|
|
|
//
|
|
// 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)
|
|
{
|
|
RtlInitUnicodeString( &DomainName, pPeriod + 1 );
|
|
|
|
SslGlobalReadLock();
|
|
NameMatch = RtlEqualUnicodeString(&DomainName, &SslGlobalDnsDomainName, TRUE);
|
|
SslGlobalReleaseLock();
|
|
|
|
if(NameMatch)
|
|
{
|
|
ch = *(pPeriod + 1);
|
|
|
|
*pPeriod = L'$';
|
|
*(pPeriod + 1) = L'\0';
|
|
|
|
RtlInitUnicodeString(&Upn, pszName);
|
|
|
|
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 )
|
|
{
|
|
RtlInitUnicodeString( &DomainName, AtSign + 1 );
|
|
|
|
SslGlobalReadLock();
|
|
NameMatch = RtlEqualUnicodeString(&DomainName, &SslGlobalDnsDomainName, TRUE);
|
|
SslGlobalReleaseLock();
|
|
|
|
if(NameMatch)
|
|
{
|
|
*AtSign = L'\0';
|
|
|
|
RtlInitUnicodeString(&Upn, pszName);
|
|
|
|
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 )
|
|
{
|
|
if(fMachineCert)
|
|
{
|
|
RtlInitUnicodeString(&Upn, pszServiceName);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Cracking name %ws at GC\n", Upn.Buffer ));
|
|
|
|
Status = LsaTable->CrackSingleName(
|
|
DS_SERVICE_PRINCIPAL_NAME,
|
|
TRUE,
|
|
&Upn,
|
|
NULL,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
&Cracked,
|
|
&DnsDomain,
|
|
&SubStatus );
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString(&Upn, pszName);
|
|
|
|
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);
|
|
}
|
|
|
|
if(pszServiceName)
|
|
{
|
|
SafeAllocaFree(pszServiceName);
|
|
}
|
|
|
|
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 = (USHORT)(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 ;
|
|
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 ) ;
|
|
|
|
SafeAllocaAllocate( CompoundName.Buffer, 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 ;
|
|
}
|
|
|
|
}
|
|
|
|
SafeAllocaFree( 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;
|
|
BOOL fReferral = FALSE;
|
|
|
|
*ReferencedDomain = NULL ;
|
|
|
|
//
|
|
// See if issuer is in cache. If so, then we know that this issuer
|
|
// doesn't map to a user account.
|
|
//
|
|
|
|
if(SPFindIssuerInCache(pIssuer, cbIssuer))
|
|
{
|
|
return STATUS_NOT_FOUND;
|
|
}
|
|
|
|
LogDistinguishedName(DEB_TRACE, "SslTryIssuer: %s\n", pIssuer, cbIssuer);
|
|
|
|
|
|
//
|
|
// Attempt to map the issuer.
|
|
//
|
|
|
|
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 ) ;
|
|
|
|
SafeAllocaAllocate( IssuerName.Buffer, 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;
|
|
fReferral = TRUE;
|
|
}
|
|
|
|
if(Cracked.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( Cracked.Buffer );
|
|
}
|
|
if(DnsDomain.Buffer != NULL)
|
|
{
|
|
LsaTable->FreeLsaHeap( DnsDomain.Buffer );
|
|
}
|
|
|
|
Status = STATUS_NOT_FOUND ;
|
|
}
|
|
|
|
if(!fReferral)
|
|
{
|
|
// No mapping was found for this issuer, and no referral
|
|
// either. Add this issuer to the issuer cache, so that
|
|
// we don't attempt to map it again (until the cache entry
|
|
// expires).
|
|
SPAddIssuerToCache(pIssuer, cbIssuer);
|
|
}
|
|
}
|
|
|
|
SafeAllocaFree(IssuerName.Buffer);
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY ;
|
|
}
|
|
|
|
return Status ;
|
|
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: SslMapCertToUserPac
|
|
//
|
|
// Synopsis: Maps a certificate to a user (hopefully) and the PAC,
|
|
//
|
|
// Arguments: [Request] --
|
|
// [RequestLength] --
|
|
// [UserPac] --
|
|
// [UserPacLen] --
|
|
//
|
|
// History: 5-11-98 RichardW Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslMapCertToUserPac(
|
|
IN PSSL_CERT_LOGON_REQ Request,
|
|
IN ULONG RequestLength,
|
|
OUT PUCHAR * UserPac,
|
|
OUT PULONG UserPacLen,
|
|
OUT PWSTR * ReferencedDomain
|
|
)
|
|
{
|
|
PCCERT_CONTEXT User ;
|
|
NTSTATUS Status = STATUS_LOGON_FAILURE;
|
|
NTSTATUS SubStatus = STATUS_NOT_FOUND;
|
|
ULONG i;
|
|
|
|
*ReferencedDomain = NULL;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertToUserPac called\n" ));
|
|
|
|
|
|
//
|
|
// Validate logon request.
|
|
//
|
|
|
|
if(RequestLength < sizeof(SSL_CERT_LOGON_REQ))
|
|
{
|
|
return SP_LOG_RESULT(STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
if(Request->Length > RequestLength)
|
|
{
|
|
return SP_LOG_RESULT(STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
|
|
//
|
|
// Extract certificate from request.
|
|
//
|
|
|
|
if((Request->OffsetCertificate > RequestLength) ||
|
|
(Request->CertLength > 0x10000) ||
|
|
(Request->OffsetCertificate + Request->CertLength > RequestLength))
|
|
{
|
|
return SP_LOG_RESULT(STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
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) &&
|
|
(g_dwCertMappingMethods & SP_REG_CERTMAP_UPN_FLAG))
|
|
{
|
|
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) &&
|
|
(g_dwCertMappingMethods & SP_REG_CERTMAP_SUBJECT_FLAG))
|
|
{
|
|
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) &&
|
|
(g_dwCertMappingMethods & SP_REG_CERTMAP_ISSUER_FLAG))
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Trying issuer mapping\n" ));
|
|
|
|
if((Request->Flags & REQ_ISSUER_CHAIN_MAPPING) && (Request->CertCount > 0))
|
|
{
|
|
if(sizeof(SSL_CERT_LOGON_REQ) +
|
|
(Request->CertCount - 1) * sizeof(SSL_CERT_NAME_INFO) > RequestLength)
|
|
{
|
|
Status = SP_LOG_RESULT(STATUS_INVALID_PARAMETER);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Loop through each issuer in the certificate chain.
|
|
for(i = 0; i < Request->CertCount; i++)
|
|
{
|
|
DWORD IssuerOffset = Request->NameInfo[i].IssuerOffset;
|
|
DWORD IssuerLength = Request->NameInfo[i].IssuerLength;
|
|
|
|
if((IssuerOffset > RequestLength) ||
|
|
(IssuerLength > 0x10000) ||
|
|
(IssuerOffset + IssuerLength > RequestLength))
|
|
{
|
|
return SP_LOG_RESULT(STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
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;
|
|
|
|
if(dwMapperFlags & SCH_FLAG_NO_VALIDATION)
|
|
{
|
|
DebugLog((DEB_TRACE, "Skipping certificate validation.\n"));
|
|
|
|
if(ppChainContext != NULL)
|
|
{
|
|
CERT_CHAIN_PARA ChainPara;
|
|
|
|
ZeroMemory(&ChainPara, sizeof(ChainPara));
|
|
ChainPara.cbSize = sizeof(ChainPara);
|
|
|
|
if(!CertGetCertificateChain(
|
|
NULL, // hChainEngine
|
|
pCertContext, // pCertContext
|
|
NULL, // pTime
|
|
pCertContext->hCertStore, // hAdditionalStore
|
|
&ChainPara, // pChainPara
|
|
dwCertFlags, // dwFlags
|
|
NULL, // pvReserved
|
|
ppChainContext)) // ppChainContext
|
|
{
|
|
Status = SP_LOG_RESULT(GetLastError());
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 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(Status != STATUS_SUCCESS)
|
|
{
|
|
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;
|
|
|
|
|
|
if(dwMapperFlags & SCH_FLAG_NO_VALIDATION)
|
|
{
|
|
// Turn on UPN mapping.
|
|
*pdwMethods |= REQ_UPN_MAPPING;
|
|
}
|
|
else
|
|
{
|
|
// Check to see if the certificate chain is valid for UPN mapping.
|
|
Status = VerifyClientCertificate(pCertContext,
|
|
dwCertFlags,
|
|
dwIgnoreErrors,
|
|
CERT_CHAIN_POLICY_NT_AUTH,
|
|
NULL);
|
|
if(Status == STATUS_SUCCESS)
|
|
{
|
|
// 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 DirectPac = NULL ;
|
|
PUCHAR IndirectPac = NULL ;
|
|
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;
|
|
|
|
UNREFERENCED_PARAMETER(pAuthority);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslLocalMapCredential, context %x\n", Mapper ));
|
|
|
|
if ( CredentialType != X509_ASN_CHAIN )
|
|
{
|
|
return( (DWORD)SEC_E_UNKNOWN_CREDENTIALS );
|
|
}
|
|
|
|
//
|
|
// Validate client certificate, and obtain pointer to
|
|
// entire certificate chain.
|
|
//
|
|
|
|
Status = MapperVerifyClientChain(pCert,
|
|
Mapper->m_dwFlags,
|
|
&dwMethods,
|
|
&VerifyStatus,
|
|
&pChainContext);
|
|
if(Status != STATUS_SUCCESS)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Attempt to logon via Kerberos S4U2Self.
|
|
//
|
|
|
|
if((dwMethods & REQ_UPN_MAPPING) &&
|
|
(g_dwCertMappingMethods & SP_REG_CERTMAP_S4U2SELF_FLAG))
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Trying S4U2Self mapping\n" ));
|
|
|
|
Status = SslTryS4U2Self(pChainContext, &Token);
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
CertFreeCertificateChain(pChainContext);
|
|
pChainContext = NULL;
|
|
|
|
*phLocator = (HLOCATOR) Token ;
|
|
|
|
return Status;
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Failed with error 0x%x\n", Status ));
|
|
}
|
|
|
|
|
|
//
|
|
// Build the logon request.
|
|
//
|
|
|
|
Status = SslBuildCertLogonRequest(pChainContext,
|
|
dwMethods,
|
|
&pRequest,
|
|
&cbRequest);
|
|
|
|
CertFreeCertificateChain(pChainContext);
|
|
pChainContext = NULL;
|
|
|
|
if(FAILED(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Attempt to find the user locally.
|
|
//
|
|
|
|
Status = SslMapCertToUserPac(
|
|
pRequest,
|
|
cbRequest,
|
|
&Pac,
|
|
&PacLength,
|
|
&ReferencedDomain );
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Free this PAC later using LsaTable->FreeLsaHeap.
|
|
DirectPac = Pac;
|
|
}
|
|
|
|
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,
|
|
TRUE,
|
|
&Pac,
|
|
&PacLength,
|
|
&Response );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
// Free this later using MIDL_user_free.
|
|
IndirectPac = Pac;
|
|
|
|
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 ;
|
|
}
|
|
}
|
|
}
|
|
|
|
LsaTable->FreeLsaHeap( ReferencedDomain );
|
|
|
|
}
|
|
else
|
|
{
|
|
AccountDomain = SslDomainName ;
|
|
}
|
|
|
|
|
|
//
|
|
// Expand the domain local groups.
|
|
//
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Status = LsaTable->ExpandAuthDataForDomain(
|
|
Pac,
|
|
PacLength,
|
|
NULL,
|
|
&ExpandedPac,
|
|
&ExpandedPacLength );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Pac = ExpandedPac ;
|
|
PacLength = ExpandedPacLength ;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Create the user token.
|
|
//
|
|
|
|
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);
|
|
}
|
|
|
|
if(DirectPac)
|
|
{
|
|
LsaTable->FreeLsaHeap(DirectPac);
|
|
}
|
|
|
|
if(IndirectPac)
|
|
{
|
|
MIDL_user_free(IndirectPac);
|
|
}
|
|
|
|
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 ;
|
|
ULONG PacLength ;
|
|
PUCHAR DirectPac = NULL ;
|
|
PUCHAR IndirectPac = NULL ;
|
|
PUCHAR ExpandedPac = NULL ;
|
|
ULONG ExpandedPacLength ;
|
|
PWSTR ReferencedDomain = NULL;
|
|
PWSTR FirstDot ;
|
|
UNICODE_STRING DomainName = { 0 };
|
|
PMSV1_0_PASSTHROUGH_RESPONSE MsvResponse = NULL ;
|
|
SECPKG_CALL_INFO CallInfo;
|
|
|
|
UNREFERENCED_PARAMETER(ClientRequest);
|
|
UNREFERENCED_PARAMETER(ClientBufferBase);
|
|
|
|
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,
|
|
SubmitBufferLen,
|
|
&Pac,
|
|
&PacLength,
|
|
&ReferencedDomain );
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Local lookup returns %x\n", Status ));
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Free this PAC later using LsaTable->FreeLsaHeap.
|
|
DirectPac = Pac;
|
|
}
|
|
|
|
if(!NT_SUCCESS(Status) &&
|
|
(ReferencedDomain != NULL))
|
|
{
|
|
BOOL NameMatch;
|
|
|
|
//
|
|
// Didn't find it at this DC, but another domain appears to
|
|
// have the mapping. Forward it there:
|
|
//
|
|
|
|
RtlInitUnicodeString( &DomainName, ReferencedDomain );
|
|
|
|
SslGlobalReadLock();
|
|
|
|
DsysAssert(!RtlEqualUnicodeString(&DomainName, &SslGlobalDnsDomainName, TRUE));
|
|
DsysAssert(!RtlEqualUnicodeString(&DomainName, &SslDomainName, TRUE));
|
|
|
|
NameMatch = (RtlEqualUnicodeString(&DomainName, &SslGlobalDnsDomainName, TRUE) ||
|
|
RtlEqualUnicodeString(&DomainName, &SslDomainName, TRUE));
|
|
|
|
SslGlobalReleaseLock();
|
|
|
|
if(NameMatch)
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "GC is out of sync, bailing on this user\n" ));
|
|
Status = STATUS_LOGON_FAILURE ;
|
|
|
|
}
|
|
else if(LsaTable->GetCallInfo(&CallInfo) &&
|
|
(CallInfo.Attributes & SECPKG_CALL_RECURSIVE))
|
|
{
|
|
DebugLog(( DEB_ERROR, "Certificate mapper is recursing!\n" ));
|
|
|
|
}
|
|
else
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Mapping certificate at DC for domain %ws\n",
|
|
ReferencedDomain ));
|
|
|
|
Status = SslMapCertAtDC(
|
|
&DomainName,
|
|
Request,
|
|
Request->Length,
|
|
TRUE,
|
|
&Pac,
|
|
&PacLength,
|
|
&MsvResponse );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
// Free this later using MIDL_user_free.
|
|
IndirectPac = Pac;
|
|
|
|
IndirectResponse = (PSSL_CERT_LOGON_RESP) MsvResponse->ValidationData ;
|
|
|
|
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 ) )
|
|
{
|
|
Pac = ExpandedPac ;
|
|
PacLength = ExpandedPacLength ;
|
|
}
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
*ReturnBufferLength = 0;
|
|
*ProtocolStatus = Status ;
|
|
|
|
Status = STATUS_SUCCESS ;
|
|
|
|
goto Cleanup ;
|
|
|
|
}
|
|
|
|
|
|
#ifdef ROGUE_DC
|
|
if(DirectPac)
|
|
{
|
|
// We're a rogue user DC, so let's add some bogus SIDs to the PAC.
|
|
// Yo ho ho.
|
|
DebugLog((DEB_TRACE, "SslDoClientRequest: Calling SslInstrumentRoguePac\n"));
|
|
|
|
Status = SslInstrumentRoguePac(&Pac, &PacLength);
|
|
ExpandedPac = Pac;
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "SslDoClientRequest: Failed SslInstrumentRoguePac 0x%x\n", Status));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
//
|
|
// 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)
|
|
{
|
|
LsaTable->FreeReturnBuffer( MsvResponse );
|
|
}
|
|
|
|
if(DirectPac)
|
|
{
|
|
LsaTable->FreeLsaHeap(DirectPac);
|
|
}
|
|
|
|
if(IndirectPac)
|
|
{
|
|
MIDL_user_free(IndirectPac);
|
|
}
|
|
|
|
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;
|
|
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(
|
|
IN PUNICODE_STRING DomainName,
|
|
IN VOID const *pCredential,
|
|
IN DWORD cbCredential,
|
|
IN BOOL IsDC,
|
|
OUT PUCHAR *UserPac,
|
|
OUT PULONG UserPacLen,
|
|
OUT PMSV1_0_PASSTHROUGH_RESPONSE * DcResponse)
|
|
{
|
|
NTSTATUS Status ;
|
|
PMSV1_0_PASSTHROUGH_REQUEST Request ;
|
|
PMSV1_0_PASSTHROUGH_RESPONSE Response = NULL;
|
|
PSSL_CERT_LOGON_RESP CertResp ;
|
|
DWORD Size ;
|
|
DWORD RequestSize ;
|
|
DWORD ResponseSize ;
|
|
PUCHAR Where ;
|
|
NTSTATUS SubStatus ;
|
|
#if DBG
|
|
DWORD CheckSize2 ;
|
|
#endif
|
|
PSID pTrustSid = NULL;
|
|
PUCHAR Pac = NULL;
|
|
ULONG PacLength;
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Remote call to DC to do the mapping\n" ));
|
|
|
|
|
|
//
|
|
// Validate the input parameters.
|
|
//
|
|
|
|
if(cbCredential > 0x4000)
|
|
{
|
|
return SEC_E_ILLEGAL_MESSAGE;
|
|
}
|
|
|
|
|
|
//
|
|
// Verify that the target DC is in the same forest. Cross-forest
|
|
// certificate mapping is not supported, except via the S4U2Self
|
|
// mechanism.
|
|
|
|
if(IsDC)
|
|
{
|
|
BOOL fWithinForest = TRUE;
|
|
|
|
Status = LsaIIsDomainWithinForest(DomainName,
|
|
&fWithinForest,
|
|
NULL,
|
|
&pTrustSid,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
if (!NT_SUCCESS (Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "SslMapCertAtDC: LsaIIsDomainWithinForest failed 0x%x\n", Status));
|
|
goto cleanup;
|
|
}
|
|
if (!fWithinForest)
|
|
{
|
|
Status = SEC_E_NO_AUTHENTICATING_AUTHORITY;
|
|
DebugLog((DEB_ERROR, "SslMapCertAtDC: Target DC is outside forest - fail request 0x%x\n", Status));
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Build the request to send to the DC.
|
|
//
|
|
|
|
Size = cbCredential;
|
|
|
|
Size = ROUND_UP_COUNT( Size, ALIGN_DWORD );
|
|
|
|
RequestSize = DomainName->Length +
|
|
SslLegacyPackageName.Length ;
|
|
|
|
RequestSize = ROUND_UP_COUNT( RequestSize, ALIGN_DWORD );
|
|
|
|
#if DBG
|
|
CheckSize2 = RequestSize ;
|
|
#endif
|
|
|
|
RequestSize += sizeof( MSV1_0_PASSTHROUGH_REQUEST ) +
|
|
Size ;
|
|
|
|
|
|
SafeAllocaAllocate( (PMSV1_0_PASSTHROUGH_REQUEST)Request, RequestSize );
|
|
|
|
if ( !Request )
|
|
{
|
|
Status = SEC_E_INSUFFICIENT_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
|
|
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 = SslLegacyPackageName ;
|
|
Request->PackageName.Buffer = (LPWSTR) Where ;
|
|
RtlCopyMemory( Where,
|
|
SslLegacyPackageName.Buffer,
|
|
SslLegacyPackageName.Length );
|
|
|
|
Where += SslLegacyPackageName.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 = LsaCallAuthenticationPackage(
|
|
SslLogonHandle,
|
|
SslMsvPackageId,
|
|
Request,
|
|
RequestSize,
|
|
&Response,
|
|
&ResponseSize,
|
|
&SubStatus );
|
|
|
|
SafeAllocaFree( Request );
|
|
Request = NULL;
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertAtDC returned status 0x%x\n", Status ));
|
|
goto cleanup;
|
|
}
|
|
|
|
if ( !NT_SUCCESS( SubStatus ) )
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertAtDC returned sub-status 0x%x\n", SubStatus ));
|
|
Status = SubStatus;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Extract out the returned PAC and perform SID filtering
|
|
//
|
|
|
|
CertResp = (PSSL_CERT_LOGON_RESP) Response->ValidationData ;
|
|
|
|
PacLength = CertResp->AuthDataLength;
|
|
Pac = MIDL_user_allocate( PacLength );
|
|
if(Pac == NULL)
|
|
{
|
|
Status = STATUS_NO_MEMORY;
|
|
goto cleanup;
|
|
}
|
|
memcpy(Pac, ((PUCHAR)CertResp) + CertResp->OffsetAuthData, PacLength);
|
|
|
|
Status = SslCheckPacForSidFiltering(
|
|
pTrustSid,
|
|
&Pac,
|
|
&PacLength);
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslCheckPacForSidFiltering returned status 0x%x\n", Status ));
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Set output parameters.
|
|
//
|
|
|
|
*UserPac = Pac;
|
|
*UserPacLen = PacLength;
|
|
Pac = NULL;
|
|
|
|
*DcResponse = Response;
|
|
Response = NULL;
|
|
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapCertAtDC returned 0x%x\n", Status ));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
|
|
cleanup:
|
|
|
|
if(Pac)
|
|
{
|
|
MIDL_user_free(Pac);
|
|
}
|
|
|
|
if(pTrustSid)
|
|
{
|
|
MIDL_user_free(pTrustSid);
|
|
}
|
|
|
|
if(Response)
|
|
{
|
|
LsaTable->FreeReturnBuffer(Response);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
UNREFERENCED_PARAMETER(ClientRequest);
|
|
UNREFERENCED_PARAMETER(ClientBufferBase);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslMapExternalCredential\n" ));
|
|
|
|
//
|
|
// Validate the input parameters.
|
|
//
|
|
|
|
if ( ARGUMENT_PRESENT( ProtocolReturnBuffer ) )
|
|
{
|
|
*ProtocolReturnBuffer = NULL ;
|
|
}
|
|
|
|
if(SubmitBufferLen < sizeof(SSL_EXTERNAL_CERT_LOGON_REQ))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
Request = (PSSL_EXTERNAL_CERT_LOGON_REQ) ProtocolSubmitBuffer ;
|
|
|
|
if(Request->Length != sizeof(SSL_EXTERNAL_CERT_LOGON_REQ))
|
|
{
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Attempt to map the certificate.
|
|
//
|
|
|
|
if(RtlGetNtProductType(&ProductType))
|
|
{
|
|
DC = (ProductType == NtProductLanManNt);
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_NO_MEMORY ;
|
|
goto cleanup;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslRemoteMapCredential returns 0x%x\n", Status ));
|
|
|
|
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;
|
|
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;
|
|
PUCHAR Pac = NULL;
|
|
ULONG PacLength;
|
|
|
|
UNREFERENCED_PARAMETER(pAuthority);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslRemoteMapCredential, context %x\n", Mapper ));
|
|
|
|
if ( CredentialType != X509_ASN_CHAIN )
|
|
{
|
|
return( (DWORD)SEC_E_UNKNOWN_CREDENTIALS );
|
|
}
|
|
|
|
|
|
//
|
|
// Validate client certificate, and obtain pointer to
|
|
// entire certificate chain.
|
|
//
|
|
|
|
Status = MapperVerifyClientChain(pCert,
|
|
Mapper->m_dwFlags,
|
|
&dwMethods,
|
|
&VerifyStatus,
|
|
&pChainContext);
|
|
if(Status != STATUS_SUCCESS)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
|
|
//
|
|
// Attempt to logon via Kerberos S4U2Self.
|
|
//
|
|
|
|
if((dwMethods & REQ_UPN_MAPPING) &&
|
|
(g_dwCertMappingMethods & SP_REG_CERTMAP_S4U2SELF_FLAG))
|
|
{
|
|
DebugLog(( DEB_TRACE_MAPPER, "Trying S4U2Self mapping\n" ));
|
|
|
|
Status = SslTryS4U2Self(pChainContext, &Token);
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
CertFreeCertificateChain(pChainContext);
|
|
pChainContext = NULL;
|
|
|
|
*phLocator = (HLOCATOR) Token ;
|
|
|
|
return Status;
|
|
}
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "Failed with error 0x%x\n", 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,
|
|
FALSE,
|
|
&Pac,
|
|
&PacLength,
|
|
&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( Pac,
|
|
PacLength,
|
|
&AccountDomain,
|
|
&LogonId,
|
|
&Token );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
*phLocator = (HLOCATOR) Token ;
|
|
}
|
|
else
|
|
{
|
|
LogCertMappingFailureEvent(Status);
|
|
}
|
|
|
|
LsaTable->FreeReturnBuffer( Response );
|
|
|
|
MIDL_user_free(Pac);
|
|
|
|
return ( Status );
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalCloseLocator(
|
|
IN PHMAPPER Mapper,
|
|
IN HLOCATOR Locator
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Mapper);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslLocalCloseLocator (%p)\n", Locator ));
|
|
|
|
NtClose( (HANDLE) Locator );
|
|
|
|
return( SEC_E_OK );
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
SslLocalGetAccessToken(
|
|
IN PHMAPPER Mapper,
|
|
IN HLOCATOR Locator,
|
|
OUT HANDLE *Token
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Mapper);
|
|
|
|
DebugLog(( DEB_TRACE_MAPPER, "SslLocalGetAccessToken (%p)\n", Locator ));
|
|
|
|
*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
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: SslDomainChangeCallback
|
|
//
|
|
// Synopsis: Function to be called when domain changes
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
VOID NTAPI
|
|
SslDomainChangeCallback(
|
|
IN POLICY_NOTIFICATION_INFORMATION_CLASS ChangedInfoClass
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PLSAPR_POLICY_INFORMATION Policy = NULL;
|
|
BOOL AcquiredLock = FALSE;
|
|
UNICODE_STRING TempDnsDomainName = {0};
|
|
|
|
//
|
|
// We only care about domain dns information
|
|
//
|
|
|
|
if (ChangedInfoClass != PolicyNotifyDnsDomainInformation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DebugLog((DEB_TRACE,"SSL domain change callback\n"));
|
|
// OutputDebugStringA("SSL domain change callback\n");
|
|
|
|
|
|
//
|
|
// Get the new domain information
|
|
//
|
|
|
|
Status = I_LsaIQueryInformationPolicyTrusted(
|
|
PolicyDnsDomainInformation,
|
|
&Policy
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to query domain dns information %x - not updating.\n", Status));
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Copy the domain name
|
|
//
|
|
|
|
Status = SslDuplicateString(
|
|
&TempDnsDomainName,
|
|
(PUNICODE_STRING) &Policy->PolicyDnsDomainInfo.DnsDomainName
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Acquire the global lock so we can update the data
|
|
//
|
|
|
|
if (!SslGlobalWriteLock())
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to acquire global resource. Not changing domain.\n"));
|
|
goto Cleanup;
|
|
}
|
|
AcquiredLock = TRUE;
|
|
|
|
|
|
//
|
|
// Copy all the data to the global structures
|
|
//
|
|
|
|
SslFreeString(&SslGlobalDnsDomainName);
|
|
SslGlobalDnsDomainName = TempDnsDomainName;
|
|
TempDnsDomainName.Buffer = NULL;
|
|
|
|
DebugLog((DEB_TRACE,"SSL DNS Domain Name changed to:%ls\n", SslGlobalDnsDomainName.Buffer));
|
|
|
|
// OutputDebugStringA("SSL DNS Domain Name changed to:");
|
|
// OutputDebugStringW(SslGlobalDnsDomainName.Buffer);
|
|
// OutputDebugStringA("\n");
|
|
|
|
Cleanup:
|
|
|
|
if (AcquiredLock)
|
|
{
|
|
SslGlobalReleaseLock();
|
|
}
|
|
|
|
if (Policy != NULL)
|
|
{
|
|
I_LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyDnsDomainInformation,
|
|
Policy
|
|
);
|
|
}
|
|
|
|
SslFreeString(&TempDnsDomainName);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: SslRegisterForDomainChange
|
|
//
|
|
// Synopsis: Register with the LSA to be notified of domain changes
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
NTSTATUS
|
|
SslRegisterForDomainChange(
|
|
VOID
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
Status = I_LsaIRegisterPolicyChangeNotificationCallback(
|
|
SslDomainChangeCallback,
|
|
PolicyNotifyDnsDomainInformation
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to register for domain change notification: 0x%x.\n",Status));
|
|
}
|
|
return(Status);
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: SslUnregisterForDomainChange
|
|
//
|
|
// Synopsis: Unregister for domain change notification
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
VOID
|
|
SslUnregisterForDomainChange(
|
|
VOID
|
|
)
|
|
{
|
|
(VOID) I_LsaIUnregisterPolicyChangeNotificationCallback(
|
|
SslDomainChangeCallback,
|
|
PolicyNotifyDnsDomainInformation
|
|
);
|
|
}
|
|
|