diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/sspi | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
42 files changed, 17184 insertions, 0 deletions
diff --git a/winpr/libwinpr/sspi/CMakeLists.txt b/winpr/libwinpr/sspi/CMakeLists.txt new file mode 100644 index 0000000..f77b38f --- /dev/null +++ b/winpr/libwinpr/sspi/CMakeLists.txt @@ -0,0 +1,129 @@ +# WinPR: Windows Portable Runtime +# libwinpr-sspi cmake build script +# +# Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(MODULE_PREFIX "WINPR_SSPI") + +set(${MODULE_PREFIX}_NTLM_SRCS + NTLM/ntlm_av_pairs.c + NTLM/ntlm_av_pairs.h + NTLM/ntlm_compute.c + NTLM/ntlm_compute.h + NTLM/ntlm_message.c + NTLM/ntlm_message.h + NTLM/ntlm.c + NTLM/ntlm.h + NTLM/ntlm_export.h) + +set(${MODULE_PREFIX}_KERBEROS_SRCS + Kerberos/kerberos.c + Kerberos/kerberos.h) + +set(${MODULE_PREFIX}_NEGOTIATE_SRCS + Negotiate/negotiate.c + Negotiate/negotiate.h) + +set(${MODULE_PREFIX}_SCHANNEL_SRCS + Schannel/schannel_openssl.c + Schannel/schannel_openssl.h + Schannel/schannel.c + Schannel/schannel.h) + +set(${MODULE_PREFIX}_CREDSSP_SRCS + CredSSP/credssp.c + CredSSP/credssp.h) + +set(${MODULE_PREFIX}_SRCS + sspi_winpr.c + sspi_winpr.h + sspi_export.c + sspi_gss.c + sspi_gss.h + sspi.c + sspi.h) + +set(KRB5_DEFAULT OFF) +if (NOT WIN32 AND NOT ANDROID AND NOT IOS AND NOT APPLE) + set(KRB5_DEFAULT ON) +endif() + +option(WITH_DEBUG_SCHANNEL "Compile support for SCHANNEL debug" ${DEFAULT_DEBUG_OPTION}) +if (WITH_DEBUG_SCHANNEL) + winpr_definition_add("-DWITH_DEBUG_SCHANNEL") +endif() + +option(WITH_KRB5 "Compile support for kerberos authentication." ${KRB5_DEFAULT}) +if (WITH_KRB5) + find_package(KRB5 REQUIRED) + + list(APPEND ${MODULE_PREFIX}_KERBEROS_SRCS + Kerberos/krb5glue.h) + + winpr_include_directory_add(${KRB5_INCLUDEDIR}) + winpr_include_directory_add(${KRB5_INCLUDE_DIRS}) + winpr_library_add_private(${KRB5_LIBRARIES}) + winpr_library_add_private(${KRB5_LIBRARY}) + winpr_library_add_compile_options(${KRB5_CFLAGS}) + winpr_library_add_link_options(${KRB5_LDFLAGS}) + winpr_library_add_link_directory(${KRB5_LIBRARY_DIRS}) + + winpr_definition_add("-DWITH_KRB5") + + if(KRB5_FLAVOUR STREQUAL "MIT") + winpr_definition_add("-DWITH_KRB5_MIT") + list(APPEND ${MODULE_PREFIX}_KERBEROS_SRCS + Kerberos/krb5glue_mit.c + ) + elseif(KRB5_FLAVOUR STREQUAL "Heimdal") + winpr_definition_add("-DWITH_KRB5_HEIMDAL") + list(APPEND ${MODULE_PREFIX}_KERBEROS_SRCS + Kerberos/krb5glue_heimdal.c + ) + else() + message(WARNING "Kerberos version not detected") + endif() + + include(CMakeDependentOption) + CMAKE_DEPENDENT_OPTION(WITH_KRB5_NO_NTLM_FALLBACK "Do not fall back to NTLM if no kerberos ticket available" OFF "WITH_KRB5" OFF) + if (WITH_KRB5_NO_NTLM_FALLBACK) + add_definitions("-DWITH_KRB5_NO_NTLM_FALLBACK") + endif() +endif() + +winpr_module_add(${${MODULE_PREFIX}_CREDSSP_SRCS} + ${${MODULE_PREFIX}_NTLM_SRCS} + ${${MODULE_PREFIX}_KERBEROS_SRCS} + ${${MODULE_PREFIX}_NEGOTIATE_SRCS} + ${${MODULE_PREFIX}_SCHANNEL_SRCS} + ${${MODULE_PREFIX}_SRCS}) + +if(OPENSSL_FOUND) + winpr_include_directory_add(${OPENSSL_INCLUDE_DIR}) + winpr_library_add_private(${OPENSSL_LIBRARIES}) +endif() + +if(MBEDTLS_FOUND) + winpr_include_directory_add(${MBEDTLS_INCLUDE_DIR}) + winpr_library_add_private(${MBEDTLS_LIBRARIES}) +endif() + +if(WIN32) + winpr_library_add_public(ws2_32) +endif() + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/winpr/libwinpr/sspi/CredSSP/credssp.c b/winpr/libwinpr/sspi/CredSSP/credssp.c new file mode 100644 index 0000000..5581555 --- /dev/null +++ b/winpr/libwinpr/sspi/CredSSP/credssp.c @@ -0,0 +1,322 @@ +/** + * WinPR: Windows Portable Runtime + * Credential Security Support Provider (CredSSP) + * + * Copyright 2010-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/crt.h> +#include <winpr/sspi.h> + +#include "credssp.h" + +#include "../sspi.h" +#include "../../log.h" + +#define TAG WINPR_TAG("sspi.CredSSP") + +static const char* CREDSSP_PACKAGE_NAME = "CredSSP"; + +static SECURITY_STATUS SEC_ENTRY credssp_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + CREDSSP_CONTEXT* context = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = (CREDSSP_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + { + union + { + const void* cpv; + void* pv; + } cnv; + context = credssp_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + { + credssp_ContextFree(context); + return SEC_E_INVALID_HANDLE; + } + + sspi_SecureHandleSetLowerPointer(phNewContext, context); + + cnv.cpv = CREDSSP_PACKAGE_NAME; + sspi_SecureHandleSetUpperPointer(phNewContext, cnv.pv); + } + + return SEC_E_OK; +} + +CREDSSP_CONTEXT* credssp_ContextNew(void) +{ + CREDSSP_CONTEXT* context = NULL; + context = (CREDSSP_CONTEXT*)calloc(1, sizeof(CREDSSP_CONTEXT)); + + if (!context) + return NULL; + + return context; +} + +void credssp_ContextFree(CREDSSP_CONTEXT* context) +{ + free(context); +} + +static SECURITY_STATUS SEC_ENTRY credssp_QueryContextAttributes(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SSPI_CREDENTIALS* credentials = NULL; + SEC_WINNT_AUTH_IDENTITY* identity = NULL; + + if (fCredentialUse == SECPKG_CRED_OUTBOUND) + { + union + { + const void* cpv; + void* pv; + } cnv; + credentials = sspi_CredentialsNew(); + + if (!credentials) + return SEC_E_INSUFFICIENT_MEMORY; + + identity = (SEC_WINNT_AUTH_IDENTITY*)pAuthData; + CopyMemory(&(credentials->identity), identity, sizeof(SEC_WINNT_AUTH_IDENTITY)); + sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials); + + cnv.cpv = CREDSSP_PACKAGE_NAME; + sspi_SecureHandleSetUpperPointer(phCredential, cnv.pv); + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + if (ulAttribute == SECPKG_CRED_ATTR_NAMES) + { + SSPI_CREDENTIALS* credentials = + (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + return SEC_E_INVALID_HANDLE; + + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_FreeCredentialsHandle(PCredHandle phCredential) +{ + SSPI_CREDENTIALS* credentials = NULL; + + if (!phCredential) + return SEC_E_INVALID_HANDLE; + + credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + return SEC_E_INVALID_HANDLE; + + sspi_CredentialsFree(credentials); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY credssp_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_DecryptMessage(PCtxtHandle phContext, + PSecBufferDesc pMessage, ULONG MessageSeqNo, + ULONG* pfQOP) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY credssp_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +const SecurityFunctionTableA CREDSSP_SecurityFunctionTableA = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + credssp_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + credssp_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + credssp_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + credssp_InitializeSecurityContextA, /* InitializeSecurityContext */ + NULL, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + NULL, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + credssp_QueryContextAttributes, /* QueryContextAttributes */ + NULL, /* ImpersonateSecurityContext */ + NULL, /* RevertSecurityContext */ + credssp_MakeSignature, /* MakeSignature */ + credssp_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + credssp_EncryptMessage, /* EncryptMessage */ + credssp_DecryptMessage, /* DecryptMessage */ + NULL, /* SetContextAttributes */ + NULL, /* SetCredentialsAttributes */ +}; + +const SecurityFunctionTableW CREDSSP_SecurityFunctionTableW = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + credssp_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + credssp_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + credssp_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + credssp_InitializeSecurityContextW, /* InitializeSecurityContext */ + NULL, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + NULL, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + credssp_QueryContextAttributes, /* QueryContextAttributes */ + NULL, /* ImpersonateSecurityContext */ + NULL, /* RevertSecurityContext */ + credssp_MakeSignature, /* MakeSignature */ + credssp_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + credssp_EncryptMessage, /* EncryptMessage */ + credssp_DecryptMessage, /* DecryptMessage */ + NULL, /* SetContextAttributes */ + NULL, /* SetCredentialsAttributes */ +}; + +const SecPkgInfoA CREDSSP_SecPkgInfoA = { + 0x000110733, /* fCapabilities */ + 1, /* wVersion */ + 0xFFFF, /* wRPCID */ + 0x000090A8, /* cbMaxToken */ + "CREDSSP", /* Name */ + "Microsoft CredSSP Security Provider" /* Comment */ +}; + +static WCHAR CREDSSP_SecPkgInfoW_NameBuffer[128] = { 0 }; +static WCHAR CREDSSP_SecPkgInfoW_CommentBuffer[128] = { 0 }; + +const SecPkgInfoW CREDSSP_SecPkgInfoW = { + 0x000110733, /* fCapabilities */ + 1, /* wVersion */ + 0xFFFF, /* wRPCID */ + 0x000090A8, /* cbMaxToken */ + CREDSSP_SecPkgInfoW_NameBuffer, /* Name */ + CREDSSP_SecPkgInfoW_CommentBuffer /* Comment */ +}; + +BOOL CREDSSP_init(void) +{ + InitializeConstWCharFromUtf8(CREDSSP_SecPkgInfoA.Name, CREDSSP_SecPkgInfoW_NameBuffer, + ARRAYSIZE(CREDSSP_SecPkgInfoW_NameBuffer)); + InitializeConstWCharFromUtf8(CREDSSP_SecPkgInfoA.Comment, CREDSSP_SecPkgInfoW_CommentBuffer, + ARRAYSIZE(CREDSSP_SecPkgInfoW_CommentBuffer)); + return TRUE; +} diff --git a/winpr/libwinpr/sspi/CredSSP/credssp.h b/winpr/libwinpr/sspi/CredSSP/credssp.h new file mode 100644 index 0000000..39c8fe9 --- /dev/null +++ b/winpr/libwinpr/sspi/CredSSP/credssp.h @@ -0,0 +1,42 @@ +/** + * WinPR: Windows Portable Runtime + * Credential Security Support Provider (CredSSP) + * + * Copyright 2010-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_CREDSSP_PRIVATE_H +#define WINPR_SSPI_CREDSSP_PRIVATE_H + +#include <winpr/sspi.h> + +#include "../sspi.h" + +typedef struct +{ + BOOL server; +} CREDSSP_CONTEXT; + +CREDSSP_CONTEXT* credssp_ContextNew(void); +void credssp_ContextFree(CREDSSP_CONTEXT* context); + +extern const SecPkgInfoA CREDSSP_SecPkgInfoA; +extern const SecPkgInfoW CREDSSP_SecPkgInfoW; +extern const SecurityFunctionTableA CREDSSP_SecurityFunctionTableA; +extern const SecurityFunctionTableW CREDSSP_SecurityFunctionTableW; + +BOOL CREDSSP_init(void); + +#endif /* WINPR_SSPI_CREDSSP_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/Kerberos/kerberos.c b/winpr/libwinpr/sspi/Kerberos/kerberos.c new file mode 100644 index 0000000..b7b71f9 --- /dev/null +++ b/winpr/libwinpr/sspi/Kerberos/kerberos.c @@ -0,0 +1,1899 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Kerberos Auth Protocol + * + * Copyright 2015 ANSSI, Author Thomas Calderon + * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com> + * Copyright 2022 David Fort <contact@hardening-consulting.com> + * Copyright 2022 Isaac Klein <fifthdegree@protonmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <winpr/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <ctype.h> + +#include <winpr/assert.h> +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/print.h> +#include <winpr/tchar.h> +#include <winpr/sysinfo.h> +#include <winpr/registry.h> +#include <winpr/endian.h> +#include <winpr/crypto.h> +#include <winpr/path.h> +#include <winpr/wtypes.h> + +#include "kerberos.h" + +#ifdef WITH_KRB5_MIT +#include "krb5glue.h" +#include <profile.h> +#endif + +#ifdef WITH_KRB5_HEIMDAL +#include "krb5glue.h" +#include <krb5-protos.h> +#endif + +#include "../sspi.h" +#include "../../log.h" +#define TAG WINPR_TAG("sspi.Kerberos") + +#define KRB_TGT_REQ 16 +#define KRB_TGT_REP 17 + +const SecPkgInfoA KERBEROS_SecPkgInfoA = { + 0x000F3BBF, /* fCapabilities */ + 1, /* wVersion */ + 0x0010, /* wRPCID */ + 0x0000BB80, /* cbMaxToken : 48k bytes maximum for Windows Server 2012 */ + "Kerberos", /* Name */ + "Kerberos Security Package" /* Comment */ +}; + +static WCHAR KERBEROS_SecPkgInfoW_NameBuffer[32] = { 0 }; +static WCHAR KERBEROS_SecPkgInfoW_CommentBuffer[32] = { 0 }; + +const SecPkgInfoW KERBEROS_SecPkgInfoW = { + 0x000F3BBF, /* fCapabilities */ + 1, /* wVersion */ + 0x0010, /* wRPCID */ + 0x0000BB80, /* cbMaxToken : 48k bytes maximum for Windows Server 2012 */ + KERBEROS_SecPkgInfoW_NameBuffer, /* Name */ + KERBEROS_SecPkgInfoW_CommentBuffer /* Comment */ +}; + +#ifdef WITH_KRB5 + +enum KERBEROS_STATE +{ + KERBEROS_STATE_INITIAL, + KERBEROS_STATE_TGT_REQ, + KERBEROS_STATE_TGT_REP, + KERBEROS_STATE_AP_REQ, + KERBEROS_STATE_AP_REP, + KERBEROS_STATE_FINAL +}; + +struct s_KRB_CONTEXT +{ + enum KERBEROS_STATE state; + krb5_context ctx; + krb5_auth_context auth_ctx; + BOOL acceptor; + uint32_t flags; + uint64_t local_seq; + uint64_t remote_seq; + struct krb5glue_keyset keyset; + BOOL u2u; +}; + +typedef struct KRB_CREDENTIALS_st +{ + char* kdc_url; + krb5_ccache ccache; + krb5_keytab keytab; + krb5_keytab client_keytab; + BOOL own_ccache; /**< Whether we created ccache, and must destroy it after use. */ +} KRB_CREDENTIALS; + +static const WinPrAsn1_OID kerberos_OID = { 9, (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +static const WinPrAsn1_OID kerberos_u2u_OID = { 10, + (void*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" }; + +#define krb_log_exec(fkt, ctx, ...) \ + kerberos_log_msg(ctx, fkt(ctx, ##__VA_ARGS__), #fkt, __FILE__, __func__, __LINE__) +#define krb_log_exec_ptr(fkt, ctx, ...) \ + kerberos_log_msg(*ctx, fkt(ctx, ##__VA_ARGS__), #fkt, __FILE__, __func__, __LINE__) +static krb5_error_code kerberos_log_msg(krb5_context ctx, krb5_error_code code, const char* what, + const char* file, const char* fkt, size_t line) +{ + switch (code) + { + case 0: + case KRB5_KT_END: + break; + default: + { + const DWORD level = WLOG_ERROR; + + wLog* log = WLog_Get(TAG); + if (WLog_IsLevelActive(log, level)) + { + const char* msg = krb5_get_error_message(ctx, code); + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, "%s (%s [%d])", + what, msg, code); + krb5_free_error_message(ctx, msg); + } + } + break; + } + return code; +} + +static void kerberos_ContextFree(KRB_CONTEXT* ctx, BOOL allocated) +{ + if (ctx && ctx->ctx) + { + krb5glue_keys_free(ctx->ctx, &ctx->keyset); + + if (ctx->auth_ctx) + krb5_auth_con_free(ctx->ctx, ctx->auth_ctx); + + krb5_free_context(ctx->ctx); + } + if (allocated) + free(ctx); +} + +static KRB_CONTEXT* kerberos_ContextNew(void) +{ + KRB_CONTEXT* context = NULL; + + context = (KRB_CONTEXT*)calloc(1, sizeof(KRB_CONTEXT)); + if (!context) + return NULL; + + return context; +} + +static krb5_error_code krb5_prompter(krb5_context context, void* data, const char* name, + const char* banner, int num_prompts, krb5_prompt prompts[]) +{ + for (int i = 0; i < num_prompts; i++) + { + krb5_prompt_type type = krb5glue_get_prompt_type(context, prompts, i); + if (type && (type == KRB5_PROMPT_TYPE_PREAUTH || type == KRB5_PROMPT_TYPE_PASSWORD) && data) + { + prompts[i].reply->data = _strdup((const char*)data); + prompts[i].reply->length = strlen((const char*)data); + } + } + return 0; +} + +static INLINE krb5glue_key get_key(struct krb5glue_keyset* keyset) +{ + return keyset->acceptor_key ? keyset->acceptor_key + : keyset->initiator_key ? keyset->initiator_key + : keyset->session_key; +} + +#endif /* WITH_KRB5 */ + +static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ +#ifdef WITH_KRB5 + SEC_WINPR_KERBEROS_SETTINGS* krb_settings = NULL; + KRB_CREDENTIALS* credentials = NULL; + krb5_context ctx = NULL; + krb5_ccache ccache = NULL; + krb5_keytab keytab = NULL; + krb5_principal principal = NULL; + char* domain = NULL; + char* username = NULL; + char* password = NULL; + BOOL own_ccache = FALSE; + const char* const default_ccache_type = "MEMORY"; + + if (pAuthData) + { + UINT32 identityFlags = sspi_GetAuthIdentityFlags(pAuthData); + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_EXTENDED) + krb_settings = (((SEC_WINNT_AUTH_IDENTITY_WINPR*)pAuthData)->kerberosSettings); + + if (!sspi_CopyAuthIdentityFieldsA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &username, + &domain, &password)) + { + WLog_ERR(TAG, "Failed to copy auth identity fields"); + goto cleanup; + } + + if (!pszPrincipal) + pszPrincipal = username; + } + + if (krb_log_exec_ptr(krb5_init_context, &ctx)) + goto cleanup; + + if (domain) + { + char* udomain = _strdup(domain); + if (!udomain) + goto cleanup; + + CharUpperA(udomain); + /* Will use domain if realm is not specified in username */ + krb5_error_code rv = krb_log_exec(krb5_set_default_realm, ctx, udomain); + free(udomain); + + if (rv) + goto cleanup; + } + + if (pszPrincipal) + { + char* cpszPrincipal = _strdup(pszPrincipal); + if (!cpszPrincipal) + goto cleanup; + + /* Find realm component if included and convert to uppercase */ + char* p = strchr(cpszPrincipal, '@'); + if (p) + CharUpperA(p); + + krb5_error_code rv = krb_log_exec(krb5_parse_name, ctx, cpszPrincipal, &principal); + free(cpszPrincipal); + + if (rv) + goto cleanup; + } + + if (krb_settings && krb_settings->cache) + { + if ((krb_log_exec(krb5_cc_set_default_name, ctx, krb_settings->cache))) + goto cleanup; + } + else + own_ccache = TRUE; + + if (principal) + { + /* Use the default cache if it's initialized with the right principal */ + if (krb5_cc_cache_match(ctx, principal, &ccache) == KRB5_CC_NOTFOUND) + { + if (own_ccache) + { + if (krb_log_exec(krb5_cc_new_unique, ctx, default_ccache_type, 0, &ccache)) + goto cleanup; + } + else + { + if (krb_log_exec(krb5_cc_resolve, ctx, krb_settings->cache, &ccache)) + goto cleanup; + } + + if (krb_log_exec(krb5_cc_initialize, ctx, ccache, principal)) + goto cleanup; + } + else + own_ccache = FALSE; + } + else if (fCredentialUse & SECPKG_CRED_OUTBOUND) + { + /* Use the default cache with it's default principal */ + if (krb_log_exec(krb5_cc_default, ctx, &ccache)) + goto cleanup; + if (krb_log_exec(krb5_cc_get_principal, ctx, ccache, &principal)) + goto cleanup; + own_ccache = FALSE; + } + else + { + if (own_ccache) + { + if (krb_log_exec(krb5_cc_new_unique, ctx, default_ccache_type, 0, &ccache)) + goto cleanup; + } + else + { + if (krb_log_exec(krb5_cc_resolve, ctx, krb_settings->cache, &ccache)) + goto cleanup; + } + } + + if (krb_settings && krb_settings->keytab) + { + if (krb_log_exec(krb5_kt_resolve, ctx, krb_settings->keytab, &keytab)) + goto cleanup; + } + else + { + if (fCredentialUse & SECPKG_CRED_INBOUND) + if (krb_log_exec(krb5_kt_default, ctx, &keytab)) + goto cleanup; + } + + /* Get initial credentials if required */ + if (fCredentialUse & SECPKG_CRED_OUTBOUND) + { + if (krb_log_exec(krb5glue_get_init_creds, ctx, principal, ccache, krb5_prompter, password, + krb_settings)) + goto cleanup; + } + + credentials = calloc(1, sizeof(KRB_CREDENTIALS)); + if (!credentials) + goto cleanup; + credentials->ccache = ccache; + credentials->keytab = keytab; + credentials->own_ccache = own_ccache; + +cleanup: + + free(domain); + free(username); + free(password); + + if (principal) + krb5_free_principal(ctx, principal); + if (ctx) + { + if (!credentials) + { + if (ccache) + { + if (own_ccache) + krb5_cc_destroy(ctx, ccache); + else + krb5_cc_close(ctx, ccache); + } + if (keytab) + krb5_kt_close(ctx, keytab); + } + krb5_free_context(ctx); + } + + /* If we managed to get credentials set the output */ + if (credentials) + { + sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)KERBEROS_SSP_NAME); + return SEC_E_OK; + } + + return SEC_E_NO_CREDENTIALS; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + char* principal = NULL; + char* package = NULL; + + if (pszPrincipal) + { + principal = ConvertWCharToUtf8Alloc(pszPrincipal, NULL); + if (!principal) + return SEC_E_INSUFFICIENT_MEMORY; + } + if (pszPackage) + { + package = ConvertWCharToUtf8Alloc(pszPackage, NULL); + if (!package) + return SEC_E_INSUFFICIENT_MEMORY; + } + + status = + kerberos_AcquireCredentialsHandleA(principal, package, fCredentialUse, pvLogonID, pAuthData, + pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry); + + if (principal) + free(principal); + if (package) + free(package); + + return status; +} + +static SECURITY_STATUS SEC_ENTRY kerberos_FreeCredentialsHandle(PCredHandle phCredential) +{ +#ifdef WITH_KRB5 + KRB_CREDENTIALS* credentials = NULL; + krb5_context ctx = NULL; + + credentials = sspi_SecureHandleGetLowerPointer(phCredential); + if (!credentials) + return SEC_E_INVALID_HANDLE; + + if (krb5_init_context(&ctx)) + return SEC_E_INTERNAL_ERROR; + + free(credentials->kdc_url); + + if (credentials->ccache) + { + if (credentials->own_ccache) + krb5_cc_destroy(ctx, credentials->ccache); + else + krb5_cc_close(ctx, credentials->ccache); + } + if (credentials->keytab) + krb5_kt_close(ctx, credentials->keytab); + + krb5_free_context(ctx); + + free(credentials); + + sspi_SecureHandleInvalidate(phCredential); + return SEC_E_OK; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ +#ifdef WITH_KRB5 + if (ulAttribute == SECPKG_CRED_ATTR_NAMES) + { + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + return kerberos_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); +} + +#ifdef WITH_KRB5 + +static BOOL kerberos_mk_tgt_token(SecBuffer* buf, int msg_type, char* sname, char* host, + const krb5_data* ticket) +{ + WinPrAsn1Encoder* enc = NULL; + WinPrAsn1_MemoryChunk data; + wStream s; + size_t len = 0; + sspi_gss_data token; + BOOL ret = FALSE; + + WINPR_ASSERT(buf); + + if (msg_type != KRB_TGT_REQ && msg_type != KRB_TGT_REP) + return FALSE; + if (msg_type == KRB_TGT_REP && !ticket) + return FALSE; + + enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return FALSE; + + /* KERB-TGT-REQUEST (SEQUENCE) */ + if (!WinPrAsn1EncSeqContainer(enc)) + goto cleanup; + + /* pvno [0] INTEGER */ + if (!WinPrAsn1EncContextualInteger(enc, 0, 5)) + goto cleanup; + + /* msg-type [1] INTEGER */ + if (!WinPrAsn1EncContextualInteger(enc, 1, msg_type)) + goto cleanup; + + if (msg_type == KRB_TGT_REQ && sname) + { + /* server-name [2] PrincipalName (SEQUENCE) */ + if (!WinPrAsn1EncContextualSeqContainer(enc, 2)) + goto cleanup; + + /* name-type [0] INTEGER */ + if (!WinPrAsn1EncContextualInteger(enc, 0, KRB5_NT_SRV_HST)) + goto cleanup; + + /* name-string [1] SEQUENCE OF GeneralString */ + if (!WinPrAsn1EncContextualSeqContainer(enc, 1)) + goto cleanup; + + if (!WinPrAsn1EncGeneralString(enc, sname)) + goto cleanup; + + if (host && !WinPrAsn1EncGeneralString(enc, host)) + goto cleanup; + + if (!WinPrAsn1EncEndContainer(enc) || !WinPrAsn1EncEndContainer(enc)) + goto cleanup; + } + else if (msg_type == KRB_TGT_REP) + { + /* ticket [2] Ticket */ + data.data = (BYTE*)ticket->data; + data.len = ticket->length; + if (!WinPrAsn1EncContextualRawContent(enc, 2, &data)) + goto cleanup; + } + + if (!WinPrAsn1EncEndContainer(enc)) + goto cleanup; + + if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer) + goto cleanup; + + Stream_StaticInit(&s, buf->pvBuffer, len); + if (!WinPrAsn1EncToStream(enc, &s)) + goto cleanup; + + token.data = buf->pvBuffer; + token.length = (UINT)len; + if (sspi_gss_wrap_token(buf, &kerberos_u2u_OID, + msg_type == KRB_TGT_REQ ? TOK_ID_TGT_REQ : TOK_ID_TGT_REP, &token)) + ret = TRUE; + +cleanup: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +static BOOL kerberos_rd_tgt_token(const sspi_gss_data* token, char** target, krb5_data* ticket) +{ + WinPrAsn1Decoder dec; + WinPrAsn1Decoder dec2; + BOOL error = 0; + WinPrAsn1_tagId tag = 0; + WinPrAsn1_INTEGER val = 0; + size_t len = 0; + wStream s; + char* buf = NULL; + char* str = NULL; + + WINPR_ASSERT(token); + + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, (BYTE*)token->data, token->length); + + /* KERB-TGT-REQUEST (SEQUENCE) */ + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + return FALSE; + dec = dec2; + + /* pvno [0] INTEGER */ + if (!WinPrAsn1DecReadContextualInteger(&dec, 0, &error, &val) || val != 5) + return FALSE; + + /* msg-type [1] INTEGER */ + if (!WinPrAsn1DecReadContextualInteger(&dec, 1, &error, &val)) + return FALSE; + + if (val == KRB_TGT_REQ) + { + if (!target) + return FALSE; + *target = NULL; + + s = WinPrAsn1DecGetStream(&dec); + len = Stream_Length(&s); + if (len == 0) + return TRUE; + + buf = malloc(len); + if (!buf) + return FALSE; + + *buf = 0; + *target = buf; + + if (!WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2)) + goto fail; + + if (tag == 2) + { + WinPrAsn1Decoder seq; + /* server-name [2] PrincipalName (SEQUENCE) */ + if (!WinPrAsn1DecReadSequence(&dec2, &seq)) + goto fail; + + /* name-type [0] INTEGER */ + if (!WinPrAsn1DecReadContextualInteger(&seq, 0, &error, &val)) + goto fail; + + /* name-string [1] SEQUENCE OF GeneralString */ + if (!WinPrAsn1DecReadContextualSequence(&seq, 1, &error, &dec2)) + goto fail; + + while (WinPrAsn1DecPeekTag(&dec2, &tag)) + { + if (!WinPrAsn1DecReadGeneralString(&dec2, &str)) + goto fail; + + if (buf != *target) + *buf++ = '/'; + buf = stpcpy(buf, str); + free(str); + } + + if (!WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2)) + return TRUE; + } + + /* realm [3] Realm */ + if (tag != 3 || !WinPrAsn1DecReadGeneralString(&dec2, &str)) + goto fail; + + *buf++ = '@'; + strcpy(buf, str); + return TRUE; + } + else if (val == KRB_TGT_REP) + { + if (!ticket) + return FALSE; + + /* ticket [2] Ticket */ + if (!WinPrAsn1DecReadContextualTag(&dec, &tag, &dec2) || tag != 2) + return FALSE; + + s = WinPrAsn1DecGetStream(&dec2); + ticket->data = (char*)Stream_Buffer(&s); + ticket->length = Stream_Length(&s); + return TRUE; + } + else + return FALSE; + +fail: + free(buf); + if (target) + *target = NULL; + return FALSE; +} + +#endif /* WITH_KRB5 */ + +static BOOL kerberos_hash_channel_bindings(WINPR_DIGEST_CTX* md5, SEC_CHANNEL_BINDINGS* bindings) +{ + BYTE buf[4]; + + Data_Write_UINT32(buf, bindings->dwInitiatorAddrType); + if (!winpr_Digest_Update(md5, buf, 4)) + return FALSE; + + Data_Write_UINT32(buf, bindings->cbInitiatorLength); + if (!winpr_Digest_Update(md5, buf, 4)) + return FALSE; + + if (bindings->cbInitiatorLength && + !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwInitiatorOffset, + bindings->cbInitiatorLength)) + return FALSE; + + Data_Write_UINT32(buf, bindings->dwAcceptorAddrType); + if (!winpr_Digest_Update(md5, buf, 4)) + return FALSE; + + Data_Write_UINT32(buf, bindings->cbAcceptorLength); + if (!winpr_Digest_Update(md5, buf, 4)) + return FALSE; + + if (bindings->cbAcceptorLength && + !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwAcceptorOffset, + bindings->cbAcceptorLength)) + return FALSE; + + Data_Write_UINT32(buf, bindings->cbApplicationDataLength); + if (!winpr_Digest_Update(md5, buf, 4)) + return FALSE; + + if (bindings->cbApplicationDataLength && + !winpr_Digest_Update(md5, (BYTE*)bindings + bindings->dwApplicationDataOffset, + bindings->cbApplicationDataLength)) + return FALSE; + + return TRUE; +} + +static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry) +{ +#ifdef WITH_KRB5 + KRB_CREDENTIALS* credentials = NULL; + KRB_CONTEXT* context = NULL; + KRB_CONTEXT new_context = { 0 }; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + PSecBuffer bindings_buffer = NULL; + WINPR_DIGEST_CTX* md5 = NULL; + char* target = NULL; + char* sname = NULL; + char* host = NULL; + krb5_data input_token = { 0 }; + krb5_data output_token = { 0 }; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + WinPrAsn1_OID oid = { 0 }; + uint16_t tok_id = 0; + krb5_ap_rep_enc_part* reply = NULL; + krb5_flags ap_flags = AP_OPTS_USE_SUBKEY; + char cksum_contents[24] = { 0 }; + krb5_data cksum = { 0 }; + krb5_creds in_creds = { 0 }; + krb5_creds* creds = NULL; + + credentials = sspi_SecureHandleGetLowerPointer(phCredential); + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = sspi_SecureHandleGetLowerPointer(phContext); + + if (!credentials) + return SEC_E_NO_CREDENTIALS; + + if (pInput) + { + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS); + } + if (pOutput) + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (fContextReq & ISC_REQ_MUTUAL_AUTH) + ap_flags |= AP_OPTS_MUTUAL_REQUIRED; + + if (fContextReq & ISC_REQ_USE_SESSION_KEY) + ap_flags |= AP_OPTS_USE_SESSION_KEY; + + if (!context) + { + context = &new_context; + + if (krb_log_exec_ptr(krb5_init_context, &context->ctx)) + return SEC_E_INTERNAL_ERROR; + + if (fContextReq & ISC_REQ_USE_SESSION_KEY) + { + context->state = KERBEROS_STATE_TGT_REQ; + context->u2u = TRUE; + } + else + context->state = KERBEROS_STATE_AP_REQ; + } + else + { + if (!input_buffer || !sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token)) + goto bad_token; + if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) || + (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID))) + goto bad_token; + } + + /* Split target name into service/hostname components */ + if (pszTargetName) + { + target = _strdup(pszTargetName); + if (!target) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto cleanup; + } + host = strchr(target, '/'); + if (host) + { + *host++ = 0; + sname = target; + } + else + host = target; + } + + /* SSPI flags are compatible with GSS flags except INTEG_FLAG */ + context->flags |= (fContextReq & 0x1F); + if (fContextReq & ISC_REQ_INTEGRITY && !(fContextReq & ISC_REQ_NO_INTEGRITY)) + context->flags |= SSPI_GSS_C_INTEG_FLAG; + + switch (context->state) + { + case KERBEROS_STATE_TGT_REQ: + + if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REQ, sname, host, NULL)) + goto cleanup; + + context->state = KERBEROS_STATE_TGT_REP; + + status = SEC_I_CONTINUE_NEEDED; + + break; + + case KERBEROS_STATE_TGT_REP: + + if (tok_id != TOK_ID_TGT_REP) + goto bad_token; + + if (!kerberos_rd_tgt_token(&input_token, NULL, &in_creds.second_ticket)) + goto bad_token; + + /* Continue to AP-REQ */ + /* fall through */ + WINPR_FALLTHROUGH + + case KERBEROS_STATE_AP_REQ: + + /* Set auth_context options */ + if (krb_log_exec(krb5_auth_con_init, context->ctx, &context->auth_ctx)) + goto cleanup; + if (krb_log_exec(krb5_auth_con_setflags, context->ctx, context->auth_ctx, + KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY)) + goto cleanup; + if (krb_log_exec(krb5glue_auth_con_set_cksumtype, context->ctx, context->auth_ctx, + GSS_CHECKSUM_TYPE)) + goto cleanup; + + /* Get a service ticket */ + if (krb_log_exec(krb5_sname_to_principal, context->ctx, host, sname, KRB5_NT_SRV_HST, + &in_creds.server)) + goto cleanup; + + if (krb_log_exec(krb5_cc_get_principal, context->ctx, credentials->ccache, + &in_creds.client)) + goto cleanup; + + if (krb_log_exec(krb5_get_credentials, context->ctx, + context->u2u ? KRB5_GC_USER_USER : 0, credentials->ccache, &in_creds, + &creds)) + goto cleanup; + + /* Write the checksum (delegation not implemented) */ + cksum.data = cksum_contents; + cksum.length = sizeof(cksum_contents); + Data_Write_UINT32(cksum_contents, 16); + Data_Write_UINT32((cksum_contents + 20), context->flags); + + if (bindings_buffer) + { + SEC_CHANNEL_BINDINGS* bindings = bindings_buffer->pvBuffer; + + /* Sanity checks */ + if (bindings_buffer->cbBuffer < sizeof(SEC_CHANNEL_BINDINGS) || + (bindings->cbInitiatorLength + bindings->dwInitiatorOffset) > + bindings_buffer->cbBuffer || + (bindings->cbAcceptorLength + bindings->dwAcceptorOffset) > + bindings_buffer->cbBuffer || + (bindings->cbApplicationDataLength + bindings->dwApplicationDataOffset) > + bindings_buffer->cbBuffer) + { + status = SEC_E_BAD_BINDINGS; + goto cleanup; + } + + md5 = winpr_Digest_New(); + if (!md5) + goto cleanup; + + if (!winpr_Digest_Init(md5, WINPR_MD_MD5)) + goto cleanup; + + if (!kerberos_hash_channel_bindings(md5, bindings)) + goto cleanup; + + if (!winpr_Digest_Final(md5, (BYTE*)cksum_contents + 4, 16)) + goto cleanup; + } + + /* Make the AP_REQ message */ + if (krb_log_exec(krb5_mk_req_extended, context->ctx, &context->auth_ctx, ap_flags, + &cksum, creds, &output_token)) + goto cleanup; + + if (!sspi_gss_wrap_token(output_buffer, + context->u2u ? &kerberos_u2u_OID : &kerberos_OID, + TOK_ID_AP_REQ, &output_token)) + goto cleanup; + + if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG) + { + if (krb_log_exec(krb5_auth_con_getlocalseqnumber, context->ctx, context->auth_ctx, + (INT32*)&context->local_seq)) + goto cleanup; + context->remote_seq ^= context->local_seq; + } + + if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, FALSE, + &context->keyset)) + goto cleanup; + + context->state = KERBEROS_STATE_AP_REP; + + if (context->flags & SSPI_GSS_C_MUTUAL_FLAG) + status = SEC_I_CONTINUE_NEEDED; + else + status = SEC_E_OK; + + break; + + case KERBEROS_STATE_AP_REP: + + if (tok_id == TOK_ID_AP_REP) + { + if (krb_log_exec(krb5_rd_rep, context->ctx, context->auth_ctx, &input_token, + &reply)) + goto cleanup; + krb5_free_ap_rep_enc_part(context->ctx, reply); + } + else if (tok_id == TOK_ID_ERROR) + { + krb5glue_log_error(context->ctx, &input_token, TAG); + goto cleanup; + } + else + goto bad_token; + + if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG) + { + if (krb_log_exec(krb5_auth_con_getremoteseqnumber, context->ctx, context->auth_ctx, + (INT32*)&context->remote_seq)) + goto cleanup; + } + + if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, FALSE, + &context->keyset)) + goto cleanup; + + context->state = KERBEROS_STATE_FINAL; + + if (output_buffer) + output_buffer->cbBuffer = 0; + status = SEC_E_OK; + + break; + + case KERBEROS_STATE_FINAL: + default: + WLog_ERR(TAG, "Kerberos in invalid state!"); + goto cleanup; + } + + /* On first call allocate a new context */ + if (new_context.ctx) + { + const KRB_CONTEXT empty = { 0 }; + + context = kerberos_ContextNew(); + if (!context) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto cleanup; + } + *context = new_context; + new_context = empty; + + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME); + } + +cleanup: + +{ + /* second_ticket is not allocated */ + krb5_data edata = { 0 }; + in_creds.second_ticket = edata; + krb5_free_cred_contents(context->ctx, &in_creds); +} + + krb5_free_creds(context->ctx, creds); + if (output_token.data) + krb5glue_free_data_contents(context->ctx, &output_token); + + winpr_Digest_Free(md5); + + free(target); + kerberos_ContextFree(&new_context, FALSE); + + return status; + +bad_token: + status = SEC_E_INVALID_TOKEN; + goto cleanup; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif /* WITH_KRB5 */ +} + +static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + char* target_name = NULL; + + if (pszTargetName) + { + target_name = ConvertWCharToUtf8Alloc(pszTargetName, NULL); + if (!target_name) + return SEC_E_INSUFFICIENT_MEMORY; + } + + status = kerberos_InitializeSecurityContextA(phCredential, phContext, target_name, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); + + if (target_name) + free(target_name); + + return status; +} + +static SECURITY_STATUS SEC_ENTRY kerberos_AcceptSecurityContext( + PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq, + ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, ULONG* pfContextAttr, + PTimeStamp ptsExpity) +{ +#ifdef WITH_KRB5 + KRB_CREDENTIALS* credentials = NULL; + KRB_CONTEXT* context = NULL; + KRB_CONTEXT new_context = { 0 }; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + WinPrAsn1_OID oid = { 0 }; + uint16_t tok_id = 0; + krb5_data input_token = { 0 }; + krb5_data output_token = { 0 }; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + krb5_flags ap_flags = 0; + krb5glue_authenticator authenticator = NULL; + char* target = NULL; + char* sname = NULL; + char* realm = NULL; + krb5_kt_cursor cur = { 0 }; + krb5_keytab_entry entry = { 0 }; + krb5_principal principal = NULL; + krb5_creds creds = { 0 }; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = sspi_SecureHandleGetLowerPointer(phContext); + credentials = sspi_SecureHandleGetLowerPointer(phCredential); + + if (pInput) + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + if (pOutput) + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (!sspi_gss_unwrap_token(input_buffer, &oid, &tok_id, &input_token)) + return SEC_E_INVALID_TOKEN; + + if (!context) + { + context = &new_context; + + if (krb_log_exec_ptr(krb5_init_context, &context->ctx)) + return SEC_E_INTERNAL_ERROR; + + if (sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) + { + context->u2u = TRUE; + context->state = KERBEROS_STATE_TGT_REQ; + } + else if (sspi_gss_oid_compare(&oid, &kerberos_OID)) + context->state = KERBEROS_STATE_AP_REQ; + else + goto bad_token; + } + else + { + if ((context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_u2u_OID)) || + (!context->u2u && !sspi_gss_oid_compare(&oid, &kerberos_OID))) + goto bad_token; + } + + if (context->state == KERBEROS_STATE_TGT_REQ && tok_id == TOK_ID_TGT_REQ) + { + if (!kerberos_rd_tgt_token(&input_token, &target, NULL)) + goto bad_token; + + if (target) + { + if (*target != 0 && *target != '@') + sname = target; + realm = strchr(target, '@'); + if (realm) + realm++; + } + + if (krb_log_exec(krb5_parse_name_flags, context->ctx, sname ? sname : "", + KRB5_PRINCIPAL_PARSE_NO_REALM, &principal)) + goto cleanup; + + if (realm) + { + if (krb_log_exec(krb5glue_set_principal_realm, context->ctx, principal, realm)) + goto cleanup; + } + + if (krb_log_exec(krb5_kt_start_seq_get, context->ctx, credentials->keytab, &cur)) + goto cleanup; + + do + { + krb5_error_code rv = + krb_log_exec(krb5_kt_next_entry, context->ctx, credentials->keytab, &entry, &cur); + if (rv == KRB5_KT_END) + break; + if (rv != 0) + goto cleanup; + + if ((!sname || krb_log_exec(krb5_principal_compare_any_realm, context->ctx, principal, + entry.principal)) && + (!realm || + krb_log_exec(krb5_realm_compare, context->ctx, principal, entry.principal))) + break; + if (krb_log_exec(krb5glue_free_keytab_entry_contents, context->ctx, &entry)) + goto cleanup; + } while (1); + + if (krb_log_exec(krb5_kt_end_seq_get, context->ctx, credentials->keytab, &cur)) + goto cleanup; + + if (!entry.principal) + goto cleanup; + + /* Get the TGT */ + if (krb_log_exec(krb5_get_init_creds_keytab, context->ctx, &creds, entry.principal, + credentials->keytab, 0, NULL, NULL)) + goto cleanup; + + if (!kerberos_mk_tgt_token(output_buffer, KRB_TGT_REP, NULL, NULL, &creds.ticket)) + goto cleanup; + + if (krb_log_exec(krb5_auth_con_init, context->ctx, &context->auth_ctx)) + goto cleanup; + + if (krb_log_exec(krb5glue_auth_con_setuseruserkey, context->ctx, context->auth_ctx, + &krb5glue_creds_getkey(creds))) + goto cleanup; + + context->state = KERBEROS_STATE_AP_REQ; + } + else if (context->state == KERBEROS_STATE_AP_REQ && tok_id == TOK_ID_AP_REQ) + { + if (krb_log_exec(krb5_rd_req, context->ctx, &context->auth_ctx, &input_token, NULL, + credentials->keytab, &ap_flags, NULL)) + goto cleanup; + + if (krb_log_exec(krb5_auth_con_setflags, context->ctx, context->auth_ctx, + KRB5_AUTH_CONTEXT_DO_SEQUENCE | KRB5_AUTH_CONTEXT_USE_SUBKEY)) + goto cleanup; + + /* Retrieve and validate the checksum */ + if (krb_log_exec(krb5_auth_con_getauthenticator, context->ctx, context->auth_ctx, + &authenticator)) + goto cleanup; + if (!krb5glue_authenticator_validate_chksum(authenticator, GSS_CHECKSUM_TYPE, + &context->flags)) + goto bad_token; + + if (ap_flags & AP_OPTS_MUTUAL_REQUIRED && context->flags & SSPI_GSS_C_MUTUAL_FLAG) + { + if (!output_buffer) + goto bad_token; + if (krb_log_exec(krb5_mk_rep, context->ctx, context->auth_ctx, &output_token)) + goto cleanup; + if (!sspi_gss_wrap_token(output_buffer, + context->u2u ? &kerberos_u2u_OID : &kerberos_OID, + TOK_ID_AP_REP, &output_token)) + goto cleanup; + } + else + { + if (output_buffer) + output_buffer->cbBuffer = 0; + } + + *pfContextAttr = context->flags & 0x1F; + if (context->flags & SSPI_GSS_C_INTEG_FLAG) + *pfContextAttr |= ASC_RET_INTEGRITY; + + if (context->flags & SSPI_GSS_C_SEQUENCE_FLAG) + { + if (krb_log_exec(krb5_auth_con_getlocalseqnumber, context->ctx, context->auth_ctx, + (INT32*)&context->local_seq)) + goto cleanup; + if (krb_log_exec(krb5_auth_con_getremoteseqnumber, context->ctx, context->auth_ctx, + (INT32*)&context->remote_seq)) + goto cleanup; + } + + if (krb_log_exec(krb5glue_update_keyset, context->ctx, context->auth_ctx, TRUE, + &context->keyset)) + goto cleanup; + + context->state = KERBEROS_STATE_FINAL; + } + else + goto bad_token; + + /* On first call allocate new context */ + if (new_context.ctx) + { + const KRB_CONTEXT empty = { 0 }; + + context = kerberos_ContextNew(); + if (!context) + { + status = SEC_E_INSUFFICIENT_MEMORY; + goto cleanup; + } + *context = new_context; + new_context = empty; + context->acceptor = TRUE; + + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME); + } + + if (context->state == KERBEROS_STATE_FINAL) + status = SEC_E_OK; + else + status = SEC_I_CONTINUE_NEEDED; + +cleanup: + + free(target); + if (output_token.data) + krb5glue_free_data_contents(context->ctx, &output_token); + if (entry.principal) + krb5glue_free_keytab_entry_contents(context->ctx, &entry); + + kerberos_ContextFree(&new_context, FALSE); + return status; + +bad_token: + status = SEC_E_INVALID_TOKEN; + goto cleanup; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif /* WITH_KRB5 */ +} + +static KRB_CONTEXT* get_context(PCtxtHandle phContext) +{ + if (!phContext) + return NULL; + + TCHAR* name = sspi_SecureHandleGetUpperPointer(phContext); + if (_tcscmp(KERBEROS_SSP_NAME, name) != 0) + return NULL; + return sspi_SecureHandleGetLowerPointer(phContext); +} + +static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phContext) +{ +#ifdef WITH_KRB5 + KRB_CONTEXT* context = get_context(phContext); + if (!context) + return SEC_E_INVALID_HANDLE; + + kerberos_ContextFree(context, TRUE); + + return SEC_E_OK; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ +#ifdef WITH_KRB5 + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + + if (ulAttribute == SECPKG_ATTR_SIZES) + { + UINT header = 0; + UINT pad = 0; + UINT trailer = 0; + krb5glue_key key = NULL; + KRB_CONTEXT* context = get_context(phContext); + SecPkgContext_Sizes* ContextSizes = (SecPkgContext_Sizes*)pBuffer; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->ctx); + WINPR_ASSERT(context->auth_ctx); + + /* The MaxTokenSize by default is 12,000 bytes. This has been the default value + * since Windows 2000 SP2 and still remains in Windows 7 and Windows 2008 R2. + * For Windows Server 2012, the default value of the MaxTokenSize registry + * entry is 48,000 bytes.*/ + ContextSizes->cbMaxToken = KERBEROS_SecPkgInfoA.cbMaxToken; + ContextSizes->cbMaxSignature = 0; + ContextSizes->cbBlockSize = 1; + ContextSizes->cbSecurityTrailer = 0; + + key = get_key(&context->keyset); + + if (context->flags & SSPI_GSS_C_CONF_FLAG) + { + krb5_error_code rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, + KRB5_CRYPTO_TYPE_HEADER, &header); + if (rv) + return rv; + rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_PADDING, + &pad); + if (rv) + return rv; + rv = krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_TRAILER, + &trailer); + if (rv) + return rv; + /* GSS header (= 16 bytes) + encrypted header = 32 bytes */ + ContextSizes->cbSecurityTrailer = header + pad + trailer + 32; + } + if (context->flags & SSPI_GSS_C_INTEG_FLAG) + { + krb5_error_code rv = + krb_log_exec(krb5glue_crypto_length, context->ctx, key, KRB5_CRYPTO_TYPE_CHECKSUM, + &ContextSizes->cbMaxSignature); + if (rv) + return rv; + ContextSizes->cbMaxSignature += 16; + } + + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_QueryContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + return kerberos_QueryContextAttributesA(phContext, ulAttribute, pBuffer); +} + +static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY kerberos_SetContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesX(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer, + BOOL unicode) +{ +#ifdef WITH_KRB5 + KRB_CREDENTIALS* credentials = NULL; + + if (!phCredential) + return SEC_E_INVALID_HANDLE; + + credentials = sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + + if (ulAttribute == SECPKG_CRED_ATTR_KDC_PROXY_SETTINGS) + { + SecPkgCredentials_KdcProxySettingsW* kdc_settings = pBuffer; + + /* Sanity checks */ + if (cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) || + kdc_settings->Version != KDC_PROXY_SETTINGS_V1 || + kdc_settings->ProxyServerOffset < sizeof(SecPkgCredentials_KdcProxySettingsW) || + cbBuffer < sizeof(SecPkgCredentials_KdcProxySettingsW) + + kdc_settings->ProxyServerOffset + kdc_settings->ProxyServerLength) + return SEC_E_INVALID_TOKEN; + + if (credentials->kdc_url) + { + free(credentials->kdc_url); + credentials->kdc_url = NULL; + } + + if (kdc_settings->ProxyServerLength > 0) + { + WCHAR* proxy = (WCHAR*)((BYTE*)pBuffer + kdc_settings->ProxyServerOffset); + + credentials->kdc_url = ConvertWCharNToUtf8Alloc( + proxy, kdc_settings->ProxyServerLength / sizeof(WCHAR), NULL); + if (!credentials->kdc_url) + return SEC_E_INSUFFICIENT_MEMORY; + } + + return SEC_E_OK; + } + + return SEC_E_UNSUPPORTED_FUNCTION; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, TRUE); +} + +static SECURITY_STATUS SEC_ENTRY kerberos_SetCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + return kerberos_SetCredentialsAttributesX(phCredential, ulAttribute, pBuffer, cbBuffer, FALSE); +} + +static SECURITY_STATUS SEC_ENTRY kerberos_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, + ULONG MessageSeqNo) +{ +#ifdef WITH_KRB5 + KRB_CONTEXT* context = get_context(phContext); + PSecBuffer sig_buffer = NULL; + PSecBuffer data_buffer = NULL; + BYTE* header = NULL; + BYTE flags = 0; + krb5glue_key key = NULL; + krb5_keyusage usage = 0; + krb5_crypto_iov encrypt_iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } }, + { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_PADDING, { 0 } }, + { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } }; + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (!(context->flags & SSPI_GSS_C_CONF_FLAG)) + return SEC_E_UNSUPPORTED_FUNCTION; + + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); + data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); + + if (!sig_buffer || !data_buffer) + return SEC_E_INVALID_TOKEN; + + if (fQOP) + return SEC_E_QOP_NOT_SUPPORTED; + + flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0; + flags |= FLAG_WRAP_CONFIDENTIAL; + + key = get_key(&context->keyset); + if (!key) + return SEC_E_INTERNAL_ERROR; + + flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0; + + usage = context->acceptor ? KG_USAGE_ACCEPTOR_SEAL : KG_USAGE_INITIATOR_SEAL; + + /* Set the lengths of the data (plaintext + header) */ + encrypt_iov[1].data.length = data_buffer->cbBuffer; + encrypt_iov[2].data.length = 16; + + /* Get the lengths of the header, trailer, and padding and ensure sig_buffer is large enough */ + if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, encrypt_iov, + ARRAYSIZE(encrypt_iov))) + return SEC_E_INTERNAL_ERROR; + if (sig_buffer->cbBuffer < + encrypt_iov[0].data.length + encrypt_iov[3].data.length + encrypt_iov[4].data.length + 32) + return SEC_E_INSUFFICIENT_MEMORY; + + /* Set up the iov array in sig_buffer */ + header = sig_buffer->pvBuffer; + encrypt_iov[2].data.data = header + 16; + encrypt_iov[3].data.data = (BYTE*)encrypt_iov[2].data.data + encrypt_iov[2].data.length; + encrypt_iov[4].data.data = (BYTE*)encrypt_iov[3].data.data + encrypt_iov[3].data.length; + encrypt_iov[0].data.data = (BYTE*)encrypt_iov[4].data.data + encrypt_iov[4].data.length; + encrypt_iov[1].data.data = data_buffer->pvBuffer; + + /* Write the GSS header with 0 in RRC */ + Data_Write_UINT16_BE(header, TOK_ID_WRAP); + header[2] = flags; + header[3] = 0xFF; + Data_Write_UINT32(header + 4, 0); + Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo)); + + /* Copy header to be encrypted */ + CopyMemory(encrypt_iov[2].data.data, header, 16); + + /* Set the correct RRC */ + Data_Write_UINT16_BE(header + 6, 16 + encrypt_iov[3].data.length + encrypt_iov[4].data.length); + + if (krb_log_exec(krb5glue_encrypt_iov, context->ctx, key, usage, encrypt_iov, + ARRAYSIZE(encrypt_iov))) + return SEC_E_INTERNAL_ERROR; + + return SEC_E_OK; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_DecryptMessage(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ +#ifdef WITH_KRB5 + KRB_CONTEXT* context = get_context(phContext); + PSecBuffer sig_buffer = NULL; + PSecBuffer data_buffer = NULL; + krb5glue_key key = NULL; + krb5_keyusage usage = 0; + char* header = NULL; + uint16_t tok_id = 0; + BYTE flags = 0; + uint16_t ec = 0; + uint16_t rrc = 0; + uint64_t seq_no = 0; + krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_HEADER, { 0 } }, + { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_PADDING, { 0 } }, + { KRB5_CRYPTO_TYPE_TRAILER, { 0 } } }; + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (!(context->flags & SSPI_GSS_C_CONF_FLAG)) + return SEC_E_UNSUPPORTED_FUNCTION; + + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); + data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); + + if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16) + return SEC_E_INVALID_TOKEN; + + /* Read in header information */ + header = sig_buffer->pvBuffer; + Data_Read_UINT16_BE(header, tok_id); + flags = header[2]; + Data_Read_UINT16_BE((header + 4), ec); + Data_Read_UINT16_BE((header + 6), rrc); + Data_Read_UINT64_BE((header + 8), seq_no); + + /* Check that the header is valid */ + if (tok_id != TOK_ID_WRAP || (BYTE)header[3] != 0xFF) + return SEC_E_INVALID_TOKEN; + + if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor) + return SEC_E_INVALID_TOKEN; + + if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo) + return SEC_E_OUT_OF_SEQUENCE; + + if (!(flags & FLAG_WRAP_CONFIDENTIAL)) + return SEC_E_INVALID_TOKEN; + + /* We don't expect a trailer buffer; the encrypted header must be rotated */ + if (rrc < 16) + return SEC_E_INVALID_TOKEN; + + /* Find the proper key and key usage */ + key = get_key(&context->keyset); + if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key)) + return SEC_E_INTERNAL_ERROR; + usage = context->acceptor ? KG_USAGE_INITIATOR_SEAL : KG_USAGE_ACCEPTOR_SEAL; + + /* Fill in the lengths of the iov array */ + iov[1].data.length = data_buffer->cbBuffer; + iov[2].data.length = 16; + if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov))) + return SEC_E_INTERNAL_ERROR; + + /* We don't expect a trailer buffer; everything must be in sig_buffer */ + if (rrc != 16 + iov[3].data.length + iov[4].data.length) + return SEC_E_INVALID_TOKEN; + if (sig_buffer->cbBuffer != 16 + rrc + iov[0].data.length) + return SEC_E_INVALID_TOKEN; + + /* Locate the parts of the message */ + iov[0].data.data = header + 16 + rrc + ec; + iov[1].data.data = data_buffer->pvBuffer; + iov[2].data.data = header + 16 + ec; + iov[3].data.data = (BYTE*)iov[2].data.data + iov[2].data.length; + iov[4].data.data = (BYTE*)iov[3].data.data + iov[3].data.length; + + if (krb_log_exec(krb5glue_decrypt_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov))) + return SEC_E_INTERNAL_ERROR; + + /* Validate the encrypted header */ + Data_Write_UINT16_BE(iov[2].data.data + 4, ec); + Data_Write_UINT16_BE(iov[2].data.data + 6, rrc); + if (memcmp(iov[2].data.data, header, 16) != 0) + return SEC_E_MESSAGE_ALTERED; + + *pfQOP = 0; + + return SEC_E_OK; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ +#ifdef WITH_KRB5 + KRB_CONTEXT* context = get_context(phContext); + PSecBuffer sig_buffer = NULL; + PSecBuffer data_buffer = NULL; + krb5glue_key key = NULL; + krb5_keyusage usage = 0; + char* header = NULL; + BYTE flags = 0; + krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } }; + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (!(context->flags & SSPI_GSS_C_INTEG_FLAG)) + return SEC_E_UNSUPPORTED_FUNCTION; + + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); + data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); + + if (!sig_buffer || !data_buffer) + return SEC_E_INVALID_TOKEN; + + flags |= context->acceptor ? FLAG_SENDER_IS_ACCEPTOR : 0; + + key = get_key(&context->keyset); + if (!key) + return SEC_E_INTERNAL_ERROR; + usage = context->acceptor ? KG_USAGE_ACCEPTOR_SIGN : KG_USAGE_INITIATOR_SIGN; + + flags |= context->keyset.acceptor_key == key ? FLAG_ACCEPTOR_SUBKEY : 0; + + /* Fill in the lengths of the iov array */ + iov[0].data.length = data_buffer->cbBuffer; + iov[1].data.length = 16; + if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov))) + return SEC_E_INTERNAL_ERROR; + + /* Ensure the buffer is big enough */ + if (sig_buffer->cbBuffer < iov[2].data.length + 16) + return SEC_E_INSUFFICIENT_MEMORY; + + /* Write the header */ + header = sig_buffer->pvBuffer; + Data_Write_UINT16_BE(header, TOK_ID_MIC); + header[2] = flags; + memset(header + 3, 0xFF, 5); + Data_Write_UINT64_BE(header + 8, (context->local_seq + MessageSeqNo)); + + /* Set up the iov array */ + iov[0].data.data = data_buffer->pvBuffer; + iov[1].data.data = header; + iov[2].data.data = header + 16; + + if (krb_log_exec(krb5glue_make_checksum_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov))) + return SEC_E_INTERNAL_ERROR; + + sig_buffer->cbBuffer = iov[2].data.length + 16; + + return SEC_E_OK; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +static SECURITY_STATUS SEC_ENTRY kerberos_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ +#ifdef WITH_KRB5 + PSecBuffer sig_buffer = NULL; + PSecBuffer data_buffer = NULL; + krb5glue_key key = NULL; + krb5_keyusage usage = 0; + char* header = NULL; + BYTE flags = 0; + uint16_t tok_id = 0; + uint64_t seq_no = 0; + krb5_boolean is_valid = 0; + krb5_crypto_iov iov[] = { { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_DATA, { 0 } }, + { KRB5_CRYPTO_TYPE_CHECKSUM, { 0 } } }; + BYTE cmp_filler[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + + KRB_CONTEXT* context = get_context(phContext); + if (!context) + return SEC_E_INVALID_HANDLE; + + if (!(context->flags & SSPI_GSS_C_INTEG_FLAG)) + return SEC_E_UNSUPPORTED_FUNCTION; + + sig_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_TOKEN); + data_buffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); + + if (!sig_buffer || !data_buffer || sig_buffer->cbBuffer < 16) + return SEC_E_INVALID_TOKEN; + + /* Read in header info */ + header = sig_buffer->pvBuffer; + Data_Read_UINT16_BE(header, tok_id); + flags = header[2]; + Data_Read_UINT64_BE((header + 8), seq_no); + + /* Validate header */ + if (tok_id != TOK_ID_MIC) + return SEC_E_INVALID_TOKEN; + + if ((flags & FLAG_SENDER_IS_ACCEPTOR) == context->acceptor || flags & FLAG_WRAP_CONFIDENTIAL) + return SEC_E_INVALID_TOKEN; + + if (memcmp(header + 3, cmp_filler, sizeof(cmp_filler))) + return SEC_E_INVALID_TOKEN; + + if (context->flags & ISC_REQ_SEQUENCE_DETECT && seq_no != context->remote_seq + MessageSeqNo) + return SEC_E_OUT_OF_SEQUENCE; + + /* Find the proper key and usage */ + key = get_key(&context->keyset); + if (!key || (flags & FLAG_ACCEPTOR_SUBKEY && context->keyset.acceptor_key != key)) + return SEC_E_INTERNAL_ERROR; + usage = context->acceptor ? KG_USAGE_INITIATOR_SIGN : KG_USAGE_ACCEPTOR_SIGN; + + /* Fill in the iov array lengths */ + iov[0].data.length = data_buffer->cbBuffer; + iov[1].data.length = 16; + if (krb_log_exec(krb5glue_crypto_length_iov, context->ctx, key, iov, ARRAYSIZE(iov))) + return SEC_E_INTERNAL_ERROR; + + if (sig_buffer->cbBuffer != iov[2].data.length + 16) + return SEC_E_INTERNAL_ERROR; + + /* Set up the iov array */ + iov[0].data.data = data_buffer->pvBuffer; + iov[1].data.data = header; + iov[2].data.data = header + 16; + + if (krb_log_exec(krb5glue_verify_checksum_iov, context->ctx, key, usage, iov, ARRAYSIZE(iov), + &is_valid)) + return SEC_E_INTERNAL_ERROR; + + if (!is_valid) + return SEC_E_MESSAGE_ALTERED; + + return SEC_E_OK; +#else + return SEC_E_UNSUPPORTED_FUNCTION; +#endif +} + +const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + kerberos_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + kerberos_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + kerberos_InitializeSecurityContextA, /* InitializeSecurityContext */ + kerberos_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + kerberos_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + kerberos_QueryContextAttributesA, /* QueryContextAttributes */ + NULL, /* ImpersonateSecurityContext */ + NULL, /* RevertSecurityContext */ + kerberos_MakeSignature, /* MakeSignature */ + kerberos_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + kerberos_EncryptMessage, /* EncryptMessage */ + kerberos_DecryptMessage, /* DecryptMessage */ + kerberos_SetContextAttributesA, /* SetContextAttributes */ + kerberos_SetCredentialsAttributesA, /* SetCredentialsAttributes */ +}; + +const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + kerberos_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + kerberos_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + kerberos_InitializeSecurityContextW, /* InitializeSecurityContext */ + kerberos_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + kerberos_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + kerberos_QueryContextAttributesW, /* QueryContextAttributes */ + NULL, /* ImpersonateSecurityContext */ + NULL, /* RevertSecurityContext */ + kerberos_MakeSignature, /* MakeSignature */ + kerberos_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + kerberos_EncryptMessage, /* EncryptMessage */ + kerberos_DecryptMessage, /* DecryptMessage */ + kerberos_SetContextAttributesW, /* SetContextAttributes */ + kerberos_SetCredentialsAttributesW, /* SetCredentialsAttributes */ +}; + +BOOL KERBEROS_init(void) +{ + InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Name, KERBEROS_SecPkgInfoW_NameBuffer, + ARRAYSIZE(KERBEROS_SecPkgInfoW_NameBuffer)); + InitializeConstWCharFromUtf8(KERBEROS_SecPkgInfoA.Comment, KERBEROS_SecPkgInfoW_CommentBuffer, + ARRAYSIZE(KERBEROS_SecPkgInfoW_CommentBuffer)); + return TRUE; +} diff --git a/winpr/libwinpr/sspi/Kerberos/kerberos.h b/winpr/libwinpr/sspi/Kerberos/kerberos.h new file mode 100644 index 0000000..aa4b86d --- /dev/null +++ b/winpr/libwinpr/sspi/Kerberos/kerberos.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Kerberos Auth Protocol + * + * Copyright 2015 ANSSI, Author Thomas Calderon + * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_KERBEROS_PRIVATE_H +#define WINPR_SSPI_KERBEROS_PRIVATE_H + +#include <winpr/sspi.h> +#include <winpr/windows.h> + +#include "../sspi.h" +#include "../../log.h" + +typedef struct s_KRB_CONTEXT KRB_CONTEXT; + +extern const SecPkgInfoA KERBEROS_SecPkgInfoA; +extern const SecPkgInfoW KERBEROS_SecPkgInfoW; +extern const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA; +extern const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW; + +BOOL KERBEROS_init(void); + +#endif /* WINPR_SSPI_KERBEROS_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/Kerberos/krb5glue.h b/winpr/libwinpr/sspi/Kerberos/krb5glue.h new file mode 100644 index 0000000..2874688 --- /dev/null +++ b/winpr/libwinpr/sspi/Kerberos/krb5glue.h @@ -0,0 +1,104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Kerberos Auth Protocol + * + * Copyright 2022 Isaac Klein <fifthdegree@protonmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_KERBEROS_GLUE_PRIVATE_H +#define WINPR_SSPI_KERBEROS_GLUE_PRIVATE_H + +#include <winpr/winpr.h> +#include <winpr/sspi.h> + +#include <krb5.h> + +#if defined(WITH_KRB5_MIT) +typedef krb5_key krb5glue_key; +typedef krb5_authenticator* krb5glue_authenticator; + +#define krb5glue_crypto_length(ctx, key, type, size) \ + krb5_c_crypto_length(ctx, krb5_k_key_enctype(ctx, key), type, size) +#define krb5glue_crypto_length_iov(ctx, key, iov, size) \ + krb5_c_crypto_length_iov(ctx, krb5_k_key_enctype(ctx, key), iov, size) +#define krb5glue_encrypt_iov(ctx, key, usage, iov, size) \ + krb5_k_encrypt_iov(ctx, key, usage, NULL, iov, size) +#define krb5glue_decrypt_iov(ctx, key, usage, iov, size) \ + krb5_k_decrypt_iov(ctx, key, usage, NULL, iov, size) +#define krb5glue_make_checksum_iov(ctx, key, usage, iov, size) \ + krb5_k_make_checksum_iov(ctx, 0, key, usage, iov, size) +#define krb5glue_verify_checksum_iov(ctx, key, usage, iov, size, is_valid) \ + krb5_k_verify_checksum_iov(ctx, 0, key, usage, iov, size, is_valid) +#define krb5glue_auth_con_set_cksumtype(ctx, auth_ctx, cksumtype) \ + krb5_auth_con_set_req_cksumtype(ctx, auth_ctx, cksumtype) +#define krb5glue_set_principal_realm(ctx, principal, realm) \ + krb5_set_principal_realm(ctx, principal, realm) +#define krb5glue_free_keytab_entry_contents(ctx, entry) krb5_free_keytab_entry_contents(ctx, entry) +#define krb5glue_auth_con_setuseruserkey(ctx, auth_ctx, keytab) \ + krb5_auth_con_setuseruserkey(ctx, auth_ctx, keytab) +#define krb5glue_free_data_contents(ctx, data) krb5_free_data_contents(ctx, data) +krb5_prompt_type krb5glue_get_prompt_type(krb5_context ctx, krb5_prompt prompts[], int index); + +#define krb5glue_creds_getkey(creds) creds.keyblock + +#elif defined(WITH_KRB5_HEIMDAL) +typedef krb5_crypto krb5glue_key; +typedef krb5_authenticator krb5glue_authenticator; + +krb5_error_code krb5glue_crypto_length(krb5_context ctx, krb5glue_key key, int type, + unsigned int* size); +#define krb5glue_crypto_length_iov(ctx, key, iov, size) krb5_crypto_length_iov(ctx, key, iov, size) +#define krb5glue_encrypt_iov(ctx, key, usage, iov, size) \ + krb5_encrypt_iov_ivec(ctx, key, usage, iov, size, NULL) +#define krb5glue_decrypt_iov(ctx, key, usage, iov, size) \ + krb5_decrypt_iov_ivec(ctx, key, usage, iov, size, NULL) +#define krb5glue_make_checksum_iov(ctx, key, usage, iov, size) \ + krb5_create_checksum_iov(ctx, key, usage, iov, size, NULL) +krb5_error_code krb5glue_verify_checksum_iov(krb5_context ctx, krb5glue_key key, unsigned usage, + krb5_crypto_iov* iov, unsigned int iov_size, + krb5_boolean* is_valid); +#define krb5glue_auth_con_set_cksumtype(ctx, auth_ctx, cksumtype) \ + krb5_auth_con_setcksumtype(ctx, auth_ctx, cksumtype) +#define krb5glue_set_principal_realm(ctx, principal, realm) \ + krb5_principal_set_realm(ctx, principal, realm) +#define krb5glue_free_keytab_entry_contents(ctx, entry) krb5_kt_free_entry(ctx, entry) +#define krb5glue_auth_con_setuseruserkey(ctx, auth_ctx, keytab) \ + krb5_auth_con_setuserkey(ctx, auth_ctx, keytab) +#define krb5glue_free_data_contents(ctx, data) krb5_data_free(data) +#define krb5glue_get_prompt_type(ctx, prompts, index) prompts[index].type + +#define krb5glue_creds_getkey(creds) creds.session +#else +#error "Missing implementation for KRB5 provider" +#endif + +struct krb5glue_keyset +{ + krb5glue_key session_key; + krb5glue_key initiator_key; + krb5glue_key acceptor_key; +}; + +void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset); +krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor, + struct krb5glue_keyset* keyset); +krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag); +BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype, + uint32_t* flags); +krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache, + krb5_prompter_fct prompter, char* password, + SEC_WINPR_KERBEROS_SETTINGS* krb_settings); + +#endif /* WINPR_SSPI_KERBEROS_GLUE_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c b/winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c new file mode 100644 index 0000000..8e01b55 --- /dev/null +++ b/winpr/libwinpr/sspi/Kerberos/krb5glue_heimdal.c @@ -0,0 +1,215 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Kerberos Auth Protocol + * + * Copyright 2022 Isaac Klein <fifthdegree@protonmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WITH_KRB5_HEIMDAL +#error "This file must ony be included with HEIMDAL kerberos" +#endif + +#include <winpr/endian.h> +#include <winpr/wlog.h> +#include <winpr/assert.h> +#include "krb5glue.h" + +void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset) +{ + if (!ctx || !keyset) + return; + if (keyset->session_key) + krb5_crypto_destroy(ctx, keyset->session_key); + if (keyset->initiator_key) + krb5_crypto_destroy(ctx, keyset->initiator_key); + if (keyset->acceptor_key) + krb5_crypto_destroy(ctx, keyset->acceptor_key); +} + +krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor, + struct krb5glue_keyset* keyset) +{ + krb5_keyblock* keyblock = NULL; + krb5_error_code rv = 0; + + WINPR_ASSERT(ctx); + WINPR_ASSERT(auth_ctx); + WINPR_ASSERT(keyset); + + krb5glue_keys_free(ctx, keyset); + + if (!(rv = krb5_auth_con_getkey(ctx, auth_ctx, &keyblock))) + { + krb5_crypto_init(ctx, keyblock, ENCTYPE_NULL, &keyset->session_key); + krb5_free_keyblock(ctx, keyblock); + keyblock = NULL; + } + + if (acceptor) + rv = krb5_auth_con_getremotesubkey(ctx, auth_ctx, &keyblock); + else + rv = krb5_auth_con_getlocalsubkey(ctx, auth_ctx, &keyblock); + + if (!rv && keyblock) + { + krb5_crypto_init(ctx, keyblock, ENCTYPE_NULL, &keyset->initiator_key); + krb5_free_keyblock(ctx, keyblock); + keyblock = NULL; + } + + if (acceptor) + rv = krb5_auth_con_getlocalsubkey(ctx, auth_ctx, &keyblock); + else + rv = krb5_auth_con_getremotesubkey(ctx, auth_ctx, &keyblock); + + if (!rv && keyblock) + { + krb5_crypto_init(ctx, keyblock, ENCTYPE_NULL, &keyset->acceptor_key); + krb5_free_keyblock(ctx, keyblock); + } + + return rv; +} + +krb5_error_code krb5glue_verify_checksum_iov(krb5_context ctx, krb5glue_key key, unsigned usage, + krb5_crypto_iov* iov, unsigned int iov_size, + krb5_boolean* is_valid) +{ + krb5_error_code rv = 0; + + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(is_valid); + + rv = krb5_verify_checksum_iov(ctx, key, usage, iov, iov_size, NULL); + *is_valid = (rv == 0); + return rv; +} + +krb5_error_code krb5glue_crypto_length(krb5_context ctx, krb5glue_key key, int type, + unsigned int* size) +{ + krb5_error_code rv = 0; + size_t s = 0; + + WINPR_ASSERT(ctx); + WINPR_ASSERT(key); + WINPR_ASSERT(size); + + rv = krb5_crypto_length(ctx, key, type, &s); + *size = (UINT)s; + return rv; +} + +krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag) +{ + krb5_error error = { 0 }; + krb5_error_code rv = 0; + + WINPR_ASSERT(ctx); + WINPR_ASSERT(msg); + WINPR_ASSERT(tag); + + if (!(rv = krb5_rd_error(ctx, msg, &error))) + { + WLog_ERR(tag, "KRB_ERROR: %" PRIx32, error.error_code); + krb5_free_error_contents(ctx, &error); + } + return rv; +} + +BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype, + uint32_t* flags) +{ + WINPR_ASSERT(flags); + + if (!authenticator || !authenticator->cksum || authenticator->cksum->cksumtype != cksumtype || + authenticator->cksum->checksum.length < 24) + return FALSE; + + const BYTE* data = authenticator->cksum->checksum.data; + Data_Read_UINT32((data + 20), (*flags)); + return TRUE; +} + +krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache, + krb5_prompter_fct prompter, char* password, + SEC_WINPR_KERBEROS_SETTINGS* krb_settings) +{ + krb5_error_code rv = 0; + krb5_deltat start_time = 0; + krb5_get_init_creds_opt* gic_opt = NULL; + krb5_init_creds_context creds_ctx = NULL; + krb5_creds creds = { 0 }; + + WINPR_ASSERT(ctx); + + do + { + if ((rv = krb5_get_init_creds_opt_alloc(ctx, &gic_opt)) != 0) + break; + + krb5_get_init_creds_opt_set_forwardable(gic_opt, 0); + krb5_get_init_creds_opt_set_proxiable(gic_opt, 0); + + if (krb_settings) + { + if (krb_settings->startTime) + start_time = krb_settings->startTime; + if (krb_settings->lifeTime) + krb5_get_init_creds_opt_set_tkt_life(gic_opt, krb_settings->lifeTime); + if (krb_settings->renewLifeTime) + krb5_get_init_creds_opt_set_renew_life(gic_opt, krb_settings->renewLifeTime); + if (krb_settings->withPac) + krb5_get_init_creds_opt_set_pac_request(ctx, gic_opt, TRUE); + if (krb_settings->pkinitX509Anchors || krb_settings->pkinitX509Identity) + { + if ((rv = krb5_get_init_creds_opt_set_pkinit( + ctx, gic_opt, princ, krb_settings->pkinitX509Identity, + krb_settings->pkinitX509Anchors, NULL, NULL, 0, prompter, password, + password)) != 0) + break; + } + } + + if ((rv = krb5_init_creds_init(ctx, princ, prompter, password, start_time, gic_opt, + &creds_ctx)) != 0) + break; + if ((rv = krb5_init_creds_set_password(ctx, creds_ctx, password)) != 0) + break; + if (krb_settings && krb_settings->armorCache) + { + krb5_ccache armor_cc = NULL; + if ((rv = krb5_cc_resolve(ctx, krb_settings->armorCache, &armor_cc)) != 0) + break; + if ((rv = krb5_init_creds_set_fast_ccache(ctx, creds_ctx, armor_cc)) != 0) + break; + krb5_cc_close(ctx, armor_cc); + } + if ((rv = krb5_init_creds_get(ctx, creds_ctx)) != 0) + break; + if ((rv = krb5_init_creds_get_creds(ctx, creds_ctx, &creds)) != 0) + break; + if ((rv = krb5_cc_store_cred(ctx, ccache, &creds)) != 0) + break; + } while (0); + + krb5_free_cred_contents(ctx, &creds); + krb5_init_creds_free(ctx, creds_ctx); + krb5_get_init_creds_opt_free(ctx, gic_opt); + + return rv; +} + diff --git a/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c b/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c new file mode 100644 index 0000000..2638b22 --- /dev/null +++ b/winpr/libwinpr/sspi/Kerberos/krb5glue_mit.c @@ -0,0 +1,248 @@ +/** + * FreeRDP: A Remote Desktop Protocol Client + * Kerberos Auth Protocol + * + * Copyright 2022 Isaac Klein <fifthdegree@protonmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WITH_KRB5_MIT +#error "This file must only be included with MIT kerberos" +#endif + +#include <winpr/path.h> +#include <winpr/wlog.h> +#include <winpr/endian.h> +#include <winpr/crypto.h> +#include <winpr/print.h> +#include <winpr/assert.h> +#include <errno.h> +#include "krb5glue.h" +#include <profile.h> + +static char* create_temporary_file(void) +{ + BYTE buffer[32]; + char* hex = NULL; + char* path = NULL; + + winpr_RAND(buffer, sizeof(buffer)); + hex = winpr_BinToHexString(buffer, sizeof(buffer), FALSE); + path = GetKnownSubPath(KNOWN_PATH_TEMP, hex); + free(hex); + return path; +} + +void krb5glue_keys_free(krb5_context ctx, struct krb5glue_keyset* keyset) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(keyset); + + krb5_k_free_key(ctx, keyset->session_key); + krb5_k_free_key(ctx, keyset->initiator_key); + krb5_k_free_key(ctx, keyset->acceptor_key); +} + +krb5_error_code krb5glue_update_keyset(krb5_context ctx, krb5_auth_context auth_ctx, BOOL acceptor, + struct krb5glue_keyset* keyset) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(auth_ctx); + WINPR_ASSERT(keyset); + + krb5glue_keys_free(ctx, keyset); + krb5_auth_con_getkey_k(ctx, auth_ctx, &keyset->session_key); + if (acceptor) + { + krb5_auth_con_getsendsubkey_k(ctx, auth_ctx, &keyset->acceptor_key); + krb5_auth_con_getrecvsubkey_k(ctx, auth_ctx, &keyset->initiator_key); + } + else + { + krb5_auth_con_getsendsubkey_k(ctx, auth_ctx, &keyset->initiator_key); + krb5_auth_con_getrecvsubkey_k(ctx, auth_ctx, &keyset->acceptor_key); + } + return 0; +} + +krb5_prompt_type krb5glue_get_prompt_type(krb5_context ctx, krb5_prompt prompts[], int index) +{ + WINPR_ASSERT(ctx); + WINPR_ASSERT(prompts); + + krb5_prompt_type* types = krb5_get_prompt_types(ctx); + return types ? types[index] : 0; +} + +krb5_error_code krb5glue_log_error(krb5_context ctx, krb5_data* msg, const char* tag) +{ + krb5_error* error = NULL; + krb5_error_code rv = 0; + + WINPR_ASSERT(ctx); + WINPR_ASSERT(msg); + WINPR_ASSERT(tag); + + if (!(rv = krb5_rd_error(ctx, msg, &error))) + { + WLog_ERR(tag, "KRB_ERROR: %s", error->text.data); + krb5_free_error(ctx, error); + } + + return rv; +} + +BOOL krb5glue_authenticator_validate_chksum(krb5glue_authenticator authenticator, int cksumtype, + uint32_t* flags) +{ + WINPR_ASSERT(flags); + + if (!authenticator || !authenticator->checksum || + authenticator->checksum->checksum_type != cksumtype || authenticator->checksum->length < 24) + return FALSE; + Data_Read_UINT32((authenticator->checksum->contents + 20), (*flags)); + return TRUE; +} + +krb5_error_code krb5glue_get_init_creds(krb5_context ctx, krb5_principal princ, krb5_ccache ccache, + krb5_prompter_fct prompter, char* password, + SEC_WINPR_KERBEROS_SETTINGS* krb_settings) +{ + krb5_error_code rv = 0; + krb5_deltat start_time = 0; + krb5_get_init_creds_opt* gic_opt = NULL; + krb5_init_creds_context creds_ctx = NULL; + char* tmp_profile_path = create_temporary_file(); + profile_t profile = NULL; + BOOL is_temp_ctx = FALSE; + + WINPR_ASSERT(ctx); + + rv = krb5_get_init_creds_opt_alloc(ctx, &gic_opt); + if (rv) + goto cleanup; + + krb5_get_init_creds_opt_set_forwardable(gic_opt, 0); + krb5_get_init_creds_opt_set_proxiable(gic_opt, 0); + + if (krb_settings) + { + if (krb_settings->startTime) + start_time = krb_settings->startTime; + if (krb_settings->lifeTime) + krb5_get_init_creds_opt_set_tkt_life(gic_opt, krb_settings->lifeTime); + if (krb_settings->renewLifeTime) + krb5_get_init_creds_opt_set_renew_life(gic_opt, krb_settings->renewLifeTime); + if (krb_settings->withPac) + { + rv = krb5_get_init_creds_opt_set_pac_request(ctx, gic_opt, TRUE); + if (rv) + goto cleanup; + } + if (krb_settings->armorCache) + { + rv = krb5_get_init_creds_opt_set_fast_ccache_name(ctx, gic_opt, + krb_settings->armorCache); + if (rv) + goto cleanup; + } + if (krb_settings->pkinitX509Identity) + { + rv = krb5_get_init_creds_opt_set_pa(ctx, gic_opt, "X509_user_identity", + krb_settings->pkinitX509Identity); + if (rv) + goto cleanup; + } + if (krb_settings->pkinitX509Anchors) + { + rv = krb5_get_init_creds_opt_set_pa(ctx, gic_opt, "X509_anchors", + krb_settings->pkinitX509Anchors); + if (rv) + goto cleanup; + } + if (krb_settings->kdcUrl) + { + const char* names[4] = { 0 }; + char* realm = NULL; + char* kdc_url = NULL; + size_t size = 0; + + if ((rv = krb5_get_profile(ctx, &profile))) + goto cleanup; + + rv = ENOMEM; + if (winpr_asprintf(&kdc_url, &size, "https://%s/KdcProxy", krb_settings->kdcUrl) <= 0) + goto cleanup; + + realm = calloc(princ->realm.length + 1, 1); + if (!realm) + { + free(kdc_url); + goto cleanup; + } + CopyMemory(realm, princ->realm.data, princ->realm.length); + + names[0] = "realms"; + names[1] = realm; + names[2] = "kdc"; + + profile_clear_relation(profile, names); + profile_add_relation(profile, names, kdc_url); + + /* Since we know who the KDC is, tell krb5 that its certificate is valid for pkinit */ + names[2] = "pkinit_kdc_hostname"; + profile_add_relation(profile, names, krb_settings->kdcUrl); + + free(kdc_url); + free(realm); + + if ((rv = profile_flush_to_file(profile, tmp_profile_path))) + goto cleanup; + + profile_release(profile); + profile = NULL; + if ((rv = profile_init_path(tmp_profile_path, &profile))) + goto cleanup; + + if ((rv = krb5_init_context_profile(profile, 0, &ctx))) + goto cleanup; + is_temp_ctx = TRUE; + } + } + + if ((rv = krb5_get_init_creds_opt_set_in_ccache(ctx, gic_opt, ccache))) + goto cleanup; + + if ((rv = krb5_get_init_creds_opt_set_out_ccache(ctx, gic_opt, ccache))) + goto cleanup; + + if ((rv = + krb5_init_creds_init(ctx, princ, prompter, password, start_time, gic_opt, &creds_ctx))) + goto cleanup; + + if ((rv = krb5_init_creds_get(ctx, creds_ctx))) + goto cleanup; + +cleanup: + krb5_init_creds_free(ctx, creds_ctx); + krb5_get_init_creds_opt_free(ctx, gic_opt); + if (is_temp_ctx) + krb5_free_context(ctx); + profile_release(profile); + winpr_DeleteFile(tmp_profile_path); + free(tmp_profile_path); + + return rv; +} + diff --git a/winpr/libwinpr/sspi/ModuleOptions.cmake b/winpr/libwinpr/sspi/ModuleOptions.cmake new file mode 100644 index 0000000..b947e30 --- /dev/null +++ b/winpr/libwinpr/sspi/ModuleOptions.cmake @@ -0,0 +1,9 @@ + +set(MINWIN_LAYER "0") +set(MINWIN_GROUP "none") +set(MINWIN_MAJOR_VERSION "0") +set(MINWIN_MINOR_VERSION "0") +set(MINWIN_SHORT_NAME "sspi") +set(MINWIN_LONG_NAME "Security Support Provider Interface") +set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}") + diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.c b/winpr/libwinpr/sspi/NTLM/ntlm.c new file mode 100644 index 0000000..6a2ee6a --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm.c @@ -0,0 +1,1526 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/sspi.h> +#include <winpr/print.h> +#include <winpr/string.h> +#include <winpr/tchar.h> +#include <winpr/sysinfo.h> +#include <winpr/registry.h> +#include <winpr/endian.h> +#include <winpr/build-config.h> + +#include "ntlm.h" +#include "ntlm_export.h" +#include "../sspi.h" + +#include "ntlm_message.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +#define WINPR_KEY "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\WinPR\\NTLM" + +static char* NTLM_PACKAGE_NAME = "NTLM"; + +#define check_context(ctx) check_context_((ctx), __FILE__, __func__, __LINE__) +static BOOL check_context_(NTLM_CONTEXT* context, const char* file, const char* fkt, size_t line) +{ + BOOL rc = TRUE; + wLog* log = WLog_Get(TAG); + const DWORD log_level = WLOG_ERROR; + + if (!context) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context"); + + return FALSE; + } + + if (!context->RecvRc4Seal) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->RecvRc4Seal"); + rc = FALSE; + } + if (!context->SendRc4Seal) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->SendRc4Seal"); + rc = FALSE; + } + + if (!context->SendSigningKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->SendSigningKey"); + rc = FALSE; + } + if (!context->RecvSigningKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->RecvSigningKey"); + rc = FALSE; + } + if (!context->SendSealingKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->SendSealingKey"); + rc = FALSE; + } + if (!context->RecvSealingKey) + { + if (WLog_IsLevelActive(log, log_level)) + WLog_PrintMessage(log, WLOG_MESSAGE_TEXT, log_level, line, file, fkt, + "invalid context->RecvSealingKey"); + rc = FALSE; + } + return rc; +} + +static int ntlm_SetContextWorkstation(NTLM_CONTEXT* context, char* Workstation) +{ + char* ws = Workstation; + DWORD nSize = 0; + CHAR* computerName = NULL; + + WINPR_ASSERT(context); + + if (!Workstation) + { + if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || + GetLastError() != ERROR_MORE_DATA) + return -1; + + computerName = calloc(nSize, sizeof(CHAR)); + + if (!computerName) + return -1; + + if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize)) + { + free(computerName); + return -1; + } + + if (nSize > MAX_COMPUTERNAME_LENGTH) + computerName[MAX_COMPUTERNAME_LENGTH] = '\0'; + + ws = computerName; + + if (!ws) + return -1; + } + + size_t len = 0; + context->Workstation.Buffer = ConvertUtf8ToWCharAlloc(ws, &len); + + if (!Workstation) + free(ws); + + if (!context->Workstation.Buffer || (len > UINT16_MAX / sizeof(WCHAR))) + return -1; + + context->Workstation.Length = (USHORT)(len * sizeof(WCHAR)); + return 1; +} + +static int ntlm_SetContextServicePrincipalNameW(NTLM_CONTEXT* context, LPWSTR ServicePrincipalName) +{ + WINPR_ASSERT(context); + + if (!ServicePrincipalName) + { + context->ServicePrincipalName.Buffer = NULL; + context->ServicePrincipalName.Length = 0; + return 1; + } + + context->ServicePrincipalName.Length = (USHORT)(_wcslen(ServicePrincipalName) * 2); + context->ServicePrincipalName.Buffer = (PWSTR)malloc(context->ServicePrincipalName.Length + 2); + + if (!context->ServicePrincipalName.Buffer) + return -1; + + memcpy(context->ServicePrincipalName.Buffer, ServicePrincipalName, + context->ServicePrincipalName.Length + 2); + return 1; +} + +static int ntlm_SetContextTargetName(NTLM_CONTEXT* context, char* TargetName) +{ + char* name = TargetName; + DWORD nSize = 0; + CHAR* computerName = NULL; + + WINPR_ASSERT(context); + + if (!name) + { + if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || + GetLastError() != ERROR_MORE_DATA) + return -1; + + computerName = calloc(nSize, sizeof(CHAR)); + + if (!computerName) + return -1; + + if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize)) + { + free(computerName); + return -1; + } + + if (nSize > MAX_COMPUTERNAME_LENGTH) + computerName[MAX_COMPUTERNAME_LENGTH] = '\0'; + + name = computerName; + + if (!name) + return -1; + + CharUpperA(name); + } + + size_t len = 0; + context->TargetName.pvBuffer = ConvertUtf8ToWCharAlloc(name, &len); + + if (!context->TargetName.pvBuffer || (len > UINT16_MAX / sizeof(WCHAR))) + { + free(context->TargetName.pvBuffer); + context->TargetName.pvBuffer = NULL; + + if (!TargetName) + free(name); + + return -1; + } + + context->TargetName.cbBuffer = (USHORT)(len * sizeof(WCHAR)); + + if (!TargetName) + free(name); + + return 1; +} + +static NTLM_CONTEXT* ntlm_ContextNew(void) +{ + HKEY hKey = 0; + LONG status = 0; + DWORD dwType = 0; + DWORD dwSize = 0; + DWORD dwValue = 0; + NTLM_CONTEXT* context = (NTLM_CONTEXT*)calloc(1, sizeof(NTLM_CONTEXT)); + + if (!context) + return NULL; + + context->NTLMv2 = TRUE; + context->UseMIC = FALSE; + context->SendVersionInfo = TRUE; + context->SendSingleHostData = FALSE; + context->SendWorkstationName = TRUE; + context->NegotiateKeyExchange = TRUE; + context->UseSamFileDatabase = TRUE; + status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, WINPR_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("NTLMv2"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->NTLMv2 = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("UseMIC"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->UseMIC = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendVersionInfo"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + context->SendVersionInfo = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendSingleHostData"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendSingleHostData = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("SendWorkstationName"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SendWorkstationName = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("WorkstationName"), NULL, &dwType, NULL, &dwSize) == + ERROR_SUCCESS) + { + char* workstation = (char*)malloc(dwSize + 1); + + if (!workstation) + { + free(context); + return NULL; + } + + status = RegQueryValueExA(hKey, "WorkstationName", NULL, &dwType, (BYTE*)workstation, + &dwSize); + if (status != ERROR_SUCCESS) + WLog_WARN(TAG, "Key ''WorkstationName' not found"); + workstation[dwSize] = '\0'; + + if (ntlm_SetContextWorkstation(context, workstation) < 0) + { + free(workstation); + free(context); + return NULL; + } + + free(workstation); + } + + RegCloseKey(hKey); + } + + /* + * Extended Protection is enabled by default in Windows 7, + * but enabling it in WinPR breaks TS Gateway at this point + */ + context->SuppressExtendedProtection = FALSE; + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\CurrentControlSet\\Control\\LSA"), 0, + KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + if (RegQueryValueEx(hKey, _T("SuppressExtendedProtection"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + context->SuppressExtendedProtection = dwValue ? 1 : 0; + + RegCloseKey(hKey); + } + + context->NegotiateFlags = 0; + context->LmCompatibilityLevel = 3; + ntlm_change_state(context, NTLM_STATE_INITIAL); + FillMemory(context->MachineID, sizeof(context->MachineID), 0xAA); + + if (context->NTLMv2) + context->UseMIC = TRUE; + + return context; +} + +static void ntlm_ContextFree(NTLM_CONTEXT* context) +{ + if (!context) + return; + + winpr_RC4_Free(context->SendRc4Seal); + winpr_RC4_Free(context->RecvRc4Seal); + sspi_SecBufferFree(&context->NegotiateMessage); + sspi_SecBufferFree(&context->ChallengeMessage); + sspi_SecBufferFree(&context->AuthenticateMessage); + sspi_SecBufferFree(&context->ChallengeTargetInfo); + sspi_SecBufferFree(&context->TargetName); + sspi_SecBufferFree(&context->NtChallengeResponse); + sspi_SecBufferFree(&context->LmChallengeResponse); + free(context->ServicePrincipalName.Buffer); + free(context->Workstation.Buffer); + free(context); +} + +static SECURITY_STATUS SEC_ENTRY ntlm_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SEC_WINPR_NTLM_SETTINGS* settings = NULL; + + if ((fCredentialUse != SECPKG_CRED_OUTBOUND) && (fCredentialUse != SECPKG_CRED_INBOUND) && + (fCredentialUse != SECPKG_CRED_BOTH)) + { + return SEC_E_INVALID_PARAMETER; + } + + SSPI_CREDENTIALS* credentials = sspi_CredentialsNew(); + + if (!credentials) + return SEC_E_INTERNAL_ERROR; + + credentials->fCredentialUse = fCredentialUse; + credentials->pGetKeyFn = pGetKeyFn; + credentials->pvGetKeyArgument = pvGetKeyArgument; + + if (pAuthData) + { + UINT32 identityFlags = sspi_GetAuthIdentityFlags(pAuthData); + + sspi_CopyAuthIdentity(&(credentials->identity), + (const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData); + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_EXTENDED) + settings = (((SEC_WINNT_AUTH_IDENTITY_WINPR*)pAuthData)->ntlmSettings); + } + + if (settings) + { + if (settings->samFile) + { + credentials->ntlmSettings.samFile = _strdup(settings->samFile); + if (!credentials->ntlmSettings.samFile) + { + sspi_CredentialsFree(credentials); + return SEC_E_INSUFFICIENT_MEMORY; + } + } + credentials->ntlmSettings.hashCallback = settings->hashCallback; + credentials->ntlmSettings.hashCallbackArg = settings->hashCallbackArg; + } + + sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)NTLM_PACKAGE_NAME); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = SEC_E_INSUFFICIENT_MEMORY; + SEC_WCHAR* principal = NULL; + SEC_WCHAR* package = NULL; + + if (pszPrincipal) + { + principal = ConvertUtf8ToWCharAlloc(pszPrincipal, NULL); + if (!principal) + goto fail; + } + if (pszPackage) + { + package = ConvertUtf8ToWCharAlloc(pszPackage, NULL); + if (!package) + goto fail; + } + + status = + ntlm_AcquireCredentialsHandleW(principal, package, fCredentialUse, pvLogonID, pAuthData, + pGetKeyFn, pvGetKeyArgument, phCredential, ptsExpiry); + +fail: + free(principal); + free(package); + + return status; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_FreeCredentialsHandle(PCredHandle phCredential) +{ + if (!phCredential) + return SEC_E_INVALID_HANDLE; + + SSPI_CREDENTIALS* credentials = + (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + return SEC_E_INVALID_HANDLE; + + sspi_CredentialsFree(credentials); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + if (ulAttribute == SECPKG_CRED_ATTR_NAMES) + { + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + return ntlm_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); +} + +/** + * @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374707 + */ +static SECURITY_STATUS SEC_ENTRY +ntlm_AcceptSecurityContext(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, + ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext, + PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp) +{ + SECURITY_STATUS status = 0; + SSPI_CREDENTIALS* credentials = NULL; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + { + context = ntlm_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + context->server = TRUE; + + if (fContextReq & ASC_REQ_CONFIDENTIALITY) + context->confidentiality = TRUE; + + credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + context->credentials = credentials; + context->SamFile = credentials->ntlmSettings.samFile; + context->HashCallback = credentials->ntlmSettings.hashCallback; + context->HashCallbackArg = credentials->ntlmSettings.hashCallbackArg; + + ntlm_SetContextTargetName(context, NULL); + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, (void*)NTLM_PACKAGE_NAME); + } + + switch (ntlm_get_state(context)) + { + case NTLM_STATE_INITIAL: + { + ntlm_change_state(context, NTLM_STATE_NEGOTIATE); + + if (!pInput) + return SEC_E_INVALID_TOKEN; + + if (pInput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (input_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + status = ntlm_read_NegotiateMessage(context, input_buffer); + if (status != SEC_I_CONTINUE_NEEDED) + return status; + + if (ntlm_get_state(context) == NTLM_STATE_CHALLENGE) + { + if (!pOutput) + return SEC_E_INVALID_TOKEN; + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!output_buffer->BufferType) + return SEC_E_INVALID_TOKEN; + + if (output_buffer->cbBuffer < 1) + return SEC_E_INSUFFICIENT_MEMORY; + + return ntlm_write_ChallengeMessage(context, output_buffer); + } + + return SEC_E_OUT_OF_SEQUENCE; + } + + case NTLM_STATE_AUTHENTICATE: + { + if (!pInput) + return SEC_E_INVALID_TOKEN; + + if (pInput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (input_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + status = ntlm_read_AuthenticateMessage(context, input_buffer); + + if (pOutput) + { + for (ULONG i = 0; i < pOutput->cBuffers; i++) + { + pOutput->pBuffers[i].cbBuffer = 0; + pOutput->pBuffers[i].BufferType = SECBUFFER_TOKEN; + } + } + + return status; + } + + default: + return SEC_E_OUT_OF_SEQUENCE; + } +} + +static SECURITY_STATUS SEC_ENTRY ntlm_ImpersonateSecurityContext(PCtxtHandle phContext) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SSPI_CREDENTIALS* credentials = NULL; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + PSecBuffer channel_bindings = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (pInput) + { + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + channel_bindings = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS); + } + + if (!context) + { + context = ntlm_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + if (fContextReq & ISC_REQ_CONFIDENTIALITY) + context->confidentiality = TRUE; + + credentials = (SSPI_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + context->credentials = credentials; + + if (context->Workstation.Length < 1) + { + if (ntlm_SetContextWorkstation(context, NULL) < 0) + { + ntlm_ContextFree(context); + return SEC_E_INTERNAL_ERROR; + } + } + + if (ntlm_SetContextServicePrincipalNameW(context, pszTargetName) < 0) + { + ntlm_ContextFree(context); + return SEC_E_INTERNAL_ERROR; + } + + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, NTLM_SSP_NAME); + } + + if ((!input_buffer) || (ntlm_get_state(context) == NTLM_STATE_AUTHENTICATE)) + { + if (!pOutput) + return SEC_E_INVALID_TOKEN; + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!output_buffer) + return SEC_E_INVALID_TOKEN; + + if (output_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + if (ntlm_get_state(context) == NTLM_STATE_INITIAL) + ntlm_change_state(context, NTLM_STATE_NEGOTIATE); + + if (ntlm_get_state(context) == NTLM_STATE_NEGOTIATE) + return ntlm_write_NegotiateMessage(context, output_buffer); + + return SEC_E_OUT_OF_SEQUENCE; + } + else + { + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + if (input_buffer->cbBuffer < 1) + return SEC_E_INVALID_TOKEN; + + channel_bindings = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS); + + if (channel_bindings) + { + context->Bindings.BindingsLength = channel_bindings->cbBuffer; + context->Bindings.Bindings = (SEC_CHANNEL_BINDINGS*)channel_bindings->pvBuffer; + } + + if (ntlm_get_state(context) == NTLM_STATE_CHALLENGE) + { + status = ntlm_read_ChallengeMessage(context, input_buffer); + + if (status != SEC_I_CONTINUE_NEEDED) + return status; + + if (!pOutput) + return SEC_E_INVALID_TOKEN; + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!output_buffer) + return SEC_E_INVALID_TOKEN; + + if (output_buffer->cbBuffer < 1) + return SEC_E_INSUFFICIENT_MEMORY; + + if (ntlm_get_state(context) == NTLM_STATE_AUTHENTICATE) + return ntlm_write_AuthenticateMessage(context, output_buffer); + } + + return SEC_E_OUT_OF_SEQUENCE; + } + + return SEC_E_OUT_OF_SEQUENCE; +} + +/** + * @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa375512%28v=vs.85%29.aspx + */ +static SECURITY_STATUS SEC_ENTRY ntlm_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SEC_WCHAR* pszTargetNameW = NULL; + + if (pszTargetName) + { + pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL); + if (!pszTargetNameW) + return SEC_E_INTERNAL_ERROR; + } + + status = ntlm_InitializeSecurityContextW(phCredential, phContext, pszTargetNameW, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); + free(pszTargetNameW); + return status; +} + +/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375354 */ + +static SECURITY_STATUS SEC_ENTRY ntlm_DeleteSecurityContext(PCtxtHandle phContext) +{ + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + ntlm_ContextFree(context); + return SEC_E_OK; +} + +SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT* ntlm, SecBuffer* ntproof) +{ + BYTE* blob = NULL; + SecBuffer* target = NULL; + + WINPR_ASSERT(ntlm); + WINPR_ASSERT(ntproof); + + target = &ntlm->ChallengeTargetInfo; + + if (!sspi_SecBufferAlloc(ntproof, 36 + target->cbBuffer)) + return SEC_E_INSUFFICIENT_MEMORY; + + blob = (BYTE*)ntproof->pvBuffer; + CopyMemory(blob, ntlm->ServerChallenge, 8); /* Server challenge. */ + blob[8] = 1; /* Response version. */ + blob[9] = 1; /* Highest response version understood by the client. */ + /* Reserved 6B. */ + CopyMemory(&blob[16], ntlm->Timestamp, 8); /* Time. */ + CopyMemory(&blob[24], ntlm->ClientChallenge, 8); /* Client challenge. */ + /* Reserved 4B. */ + /* Server name. */ + CopyMemory(&blob[36], target->pvBuffer, target->cbBuffer); + return SEC_E_OK; +} + +SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT* ntlm, SecBuffer* micvalue) +{ + BYTE* blob = NULL; + ULONG msgSize = 0; + + WINPR_ASSERT(ntlm); + WINPR_ASSERT(micvalue); + + msgSize = ntlm->NegotiateMessage.cbBuffer + ntlm->ChallengeMessage.cbBuffer + + ntlm->AuthenticateMessage.cbBuffer; + + if (!sspi_SecBufferAlloc(micvalue, msgSize)) + return SEC_E_INSUFFICIENT_MEMORY; + + blob = (BYTE*)micvalue->pvBuffer; + CopyMemory(blob, ntlm->NegotiateMessage.pvBuffer, ntlm->NegotiateMessage.cbBuffer); + blob += ntlm->NegotiateMessage.cbBuffer; + CopyMemory(blob, ntlm->ChallengeMessage.pvBuffer, ntlm->ChallengeMessage.cbBuffer); + blob += ntlm->ChallengeMessage.cbBuffer; + CopyMemory(blob, ntlm->AuthenticateMessage.pvBuffer, ntlm->AuthenticateMessage.cbBuffer); + blob += ntlm->MessageIntegrityCheckOffset; + ZeroMemory(blob, 16); + return SEC_E_OK; +} + +/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa379337/ */ + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + if (ulAttribute == SECPKG_ATTR_SIZES) + { + SecPkgContext_Sizes* ContextSizes = (SecPkgContext_Sizes*)pBuffer; + ContextSizes->cbMaxToken = 2010; + ContextSizes->cbMaxSignature = 16; /* the size of expected signature is 16 bytes */ + ContextSizes->cbBlockSize = 0; /* no padding */ + ContextSizes->cbSecurityTrailer = 16; /* no security trailer appended in NTLM + contrary to Kerberos */ + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_IDENTITY) + { + SSPI_CREDENTIALS* credentials = NULL; + const SecPkgContext_AuthIdentity empty = { 0 }; + SecPkgContext_AuthIdentity* AuthIdentity = (SecPkgContext_AuthIdentity*)pBuffer; + + WINPR_ASSERT(AuthIdentity); + *AuthIdentity = empty; + + context->UseSamFileDatabase = FALSE; + credentials = context->credentials; + + if (credentials->identity.UserLength > 0) + { + if (ConvertWCharNToUtf8(credentials->identity.User, credentials->identity.UserLength, + AuthIdentity->User, ARRAYSIZE(AuthIdentity->User)) <= 0) + return SEC_E_INTERNAL_ERROR; + } + + if (credentials->identity.DomainLength > 0) + { + if (ConvertWCharNToUtf8(credentials->identity.Domain, + credentials->identity.DomainLength, AuthIdentity->Domain, + ARRAYSIZE(AuthIdentity->Domain)) <= 0) + return SEC_E_INTERNAL_ERROR; + } + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_NTPROOF_VALUE) + { + return ntlm_computeProofValue(context, (SecBuffer*)pBuffer); + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_RANDKEY) + { + SecBuffer* randkey = NULL; + randkey = (SecBuffer*)pBuffer; + + if (!sspi_SecBufferAlloc(randkey, 16)) + return (SEC_E_INSUFFICIENT_MEMORY); + + CopyMemory(randkey->pvBuffer, context->EncryptedRandomSessionKey, 16); + return (SEC_E_OK); + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC) + { + SecBuffer* mic = (SecBuffer*)pBuffer; + NTLM_AUTHENTICATE_MESSAGE* message = &context->AUTHENTICATE_MESSAGE; + + if (!sspi_SecBufferAlloc(mic, 16)) + return (SEC_E_INSUFFICIENT_MEMORY); + + CopyMemory(mic->pvBuffer, message->MessageIntegrityCheck, 16); + return (SEC_E_OK); + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MIC_VALUE) + { + return ntlm_computeMicValue(context, (SecBuffer*)pBuffer); + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=0x%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_QueryContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + return ntlm_QueryContextAttributesW(phContext, ulAttribute, pBuffer); +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INVALID_PARAMETER; + + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!context) + return SEC_E_INVALID_HANDLE; + + if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_HASH) + { + SecPkgContext_AuthNtlmHash* AuthNtlmHash = (SecPkgContext_AuthNtlmHash*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmHash)) + return SEC_E_INVALID_PARAMETER; + + if (AuthNtlmHash->Version == 1) + CopyMemory(context->NtlmHash, AuthNtlmHash->NtlmHash, 16); + else if (AuthNtlmHash->Version == 2) + CopyMemory(context->NtlmV2Hash, AuthNtlmHash->NtlmHash, 16); + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_MESSAGE) + { + SecPkgContext_AuthNtlmMessage* AuthNtlmMessage = (SecPkgContext_AuthNtlmMessage*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmMessage)) + return SEC_E_INVALID_PARAMETER; + + if (AuthNtlmMessage->type == 1) + { + sspi_SecBufferFree(&context->NegotiateMessage); + + if (!sspi_SecBufferAlloc(&context->NegotiateMessage, AuthNtlmMessage->length)) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(context->NegotiateMessage.pvBuffer, AuthNtlmMessage->buffer, + AuthNtlmMessage->length); + } + else if (AuthNtlmMessage->type == 2) + { + sspi_SecBufferFree(&context->ChallengeMessage); + + if (!sspi_SecBufferAlloc(&context->ChallengeMessage, AuthNtlmMessage->length)) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(context->ChallengeMessage.pvBuffer, AuthNtlmMessage->buffer, + AuthNtlmMessage->length); + } + else if (AuthNtlmMessage->type == 3) + { + sspi_SecBufferFree(&context->AuthenticateMessage); + + if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, AuthNtlmMessage->length)) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(context->AuthenticateMessage.pvBuffer, AuthNtlmMessage->buffer, + AuthNtlmMessage->length); + } + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_TIMESTAMP) + { + SecPkgContext_AuthNtlmTimestamp* AuthNtlmTimestamp = + (SecPkgContext_AuthNtlmTimestamp*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmTimestamp)) + return SEC_E_INVALID_PARAMETER; + + if (AuthNtlmTimestamp->ChallengeOrResponse) + CopyMemory(context->ChallengeTimestamp, AuthNtlmTimestamp->Timestamp, 8); + else + CopyMemory(context->Timestamp, AuthNtlmTimestamp->Timestamp, 8); + + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE) + { + SecPkgContext_AuthNtlmClientChallenge* AuthNtlmClientChallenge = + (SecPkgContext_AuthNtlmClientChallenge*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmClientChallenge)) + return SEC_E_INVALID_PARAMETER; + + CopyMemory(context->ClientChallenge, AuthNtlmClientChallenge->ClientChallenge, 8); + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE) + { + SecPkgContext_AuthNtlmServerChallenge* AuthNtlmServerChallenge = + (SecPkgContext_AuthNtlmServerChallenge*)pBuffer; + + if (cbBuffer < sizeof(SecPkgContext_AuthNtlmServerChallenge)) + return SEC_E_INVALID_PARAMETER; + + CopyMemory(context->ServerChallenge, AuthNtlmServerChallenge->ServerChallenge, 8); + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return ntlm_SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer); +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_SetCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_RevertSecurityContext(PCtxtHandle phContext) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + const UINT32 SeqNo = MessageSeqNo; + UINT32 value = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + ULONG version = 1; + PSecBuffer data_buffer = NULL; + PSecBuffer signature_buffer = NULL; + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG index = 0; index < pMessage->cBuffers; index++) + { + SecBuffer* cur = &pMessage->pBuffers[index]; + + if (cur->BufferType & SECBUFFER_DATA) + data_buffer = cur; + else if (cur->BufferType & SECBUFFER_TOKEN) + signature_buffer = cur; + } + + if (!data_buffer) + return SEC_E_INVALID_TOKEN; + + if (!signature_buffer) + return SEC_E_INVALID_TOKEN; + + /* Copy original data buffer */ + ULONG length = data_buffer->cbBuffer; + void* data = malloc(length); + + if (!data) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(data, data_buffer->pvBuffer, length); + /* Compute the HMAC-MD5 hash of ConcatenationOf(seq_num,data) using the client signing key */ + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (hmac && + winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->SendSigningKey, WINPR_MD5_DIGEST_LENGTH)) + { + Data_Write_UINT32(&value, SeqNo); + winpr_HMAC_Update(hmac, (void*)&value, 4); + winpr_HMAC_Update(hmac, (void*)data, length); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + } + else + { + winpr_HMAC_Free(hmac); + free(data); + return SEC_E_INSUFFICIENT_MEMORY; + } + + /* Encrypt message using with RC4, result overwrites original buffer */ + if ((data_buffer->BufferType & SECBUFFER_READONLY) == 0) + { + if (context->confidentiality) + winpr_RC4_Update(context->SendRc4Seal, length, (BYTE*)data, + (BYTE*)data_buffer->pvBuffer); + else + CopyMemory(data_buffer->pvBuffer, data, length); + } + +#ifdef WITH_DEBUG_NTLM + WLog_DBG(TAG, "Data Buffer (length = %" PRIuz ")", length); + winpr_HexDump(TAG, WLOG_DEBUG, data, length); + WLog_DBG(TAG, "Encrypted Data Buffer (length = %" PRIu32 ")", data_buffer->cbBuffer); + winpr_HexDump(TAG, WLOG_DEBUG, data_buffer->pvBuffer, data_buffer->cbBuffer); +#endif + free(data); + /* RC4-encrypt first 8 bytes of digest */ + winpr_RC4_Update(context->SendRc4Seal, 8, digest, checksum); + if ((signature_buffer->BufferType & SECBUFFER_READONLY) == 0) + { + BYTE* signature = signature_buffer->pvBuffer; + /* Concatenate version, ciphertext and sequence number to build signature */ + Data_Write_UINT32(signature, version); + CopyMemory(&signature[4], (void*)checksum, 8); + Data_Write_UINT32(&signature[12], SeqNo); + } + context->SendSeqNum++; +#ifdef WITH_DEBUG_NTLM + WLog_DBG(TAG, "Signature (length = %" PRIu32 ")", signature_buffer->cbBuffer); + winpr_HexDump(TAG, WLOG_DEBUG, signature_buffer->pvBuffer, signature_buffer->cbBuffer); +#endif + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_DecryptMessage(PCtxtHandle phContext, PSecBufferDesc pMessage, + ULONG MessageSeqNo, PULONG pfQOP) +{ + const UINT32 SeqNo = (UINT32)MessageSeqNo; + UINT32 value = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + UINT32 version = 1; + BYTE expected_signature[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + PSecBuffer data_buffer = NULL; + PSecBuffer signature_buffer = NULL; + NTLM_CONTEXT* context = (NTLM_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG index = 0; index < pMessage->cBuffers; index++) + { + if (pMessage->pBuffers[index].BufferType == SECBUFFER_DATA) + data_buffer = &pMessage->pBuffers[index]; + else if (pMessage->pBuffers[index].BufferType == SECBUFFER_TOKEN) + signature_buffer = &pMessage->pBuffers[index]; + } + + if (!data_buffer) + return SEC_E_INVALID_TOKEN; + + if (!signature_buffer) + return SEC_E_INVALID_TOKEN; + + /* Copy original data buffer */ + const ULONG length = data_buffer->cbBuffer; + void* data = malloc(length); + + if (!data) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(data, data_buffer->pvBuffer, length); + + /* Decrypt message using with RC4, result overwrites original buffer */ + + if (context->confidentiality) + winpr_RC4_Update(context->RecvRc4Seal, length, (BYTE*)data, (BYTE*)data_buffer->pvBuffer); + else + CopyMemory(data_buffer->pvBuffer, data, length); + + /* Compute the HMAC-MD5 hash of ConcatenationOf(seq_num,data) using the client signing key */ + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (hmac && + winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->RecvSigningKey, WINPR_MD5_DIGEST_LENGTH)) + { + Data_Write_UINT32(&value, SeqNo); + winpr_HMAC_Update(hmac, (void*)&value, 4); + winpr_HMAC_Update(hmac, (void*)data_buffer->pvBuffer, data_buffer->cbBuffer); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + } + else + { + winpr_HMAC_Free(hmac); + free(data); + return SEC_E_INSUFFICIENT_MEMORY; + } + +#ifdef WITH_DEBUG_NTLM + WLog_DBG(TAG, "Encrypted Data Buffer (length = %" PRIuz ")", length); + winpr_HexDump(TAG, WLOG_DEBUG, data, length); + WLog_DBG(TAG, "Data Buffer (length = %" PRIu32 ")", data_buffer->cbBuffer); + winpr_HexDump(TAG, WLOG_DEBUG, data_buffer->pvBuffer, data_buffer->cbBuffer); +#endif + free(data); + /* RC4-encrypt first 8 bytes of digest */ + winpr_RC4_Update(context->RecvRc4Seal, 8, digest, checksum); + /* Concatenate version, ciphertext and sequence number to build signature */ + Data_Write_UINT32(expected_signature, version); + CopyMemory(&expected_signature[4], (void*)checksum, 8); + Data_Write_UINT32(&expected_signature[12], SeqNo); + context->RecvSeqNum++; + + if (memcmp(signature_buffer->pvBuffer, expected_signature, 16) != 0) + { + /* signature verification failed! */ + WLog_ERR(TAG, "signature verification failed, something nasty is going on!"); +#ifdef WITH_DEBUG_NTLM + WLog_ERR(TAG, "Expected Signature:"); + winpr_HexDump(TAG, WLOG_ERROR, expected_signature, 16); + WLog_ERR(TAG, "Actual Signature:"); + winpr_HexDump(TAG, WLOG_ERROR, (BYTE*)signature_buffer->pvBuffer, 16); +#endif + return SEC_E_MESSAGE_ALTERED; + } + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + PSecBuffer data_buffer = NULL; + PSecBuffer sig_buffer = NULL; + UINT32 seq_no = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + + NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG i = 0; i < pMessage->cBuffers; i++) + { + if (pMessage->pBuffers[i].BufferType == SECBUFFER_DATA) + data_buffer = &pMessage->pBuffers[i]; + else if (pMessage->pBuffers[i].BufferType == SECBUFFER_TOKEN) + sig_buffer = &pMessage->pBuffers[i]; + } + + if (!data_buffer || !sig_buffer) + return SEC_E_INVALID_TOKEN; + + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->SendSigningKey, WINPR_MD5_DIGEST_LENGTH)) + return SEC_E_INTERNAL_ERROR; + + Data_Write_UINT32(&seq_no, MessageSeqNo); + winpr_HMAC_Update(hmac, (BYTE*)&seq_no, 4); + winpr_HMAC_Update(hmac, data_buffer->pvBuffer, data_buffer->cbBuffer); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + + winpr_RC4_Update(context->SendRc4Seal, 8, digest, checksum); + + BYTE* signature = sig_buffer->pvBuffer; + Data_Write_UINT32(signature, 1L); + CopyMemory(&signature[4], checksum, 8); + Data_Write_UINT32(&signature[12], seq_no); + sig_buffer->cbBuffer = 16; + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY ntlm_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, ULONG MessageSeqNo, + PULONG pfQOP) +{ + PSecBuffer data_buffer = NULL; + PSecBuffer sig_buffer = NULL; + UINT32 seq_no = 0; + BYTE digest[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + BYTE checksum[8] = { 0 }; + BYTE signature[16] = { 0 }; + + NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + if (!check_context(context)) + return SEC_E_INVALID_HANDLE; + + for (ULONG i = 0; i < pMessage->cBuffers; i++) + { + if (pMessage->pBuffers[i].BufferType == SECBUFFER_DATA) + data_buffer = &pMessage->pBuffers[i]; + else if (pMessage->pBuffers[i].BufferType == SECBUFFER_TOKEN) + sig_buffer = &pMessage->pBuffers[i]; + } + + if (!data_buffer || !sig_buffer) + return SEC_E_INVALID_TOKEN; + + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + if (!winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->RecvSigningKey, WINPR_MD5_DIGEST_LENGTH)) + return SEC_E_INTERNAL_ERROR; + + Data_Write_UINT32(&seq_no, MessageSeqNo); + winpr_HMAC_Update(hmac, (BYTE*)&seq_no, 4); + winpr_HMAC_Update(hmac, data_buffer->pvBuffer, data_buffer->cbBuffer); + winpr_HMAC_Final(hmac, digest, WINPR_MD5_DIGEST_LENGTH); + winpr_HMAC_Free(hmac); + + winpr_RC4_Update(context->RecvRc4Seal, 8, digest, checksum); + + Data_Write_UINT32(signature, 1L); + CopyMemory(&signature[4], checksum, 8); + Data_Write_UINT32(&signature[12], seq_no); + + if (memcmp(sig_buffer->pvBuffer, signature, 16) != 0) + return SEC_E_MESSAGE_ALTERED; + + return SEC_E_OK; +} + +const SecurityFunctionTableA NTLM_SecurityFunctionTableA = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + ntlm_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + ntlm_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + ntlm_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + ntlm_InitializeSecurityContextA, /* InitializeSecurityContext */ + ntlm_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + ntlm_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + ntlm_QueryContextAttributesA, /* QueryContextAttributes */ + ntlm_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + ntlm_RevertSecurityContext, /* RevertSecurityContext */ + ntlm_MakeSignature, /* MakeSignature */ + ntlm_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + ntlm_EncryptMessage, /* EncryptMessage */ + ntlm_DecryptMessage, /* DecryptMessage */ + ntlm_SetContextAttributesA, /* SetContextAttributes */ + ntlm_SetCredentialsAttributesA, /* SetCredentialsAttributes */ +}; + +const SecurityFunctionTableW NTLM_SecurityFunctionTableW = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + ntlm_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + ntlm_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + ntlm_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + ntlm_InitializeSecurityContextW, /* InitializeSecurityContext */ + ntlm_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + ntlm_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + ntlm_QueryContextAttributesW, /* QueryContextAttributes */ + ntlm_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + ntlm_RevertSecurityContext, /* RevertSecurityContext */ + ntlm_MakeSignature, /* MakeSignature */ + ntlm_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + ntlm_EncryptMessage, /* EncryptMessage */ + ntlm_DecryptMessage, /* DecryptMessage */ + ntlm_SetContextAttributesW, /* SetContextAttributes */ + ntlm_SetCredentialsAttributesW, /* SetCredentialsAttributes */ +}; + +const SecPkgInfoA NTLM_SecPkgInfoA = { + 0x00082B37, /* fCapabilities */ + 1, /* wVersion */ + 0x000A, /* wRPCID */ + 0x00000B48, /* cbMaxToken */ + "NTLM", /* Name */ + "NTLM Security Package" /* Comment */ +}; + +static WCHAR NTLM_SecPkgInfoW_NameBuffer[32] = { 0 }; +static WCHAR NTLM_SecPkgInfoW_CommentBuffer[32] = { 0 }; + +const SecPkgInfoW NTLM_SecPkgInfoW = { + 0x00082B37, /* fCapabilities */ + 1, /* wVersion */ + 0x000A, /* wRPCID */ + 0x00000B48, /* cbMaxToken */ + NTLM_SecPkgInfoW_NameBuffer, /* Name */ + NTLM_SecPkgInfoW_CommentBuffer /* Comment */ +}; + +char* ntlm_negotiate_flags_string(char* buffer, size_t size, UINT32 flags) +{ + if (!buffer || (size == 0)) + return buffer; + + _snprintf(buffer, size, "[0x%08" PRIx32 "] ", flags); + + for (int x = 0; x < 31; x++) + { + const UINT32 mask = 1 << x; + size_t len = strnlen(buffer, size); + if (flags & mask) + { + const char* str = ntlm_get_negotiate_string(mask); + const size_t flen = strlen(str); + + if ((len > 0) && (buffer[len - 1] != ' ')) + { + if (size - len < 1) + break; + winpr_str_append("|", buffer, size, NULL); + len++; + } + + if (size - len < flen) + break; + winpr_str_append(str, buffer, size, NULL); + } + } + + return buffer; +} + +const char* ntlm_message_type_string(UINT32 messageType) +{ + switch (messageType) + { + case MESSAGE_TYPE_NEGOTIATE: + return "MESSAGE_TYPE_NEGOTIATE"; + case MESSAGE_TYPE_CHALLENGE: + return "MESSAGE_TYPE_CHALLENGE"; + case MESSAGE_TYPE_AUTHENTICATE: + return "MESSAGE_TYPE_AUTHENTICATE"; + default: + return "MESSAGE_TYPE_UNKNOWN"; + } +} + +const char* ntlm_state_string(NTLM_STATE state) +{ + switch (state) + { + case NTLM_STATE_INITIAL: + return "NTLM_STATE_INITIAL"; + case NTLM_STATE_NEGOTIATE: + return "NTLM_STATE_NEGOTIATE"; + case NTLM_STATE_CHALLENGE: + return "NTLM_STATE_CHALLENGE"; + case NTLM_STATE_AUTHENTICATE: + return "NTLM_STATE_AUTHENTICATE"; + case NTLM_STATE_FINAL: + return "NTLM_STATE_FINAL"; + default: + return "NTLM_STATE_UNKNOWN"; + } +} +void ntlm_change_state(NTLM_CONTEXT* ntlm, NTLM_STATE state) +{ + WINPR_ASSERT(ntlm); + WLog_DBG(TAG, "change state from %s to %s", ntlm_state_string(ntlm->state), + ntlm_state_string(state)); + ntlm->state = state; +} + +NTLM_STATE ntlm_get_state(NTLM_CONTEXT* ntlm) +{ + WINPR_ASSERT(ntlm); + return ntlm->state; +} + +BOOL ntlm_reset_cipher_state(PSecHandle phContext) +{ + NTLM_CONTEXT* context = sspi_SecureHandleGetLowerPointer(phContext); + + if (context) + { + check_context(context); + winpr_RC4_Free(context->SendRc4Seal); + winpr_RC4_Free(context->RecvRc4Seal); + context->SendRc4Seal = winpr_RC4_New(context->RecvSealingKey, 16); + context->RecvRc4Seal = winpr_RC4_New(context->SendSealingKey, 16); + + if (!context->SendRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal"); + return FALSE; + } + if (!context->RecvRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal"); + return FALSE; + } + } + + return TRUE; +} + +BOOL NTLM_init(void) +{ + InitializeConstWCharFromUtf8(NTLM_SecPkgInfoA.Name, NTLM_SecPkgInfoW_NameBuffer, + ARRAYSIZE(NTLM_SecPkgInfoW_NameBuffer)); + InitializeConstWCharFromUtf8(NTLM_SecPkgInfoA.Comment, NTLM_SecPkgInfoW_CommentBuffer, + ARRAYSIZE(NTLM_SecPkgInfoW_CommentBuffer)); + + return TRUE; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm.h b/winpr/libwinpr/sspi/NTLM/ntlm.h new file mode 100644 index 0000000..4eac436 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm.h @@ -0,0 +1,301 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_NTLM_PRIVATE_H +#define WINPR_SSPI_NTLM_PRIVATE_H + +#include <winpr/sspi.h> +#include <winpr/windows.h> + +#include <winpr/nt.h> +#include <winpr/crypto.h> + +#include "../sspi.h" + +#define MESSAGE_TYPE_NEGOTIATE 1 +#define MESSAGE_TYPE_CHALLENGE 2 +#define MESSAGE_TYPE_AUTHENTICATE 3 + +#define NTLMSSP_NEGOTIATE_56 0x80000000 /* W (0) */ +#define NTLMSSP_NEGOTIATE_KEY_EXCH 0x40000000 /* V (1) */ +#define NTLMSSP_NEGOTIATE_128 0x20000000 /* U (2) */ +#define NTLMSSP_RESERVED1 0x10000000 /* r1 (3) */ +#define NTLMSSP_RESERVED2 0x08000000 /* r2 (4) */ +#define NTLMSSP_RESERVED3 0x04000000 /* r3 (5) */ +#define NTLMSSP_NEGOTIATE_VERSION 0x02000000 /* T (6) */ +#define NTLMSSP_RESERVED4 0x01000000 /* r4 (7) */ +#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000 /* S (8) */ +#define NTLMSSP_REQUEST_NON_NT_SESSION_KEY 0x00400000 /* R (9) */ +#define NTLMSSP_RESERVED5 0x00200000 /* r5 (10) */ +#define NTLMSSP_NEGOTIATE_IDENTIFY 0x00100000 /* Q (11) */ +#define NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY 0x00080000 /* P (12) */ +#define NTLMSSP_RESERVED6 0x00040000 /* r6 (13) */ +#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000 /* O (14) */ +#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000 /* N (15) */ +#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000 /* M (16) */ +#define NTLMSSP_RESERVED7 0x00004000 /* r7 (17) */ +#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000 /* L (18) */ +#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000 /* K (19) */ +#define NTLMSSP_NEGOTIATE_ANONYMOUS 0x00000800 /* J (20) */ +#define NTLMSSP_RESERVED8 0x00000400 /* r8 (21) */ +#define NTLMSSP_NEGOTIATE_NTLM 0x00000200 /* H (22) */ +#define NTLMSSP_RESERVED9 0x00000100 /* r9 (23) */ +#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080 /* G (24) */ +#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040 /* F (25) */ +#define NTLMSSP_NEGOTIATE_SEAL 0x00000020 /* E (26) */ +#define NTLMSSP_NEGOTIATE_SIGN 0x00000010 /* D (27) */ +#define NTLMSSP_RESERVED10 0x00000008 /* r10 (28) */ +#define NTLMSSP_REQUEST_TARGET 0x00000004 /* C (29) */ +#define NTLMSSP_NEGOTIATE_OEM 0x00000002 /* B (30) */ +#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001 /* A (31) */ + +typedef enum +{ + NTLM_STATE_INITIAL, + NTLM_STATE_NEGOTIATE, + NTLM_STATE_CHALLENGE, + NTLM_STATE_AUTHENTICATE, + NTLM_STATE_FINAL +} NTLM_STATE; + +#ifdef __MINGW32__ +typedef MSV1_0_AVID NTLM_AV_ID; + +#if __MINGW64_VERSION_MAJOR < 9 +enum +{ + MsvAvTimestamp = MsvAvFlags + 1, + MsvAvRestrictions, + MsvAvTargetName, + MsvAvChannelBindings, + MsvAvSingleHost = MsvAvRestrictions +}; + +#else +#ifndef MsvAvSingleHost +#define MsvAvSingleHost MsvAvRestrictions +#endif +#endif +#else +typedef enum +{ + MsvAvEOL, + MsvAvNbComputerName, + MsvAvNbDomainName, + MsvAvDnsComputerName, + MsvAvDnsDomainName, + MsvAvDnsTreeName, + MsvAvFlags, + MsvAvTimestamp, + MsvAvSingleHost, + MsvAvTargetName, + MsvAvChannelBindings +} NTLM_AV_ID; +#endif /* __MINGW32__ */ + +typedef struct +{ + UINT16 AvId; + UINT16 AvLen; +} NTLM_AV_PAIR; + +#define MSV_AV_FLAGS_AUTHENTICATION_CONSTRAINED 0x00000001 +#define MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK 0x00000002 +#define MSV_AV_FLAGS_TARGET_SPN_UNTRUSTED_SOURCE 0x00000004 + +#define WINDOWS_MAJOR_VERSION_5 0x05 +#define WINDOWS_MAJOR_VERSION_6 0x06 +#define WINDOWS_MINOR_VERSION_0 0x00 +#define WINDOWS_MINOR_VERSION_1 0x01 +#define WINDOWS_MINOR_VERSION_2 0x02 +#define NTLMSSP_REVISION_W2K3 0x0F + +typedef struct +{ + UINT8 ProductMajorVersion; + UINT8 ProductMinorVersion; + UINT16 ProductBuild; + BYTE Reserved[3]; + UINT8 NTLMRevisionCurrent; +} NTLM_VERSION_INFO; + +typedef struct +{ + UINT32 Size; + UINT32 Z4; + UINT32 DataPresent; + UINT32 CustomData; + BYTE MachineID[32]; +} NTLM_SINGLE_HOST_DATA; + +typedef struct +{ + BYTE Response[24]; +} NTLM_RESPONSE; + +typedef struct +{ + UINT8 RespType; + UINT8 HiRespType; + UINT16 Reserved1; + UINT32 Reserved2; + BYTE Timestamp[8]; + BYTE ClientChallenge[8]; + UINT32 Reserved3; + NTLM_AV_PAIR* AvPairs; + UINT32 cbAvPairs; +} NTLMv2_CLIENT_CHALLENGE; + +typedef struct +{ + BYTE Response[16]; + NTLMv2_CLIENT_CHALLENGE Challenge; +} NTLMv2_RESPONSE; + +typedef struct +{ + UINT16 Len; + UINT16 MaxLen; + PBYTE Buffer; + UINT32 BufferOffset; +} NTLM_MESSAGE_FIELDS; + +typedef struct +{ + BYTE Signature[8]; + UINT32 MessageType; +} NTLM_MESSAGE_HEADER; + +typedef struct +{ + NTLM_MESSAGE_HEADER header; + UINT32 NegotiateFlags; + NTLM_VERSION_INFO Version; + NTLM_MESSAGE_FIELDS DomainName; + NTLM_MESSAGE_FIELDS Workstation; +} NTLM_NEGOTIATE_MESSAGE; + +typedef struct +{ + NTLM_MESSAGE_HEADER header; + UINT32 NegotiateFlags; + BYTE ServerChallenge[8]; + BYTE Reserved[8]; + NTLM_VERSION_INFO Version; + NTLM_MESSAGE_FIELDS TargetName; + NTLM_MESSAGE_FIELDS TargetInfo; +} NTLM_CHALLENGE_MESSAGE; + +typedef struct +{ + NTLM_MESSAGE_HEADER header; + UINT32 NegotiateFlags; + NTLM_VERSION_INFO Version; + NTLM_MESSAGE_FIELDS DomainName; + NTLM_MESSAGE_FIELDS UserName; + NTLM_MESSAGE_FIELDS Workstation; + NTLM_MESSAGE_FIELDS LmChallengeResponse; + NTLM_MESSAGE_FIELDS NtChallengeResponse; + NTLM_MESSAGE_FIELDS EncryptedRandomSessionKey; + BYTE MessageIntegrityCheck[16]; +} NTLM_AUTHENTICATE_MESSAGE; + +typedef struct +{ + BOOL server; + BOOL NTLMv2; + BOOL UseMIC; + NTLM_STATE state; + int SendSeqNum; + int RecvSeqNum; + char* SamFile; + BYTE NtlmHash[16]; + BYTE NtlmV2Hash[16]; + BYTE MachineID[32]; + BOOL SendVersionInfo; + BOOL confidentiality; + WINPR_RC4_CTX* SendRc4Seal; + WINPR_RC4_CTX* RecvRc4Seal; + BYTE* SendSigningKey; + BYTE* RecvSigningKey; + BYTE* SendSealingKey; + BYTE* RecvSealingKey; + UINT32 NegotiateFlags; + BOOL UseSamFileDatabase; + int LmCompatibilityLevel; + int SuppressExtendedProtection; + BOOL SendWorkstationName; + UNICODE_STRING Workstation; + UNICODE_STRING ServicePrincipalName; + SSPI_CREDENTIALS* credentials; + BYTE* ChannelBindingToken; + BYTE ChannelBindingsHash[16]; + SecPkgContext_Bindings Bindings; + BOOL SendSingleHostData; + BOOL NegotiateKeyExchange; + NTLM_SINGLE_HOST_DATA SingleHostData; + NTLM_NEGOTIATE_MESSAGE NEGOTIATE_MESSAGE; + NTLM_CHALLENGE_MESSAGE CHALLENGE_MESSAGE; + NTLM_AUTHENTICATE_MESSAGE AUTHENTICATE_MESSAGE; + size_t MessageIntegrityCheckOffset; + SecBuffer NegotiateMessage; + SecBuffer ChallengeMessage; + SecBuffer AuthenticateMessage; + SecBuffer ChallengeTargetInfo; + SecBuffer AuthenticateTargetInfo; + SecBuffer TargetName; + SecBuffer NtChallengeResponse; + SecBuffer LmChallengeResponse; + NTLMv2_RESPONSE NTLMv2Response; + BYTE NtProofString[16]; + BYTE Timestamp[8]; + BYTE ChallengeTimestamp[8]; + BYTE ServerChallenge[8]; + BYTE ClientChallenge[8]; + BYTE SessionBaseKey[16]; + BYTE KeyExchangeKey[16]; + BYTE RandomSessionKey[16]; + BYTE ExportedSessionKey[16]; + BYTE EncryptedRandomSessionKey[16]; + BYTE ClientSigningKey[16]; + BYTE ClientSealingKey[16]; + BYTE ServerSigningKey[16]; + BYTE ServerSealingKey[16]; + psSspiNtlmHashCallback HashCallback; + void* HashCallbackArg; +} NTLM_CONTEXT; + +char* ntlm_negotiate_flags_string(char* buffer, size_t size, UINT32 flags); +const char* ntlm_message_type_string(UINT32 messageType); + +const char* ntlm_state_string(NTLM_STATE state); +void ntlm_change_state(NTLM_CONTEXT* ntlm, NTLM_STATE state); +NTLM_STATE ntlm_get_state(NTLM_CONTEXT* ntlm); +BOOL ntlm_reset_cipher_state(PSecHandle phContext); + +SECURITY_STATUS ntlm_computeProofValue(NTLM_CONTEXT* ntlm, SecBuffer* ntproof); +SECURITY_STATUS ntlm_computeMicValue(NTLM_CONTEXT* ntlm, SecBuffer* micvalue); + +#ifdef WITH_DEBUG_NLA +#define WITH_DEBUG_NTLM +#endif + +BOOL NTLM_init(void); + +#endif /* WINPR_SSPI_NTLM_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c new file mode 100644 index 0000000..881a743 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.c @@ -0,0 +1,775 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (AV_PAIRs) + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/assert.h> + +#include "ntlm.h" +#include "../sspi.h" + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/sysinfo.h> +#include <winpr/tchar.h> +#include <winpr/crypto.h> + +#include "ntlm_compute.h" + +#include "ntlm_av_pairs.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +static BOOL ntlm_av_pair_get_next_offset(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pOffset); + +static BOOL ntlm_av_pair_check_data(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair, size_t size) +{ + size_t offset = 0; + if (!pAvPair || cbAvPair < sizeof(NTLM_AV_PAIR) + size) + return FALSE; + if (!ntlm_av_pair_get_next_offset(pAvPair, cbAvPair, &offset)) + return FALSE; + return cbAvPair >= offset; +} + +static const char* get_av_pair_string(UINT16 pair) +{ + switch (pair) + { + case MsvAvEOL: + return "MsvAvEOL"; + case MsvAvNbComputerName: + return "MsvAvNbComputerName"; + case MsvAvNbDomainName: + return "MsvAvNbDomainName"; + case MsvAvDnsComputerName: + return "MsvAvDnsComputerName"; + case MsvAvDnsDomainName: + return "MsvAvDnsDomainName"; + case MsvAvDnsTreeName: + return "MsvAvDnsTreeName"; + case MsvAvFlags: + return "MsvAvFlags"; + case MsvAvTimestamp: + return "MsvAvTimestamp"; + case MsvAvSingleHost: + return "MsvAvSingleHost"; + case MsvAvTargetName: + return "MsvAvTargetName"; + case MsvAvChannelBindings: + return "MsvAvChannelBindings"; + default: + return "UNKNOWN"; + } +} + +static BOOL ntlm_av_pair_check(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair); +static NTLM_AV_PAIR* ntlm_av_pair_next(NTLM_AV_PAIR* pAvPairList, size_t* pcbAvPairList); + +static INLINE void ntlm_av_pair_set_id(NTLM_AV_PAIR* pAvPair, UINT16 id) +{ + WINPR_ASSERT(pAvPair); + Data_Write_UINT16(&pAvPair->AvId, id); +} + +static INLINE void ntlm_av_pair_set_len(NTLM_AV_PAIR* pAvPair, UINT16 len) +{ + WINPR_ASSERT(pAvPair); + Data_Write_UINT16(&pAvPair->AvLen, len); +} + +static BOOL ntlm_av_pair_list_init(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList) +{ + NTLM_AV_PAIR* pAvPair = pAvPairList; + + if (!pAvPair || (cbAvPairList < sizeof(NTLM_AV_PAIR))) + return FALSE; + + ntlm_av_pair_set_id(pAvPair, MsvAvEOL); + ntlm_av_pair_set_len(pAvPair, 0); + return TRUE; +} + +static INLINE BOOL ntlm_av_pair_get_id(const NTLM_AV_PAIR* pAvPair, size_t size, UINT16* pair) +{ + UINT16 AvId = 0; + if (!pAvPair || !pair) + return FALSE; + + if (size < sizeof(NTLM_AV_PAIR)) + return FALSE; + + Data_Read_UINT16(&pAvPair->AvId, AvId); + + *pair = AvId; + return TRUE; +} + +ULONG ntlm_av_pair_list_length(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList) +{ + size_t size = 0; + size_t cbAvPair = 0; + NTLM_AV_PAIR* pAvPair = NULL; + + pAvPair = ntlm_av_pair_get(pAvPairList, cbAvPairList, MsvAvEOL, &cbAvPair); + if (!pAvPair) + return 0; + + size = ((PBYTE)pAvPair - (PBYTE)pAvPairList) + sizeof(NTLM_AV_PAIR); + WINPR_ASSERT(size <= ULONG_MAX); + return (ULONG)size; +} + +static INLINE BOOL ntlm_av_pair_get_len(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pAvLen) +{ + UINT16 AvLen = 0; + if (!pAvPair) + return FALSE; + + if (size < sizeof(NTLM_AV_PAIR)) + return FALSE; + + Data_Read_UINT16(&pAvPair->AvLen, AvLen); + + *pAvLen = AvLen; + return TRUE; +} + +#ifdef WITH_DEBUG_NTLM +void ntlm_print_av_pair_list(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList) +{ + UINT16 pair = 0; + size_t cbAvPair = cbAvPairList; + NTLM_AV_PAIR* pAvPair = pAvPairList; + + if (!ntlm_av_pair_check(pAvPair, cbAvPair)) + return; + + WLog_VRB(TAG, "AV_PAIRs ="); + + while (pAvPair && ntlm_av_pair_get_id(pAvPair, cbAvPair, &pair) && (pair != MsvAvEOL)) + { + size_t cbLen = 0; + ntlm_av_pair_get_len(pAvPair, cbAvPair, &cbLen); + + WLog_VRB(TAG, "\t%s AvId: %" PRIu16 " AvLen: %" PRIu16 "", get_av_pair_string(pair), pair); + winpr_HexDump(TAG, WLOG_TRACE, ntlm_av_pair_get_value_pointer(pAvPair), cbLen); + + pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair); + } +} +#endif + +static ULONG ntlm_av_pair_list_size(ULONG AvPairsCount, ULONG AvPairsValueLength) +{ + /* size of headers + value lengths + terminating MsvAvEOL AV_PAIR */ + return ((AvPairsCount + 1) * 4) + AvPairsValueLength; +} + +PBYTE ntlm_av_pair_get_value_pointer(NTLM_AV_PAIR* pAvPair) +{ + WINPR_ASSERT(pAvPair); + return (PBYTE)pAvPair + sizeof(NTLM_AV_PAIR); +} + +static BOOL ntlm_av_pair_get_next_offset(const NTLM_AV_PAIR* pAvPair, size_t size, size_t* pOffset) +{ + size_t avLen = 0; + if (!pOffset) + return FALSE; + + if (!ntlm_av_pair_get_len(pAvPair, size, &avLen)) + return FALSE; + *pOffset = avLen + sizeof(NTLM_AV_PAIR); + return TRUE; +} + +static BOOL ntlm_av_pair_check(const NTLM_AV_PAIR* pAvPair, size_t cbAvPair) +{ + return ntlm_av_pair_check_data(pAvPair, cbAvPair, 0); +} + +static NTLM_AV_PAIR* ntlm_av_pair_next(NTLM_AV_PAIR* pAvPair, size_t* pcbAvPair) +{ + size_t offset = 0; + + if (!pcbAvPair) + return NULL; + if (!ntlm_av_pair_check(pAvPair, *pcbAvPair)) + return NULL; + + if (!ntlm_av_pair_get_next_offset(pAvPair, *pcbAvPair, &offset)) + return NULL; + + *pcbAvPair -= offset; + return (NTLM_AV_PAIR*)((PBYTE)pAvPair + offset); +} + +NTLM_AV_PAIR* ntlm_av_pair_get(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId, + size_t* pcbAvPairListRemaining) +{ + UINT16 id = 0; + size_t cbAvPair = cbAvPairList; + NTLM_AV_PAIR* pAvPair = pAvPairList; + + if (!ntlm_av_pair_check(pAvPair, cbAvPair)) + pAvPair = NULL; + + while (pAvPair && ntlm_av_pair_get_id(pAvPair, cbAvPair, &id)) + { + if (id == AvId) + break; + if (id == MsvAvEOL) + { + pAvPair = NULL; + break; + } + + pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair); + } + + if (!pAvPair) + cbAvPair = 0; + if (pcbAvPairListRemaining) + *pcbAvPairListRemaining = cbAvPair; + + return pAvPair; +} + +static BOOL ntlm_av_pair_add(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId, + PBYTE Value, UINT16 AvLen) +{ + size_t cbAvPair = 0; + NTLM_AV_PAIR* pAvPair = NULL; + + pAvPair = ntlm_av_pair_get(pAvPairList, cbAvPairList, MsvAvEOL, &cbAvPair); + + /* size of header + value length + terminating MsvAvEOL AV_PAIR */ + if (!pAvPair || cbAvPair < 2 * sizeof(NTLM_AV_PAIR) + AvLen) + return FALSE; + + ntlm_av_pair_set_id(pAvPair, (UINT16)AvId); + ntlm_av_pair_set_len(pAvPair, AvLen); + if (AvLen) + { + WINPR_ASSERT(Value != NULL); + CopyMemory(ntlm_av_pair_get_value_pointer(pAvPair), Value, AvLen); + } + + pAvPair = ntlm_av_pair_next(pAvPair, &cbAvPair); + return ntlm_av_pair_list_init(pAvPair, cbAvPair); +} + +static BOOL ntlm_av_pair_add_copy(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, + NTLM_AV_PAIR* pAvPair, size_t cbAvPair) +{ + UINT16 pair = 0; + size_t avLen = 0; + + if (!ntlm_av_pair_check(pAvPair, cbAvPair)) + return FALSE; + + if (!ntlm_av_pair_get_id(pAvPair, cbAvPair, &pair)) + return FALSE; + + if (!ntlm_av_pair_get_len(pAvPair, cbAvPair, &avLen)) + return FALSE; + + WINPR_ASSERT(avLen <= UINT16_MAX); + return ntlm_av_pair_add(pAvPairList, cbAvPairList, pair, + ntlm_av_pair_get_value_pointer(pAvPair), (UINT16)avLen); +} + +static int ntlm_get_target_computer_name(PUNICODE_STRING pName, COMPUTER_NAME_FORMAT type) +{ + char* name = NULL; + int status = -1; + DWORD nSize = 0; + CHAR* computerName = NULL; + + WINPR_ASSERT(pName); + + if (GetComputerNameExA(ComputerNameNetBIOS, NULL, &nSize) || GetLastError() != ERROR_MORE_DATA) + return -1; + + computerName = calloc(nSize, sizeof(CHAR)); + + if (!computerName) + return -1; + + if (!GetComputerNameExA(ComputerNameNetBIOS, computerName, &nSize)) + { + free(computerName); + return -1; + } + + if (nSize > MAX_COMPUTERNAME_LENGTH) + computerName[MAX_COMPUTERNAME_LENGTH] = '\0'; + + name = computerName; + + if (!name) + return -1; + + if (type == ComputerNameNetBIOS) + CharUpperA(name); + + size_t len = 0; + pName->Buffer = ConvertUtf8ToWCharAlloc(name, &len); + + if (!pName->Buffer || (len == 0) || (len > UINT16_MAX / sizeof(WCHAR))) + { + free(pName->Buffer); + pName->Buffer = NULL; + free(name); + return status; + } + + pName->Length = (USHORT)((len) * sizeof(WCHAR)); + pName->MaximumLength = pName->Length; + free(name); + return 1; +} + +static void ntlm_free_unicode_string(PUNICODE_STRING string) +{ + if (string) + { + if (string->Length > 0) + { + free(string->Buffer); + string->Buffer = NULL; + string->Length = 0; + string->MaximumLength = 0; + } + } +} + +/** + * From http://www.ietf.org/proceedings/72/slides/sasl-2.pdf: + * + * tls-server-end-point: + * + * The hash of the TLS server's end entity certificate as it appears, octet for octet, + * in the server's Certificate message (note that the Certificate message contains a + * certificate_list, the first element of which is the server's end entity certificate.) + * The hash function to be selected is as follows: if the certificate's signature hash + * algorithm is either MD5 or SHA-1, then use SHA-256, otherwise use the certificate's + * signature hash algorithm. + */ + +/** + * Channel Bindings sample usage: + * https://raw.github.com/mozilla/mozilla-central/master/extensions/auth/nsAuthSSPI.cpp + */ + +/* +typedef struct gss_channel_bindings_struct { + OM_uint32 initiator_addrtype; + gss_buffer_desc initiator_address; + OM_uint32 acceptor_addrtype; + gss_buffer_desc acceptor_address; + gss_buffer_desc application_data; +} *gss_channel_bindings_t; + */ + +static BOOL ntlm_md5_update_uint32_be(WINPR_DIGEST_CTX* md5, UINT32 num) +{ + BYTE be32[4]; + be32[0] = (num >> 0) & 0xFF; + be32[1] = (num >> 8) & 0xFF; + be32[2] = (num >> 16) & 0xFF; + be32[3] = (num >> 24) & 0xFF; + return winpr_Digest_Update(md5, be32, 4); +} + +static void ntlm_compute_channel_bindings(NTLM_CONTEXT* context) +{ + WINPR_DIGEST_CTX* md5 = NULL; + BYTE* ChannelBindingToken = NULL; + UINT32 ChannelBindingTokenLength = 0; + SEC_CHANNEL_BINDINGS* ChannelBindings = NULL; + + WINPR_ASSERT(context); + + ZeroMemory(context->ChannelBindingsHash, WINPR_MD5_DIGEST_LENGTH); + ChannelBindings = context->Bindings.Bindings; + + if (!ChannelBindings) + return; + + if (!(md5 = winpr_Digest_New())) + return; + + if (!winpr_Digest_Init(md5, WINPR_MD_MD5)) + goto out; + + ChannelBindingTokenLength = context->Bindings.BindingsLength - sizeof(SEC_CHANNEL_BINDINGS); + ChannelBindingToken = &((BYTE*)ChannelBindings)[ChannelBindings->dwApplicationDataOffset]; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->dwInitiatorAddrType)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbInitiatorLength)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->dwAcceptorAddrType)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbAcceptorLength)) + goto out; + + if (!ntlm_md5_update_uint32_be(md5, ChannelBindings->cbApplicationDataLength)) + goto out; + + if (!winpr_Digest_Update(md5, (void*)ChannelBindingToken, ChannelBindingTokenLength)) + goto out; + + if (!winpr_Digest_Final(md5, context->ChannelBindingsHash, WINPR_MD5_DIGEST_LENGTH)) + goto out; + +out: + winpr_Digest_Free(md5); +} + +static void ntlm_compute_single_host_data(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + /** + * The Single_Host_Data structure allows a client to send machine-specific information + * within an authentication exchange to services on the same machine. The client can + * produce additional information to be processed in an implementation-specific way when + * the client and server are on the same host. If the server and client platforms are + * different or if they are on different hosts, then the information MUST be ignored. + * Any fields after the MachineID field MUST be ignored on receipt. + */ + Data_Write_UINT32(&context->SingleHostData.Size, 48); + Data_Write_UINT32(&context->SingleHostData.Z4, 0); + Data_Write_UINT32(&context->SingleHostData.DataPresent, 1); + Data_Write_UINT32(&context->SingleHostData.CustomData, SECURITY_MANDATORY_MEDIUM_RID); + FillMemory(context->SingleHostData.MachineID, 32, 0xAA); +} + +BOOL ntlm_construct_challenge_target_info(NTLM_CONTEXT* context) +{ + BOOL rc = FALSE; + ULONG length = 0; + ULONG AvPairsCount = 0; + ULONG AvPairsLength = 0; + NTLM_AV_PAIR* pAvPairList = NULL; + size_t cbAvPairList = 0; + UNICODE_STRING NbDomainName = { 0 }; + UNICODE_STRING NbComputerName = { 0 }; + UNICODE_STRING DnsDomainName = { 0 }; + UNICODE_STRING DnsComputerName = { 0 }; + + WINPR_ASSERT(context); + + if (ntlm_get_target_computer_name(&NbDomainName, ComputerNameNetBIOS) < 0) + goto fail; + + NbComputerName.Buffer = NULL; + + if (ntlm_get_target_computer_name(&NbComputerName, ComputerNameNetBIOS) < 0) + goto fail; + + DnsDomainName.Buffer = NULL; + + if (ntlm_get_target_computer_name(&DnsDomainName, ComputerNameDnsDomain) < 0) + goto fail; + + DnsComputerName.Buffer = NULL; + + if (ntlm_get_target_computer_name(&DnsComputerName, ComputerNameDnsHostname) < 0) + goto fail; + + AvPairsCount = 5; + AvPairsLength = NbDomainName.Length + NbComputerName.Length + DnsDomainName.Length + + DnsComputerName.Length + 8; + length = ntlm_av_pair_list_size(AvPairsCount, AvPairsLength); + + if (!sspi_SecBufferAlloc(&context->ChallengeTargetInfo, length)) + goto fail; + + pAvPairList = (NTLM_AV_PAIR*)context->ChallengeTargetInfo.pvBuffer; + cbAvPairList = context->ChallengeTargetInfo.cbBuffer; + + if (!ntlm_av_pair_list_init(pAvPairList, cbAvPairList)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvNbDomainName, (PBYTE)NbDomainName.Buffer, + NbDomainName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvNbComputerName, + (PBYTE)NbComputerName.Buffer, NbComputerName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvDnsDomainName, + (PBYTE)DnsDomainName.Buffer, DnsDomainName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvDnsComputerName, + (PBYTE)DnsComputerName.Buffer, DnsComputerName.Length)) + goto fail; + + if (!ntlm_av_pair_add(pAvPairList, cbAvPairList, MsvAvTimestamp, context->Timestamp, + sizeof(context->Timestamp))) + goto fail; + + rc = TRUE; +fail: + ntlm_free_unicode_string(&NbDomainName); + ntlm_free_unicode_string(&NbComputerName); + ntlm_free_unicode_string(&DnsDomainName); + ntlm_free_unicode_string(&DnsComputerName); + return rc; +} + +BOOL ntlm_construct_authenticate_target_info(NTLM_CONTEXT* context) +{ + ULONG size = 0; + ULONG AvPairsCount = 0; + ULONG AvPairsValueLength = 0; + NTLM_AV_PAIR* AvTimestamp = NULL; + NTLM_AV_PAIR* AvNbDomainName = NULL; + NTLM_AV_PAIR* AvNbComputerName = NULL; + NTLM_AV_PAIR* AvDnsDomainName = NULL; + NTLM_AV_PAIR* AvDnsComputerName = NULL; + NTLM_AV_PAIR* AvDnsTreeName = NULL; + NTLM_AV_PAIR* ChallengeTargetInfo = NULL; + NTLM_AV_PAIR* AuthenticateTargetInfo = NULL; + size_t cbAvTimestamp = 0; + size_t cbAvNbDomainName = 0; + size_t cbAvNbComputerName = 0; + size_t cbAvDnsDomainName = 0; + size_t cbAvDnsComputerName = 0; + size_t cbAvDnsTreeName = 0; + size_t cbChallengeTargetInfo = 0; + size_t cbAuthenticateTargetInfo = 0; + + WINPR_ASSERT(context); + + AvPairsCount = 1; + AvPairsValueLength = 0; + ChallengeTargetInfo = (NTLM_AV_PAIR*)context->ChallengeTargetInfo.pvBuffer; + cbChallengeTargetInfo = context->ChallengeTargetInfo.cbBuffer; + AvNbDomainName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvNbDomainName, + &cbAvNbDomainName); + AvNbComputerName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, + MsvAvNbComputerName, &cbAvNbComputerName); + AvDnsDomainName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, + MsvAvDnsDomainName, &cbAvDnsDomainName); + AvDnsComputerName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, + MsvAvDnsComputerName, &cbAvDnsComputerName); + AvDnsTreeName = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvDnsTreeName, + &cbAvDnsTreeName); + AvTimestamp = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvTimestamp, + &cbAvTimestamp); + + if (AvNbDomainName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvNbDomainName, cbAvNbDomainName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvNbDomainName */ + AvPairsValueLength += avLen; + } + + if (AvNbComputerName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvNbComputerName, cbAvNbComputerName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvNbComputerName */ + AvPairsValueLength += avLen; + } + + if (AvDnsDomainName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvDnsDomainName, cbAvDnsDomainName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvDnsDomainName */ + AvPairsValueLength += avLen; + } + + if (AvDnsComputerName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvDnsComputerName, cbAvDnsComputerName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvDnsComputerName */ + AvPairsValueLength += avLen; + } + + if (AvDnsTreeName) + { + size_t avLen = 0; + if (!ntlm_av_pair_get_len(AvDnsTreeName, cbAvDnsTreeName, &avLen)) + goto fail; + AvPairsCount++; /* MsvAvDnsTreeName */ + AvPairsValueLength += avLen; + } + + AvPairsCount++; /* MsvAvTimestamp */ + AvPairsValueLength += 8; + + if (context->UseMIC) + { + AvPairsCount++; /* MsvAvFlags */ + AvPairsValueLength += 4; + } + + if (context->SendSingleHostData) + { + AvPairsCount++; /* MsvAvSingleHost */ + ntlm_compute_single_host_data(context); + AvPairsValueLength += context->SingleHostData.Size; + } + + /** + * Extended Protection for Authentication: + * http://blogs.technet.com/b/srd/archive/2009/12/08/extended-protection-for-authentication.aspx + */ + + if (!context->SuppressExtendedProtection) + { + /** + * SEC_CHANNEL_BINDINGS structure + * http://msdn.microsoft.com/en-us/library/windows/desktop/dd919963/ + */ + AvPairsCount++; /* MsvAvChannelBindings */ + AvPairsValueLength += 16; + ntlm_compute_channel_bindings(context); + + if (context->ServicePrincipalName.Length > 0) + { + AvPairsCount++; /* MsvAvTargetName */ + AvPairsValueLength += context->ServicePrincipalName.Length; + } + } + + size = ntlm_av_pair_list_size(AvPairsCount, AvPairsValueLength); + + if (context->NTLMv2) + size += 8; /* unknown 8-byte padding */ + + if (!sspi_SecBufferAlloc(&context->AuthenticateTargetInfo, size)) + goto fail; + + AuthenticateTargetInfo = (NTLM_AV_PAIR*)context->AuthenticateTargetInfo.pvBuffer; + cbAuthenticateTargetInfo = context->AuthenticateTargetInfo.cbBuffer; + + if (!ntlm_av_pair_list_init(AuthenticateTargetInfo, cbAuthenticateTargetInfo)) + goto fail; + + if (AvNbDomainName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvNbDomainName, + cbAvNbDomainName)) + goto fail; + } + + if (AvNbComputerName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + AvNbComputerName, cbAvNbComputerName)) + goto fail; + } + + if (AvDnsDomainName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + AvDnsDomainName, cbAvDnsDomainName)) + goto fail; + } + + if (AvDnsComputerName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + AvDnsComputerName, cbAvDnsComputerName)) + goto fail; + } + + if (AvDnsTreeName) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvDnsTreeName, + cbAvDnsTreeName)) + goto fail; + } + + if (AvTimestamp) + { + if (!ntlm_av_pair_add_copy(AuthenticateTargetInfo, cbAuthenticateTargetInfo, AvTimestamp, + cbAvTimestamp)) + goto fail; + } + + if (context->UseMIC) + { + UINT32 flags = 0; + Data_Write_UINT32(&flags, MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK); + + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvFlags, + (PBYTE)&flags, 4)) + goto fail; + } + + if (context->SendSingleHostData) + { + WINPR_ASSERT(context->SingleHostData.Size <= UINT16_MAX); + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvSingleHost, + (PBYTE)&context->SingleHostData, + (UINT16)context->SingleHostData.Size)) + goto fail; + } + + if (!context->SuppressExtendedProtection) + { + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, + MsvAvChannelBindings, context->ChannelBindingsHash, 16)) + goto fail; + + if (context->ServicePrincipalName.Length > 0) + { + if (!ntlm_av_pair_add(AuthenticateTargetInfo, cbAuthenticateTargetInfo, MsvAvTargetName, + (PBYTE)context->ServicePrincipalName.Buffer, + context->ServicePrincipalName.Length)) + goto fail; + } + } + + if (context->NTLMv2) + { + NTLM_AV_PAIR* AvEOL = NULL; + AvEOL = ntlm_av_pair_get(ChallengeTargetInfo, cbChallengeTargetInfo, MsvAvEOL, NULL); + + if (!AvEOL) + goto fail; + + ZeroMemory(AvEOL, sizeof(NTLM_AV_PAIR)); + } + + return TRUE; +fail: + sspi_SecBufferFree(&context->AuthenticateTargetInfo); + return FALSE; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h new file mode 100644 index 0000000..ab9da43 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_av_pairs.h @@ -0,0 +1,42 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (AV_PAIRs) + * + * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_NTLM_AV_PAIRS_H +#define WINPR_SSPI_NTLM_AV_PAIRS_H + +#include <winpr/config.h> + +#include "ntlm.h" + +#include <winpr/stream.h> + +ULONG ntlm_av_pair_list_length(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList); + +#ifdef WITH_DEBUG_NTLM +void ntlm_print_av_pair_list(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList); +#endif + +PBYTE ntlm_av_pair_get_value_pointer(NTLM_AV_PAIR* pAvPair); +NTLM_AV_PAIR* ntlm_av_pair_get(NTLM_AV_PAIR* pAvPairList, size_t cbAvPairList, NTLM_AV_ID AvId, + size_t* pcbAvPairListRemaining); + +BOOL ntlm_construct_challenge_target_info(NTLM_CONTEXT* context); +BOOL ntlm_construct_authenticate_target_info(NTLM_CONTEXT* context); + +#endif /* WINPR_SSPI_NTLM_AV_PAIRS_H */ diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_compute.c b/winpr/libwinpr/sspi/NTLM/ntlm_compute.c new file mode 100644 index 0000000..9c6e818 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_compute.c @@ -0,0 +1,887 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (Compute) + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/assert.h> + +#include "ntlm.h" +#include "../sspi.h" + +#include <winpr/crt.h> +#include <winpr/sam.h> +#include <winpr/ntlm.h> +#include <winpr/print.h> +#include <winpr/crypto.h> +#include <winpr/sysinfo.h> + +#include "ntlm_compute.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \ + Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \ + __func__, __FILE__, (size_t)__LINE__) + +static char NTLM_CLIENT_SIGN_MAGIC[] = "session key to client-to-server signing key magic constant"; +static char NTLM_SERVER_SIGN_MAGIC[] = "session key to server-to-client signing key magic constant"; +static char NTLM_CLIENT_SEAL_MAGIC[] = "session key to client-to-server sealing key magic constant"; +static char NTLM_SERVER_SEAL_MAGIC[] = "session key to server-to-client sealing key magic constant"; + +static const BYTE NTLM_NULL_BUFFER[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/** + * Populate VERSION structure msdn{cc236654} + * @param versionInfo A pointer to the version struct + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(versionInfo); + +#if defined(WITH_WINPR_DEPRECATED) + OSVERSIONINFOA osVersionInfo = { 0 }; + osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); + if (!GetVersionExA(&osVersionInfo)) + return FALSE; + versionInfo->ProductMajorVersion = (UINT8)osVersionInfo.dwMajorVersion; + versionInfo->ProductMinorVersion = (UINT8)osVersionInfo.dwMinorVersion; + versionInfo->ProductBuild = (UINT16)osVersionInfo.dwBuildNumber; +#else + /* Always return fixed version number. + * + * ProductVersion is fixed since windows 10 to Major 10, Minor 0 + * ProductBuild taken from https://en.wikipedia.org/wiki/Windows_11_version_history + * with most recent (pre) release build number + */ + versionInfo->ProductMajorVersion = 10; + versionInfo->ProductMinorVersion = 0; + versionInfo->ProductBuild = 22631; +#endif + ZeroMemory(versionInfo->Reserved, sizeof(versionInfo->Reserved)); + versionInfo->NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; + return TRUE; +} + +/** + * Read VERSION structure. msdn{cc236654} + * @param s A pointer to a stream to read + * @param versionInfo A pointer to the struct to read data to + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(versionInfo); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return FALSE; + + Stream_Read_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */ + Stream_Read_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */ + Stream_Read_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */ + Stream_Read(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */ + Stream_Read_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */ + return TRUE; +} + +/** + * Write VERSION structure. msdn{cc236654} + * @param s A pointer to the stream to write to + * @param versionInfo A pointer to the buffer to read the data from + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(versionInfo); + + if (!Stream_CheckAndLogRequiredCapacityEx( + TAG, WLOG_WARN, s, 5ull + sizeof(versionInfo->Reserved), 1ull, + "%s(%s:%" PRIuz ") NTLM_VERSION_INFO", __func__, __FILE__, (size_t)__LINE__)) + return FALSE; + + Stream_Write_UINT8(s, versionInfo->ProductMajorVersion); /* ProductMajorVersion (1 byte) */ + Stream_Write_UINT8(s, versionInfo->ProductMinorVersion); /* ProductMinorVersion (1 byte) */ + Stream_Write_UINT16(s, versionInfo->ProductBuild); /* ProductBuild (2 bytes) */ + Stream_Write(s, versionInfo->Reserved, sizeof(versionInfo->Reserved)); /* Reserved (3 bytes) */ + Stream_Write_UINT8(s, versionInfo->NTLMRevisionCurrent); /* NTLMRevisionCurrent (1 byte) */ + return TRUE; +} + +/** + * Print VERSION structure. msdn{cc236654} + * @param versionInfo A pointer to the struct containing the data to print + */ +#ifdef WITH_DEBUG_NTLM +void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo) +{ + WINPR_ASSERT(versionInfo); + + WLog_VRB(TAG, "VERSION ={"); + WLog_VRB(TAG, "\tProductMajorVersion: %" PRIu8 "", versionInfo->ProductMajorVersion); + WLog_VRB(TAG, "\tProductMinorVersion: %" PRIu8 "", versionInfo->ProductMinorVersion); + WLog_VRB(TAG, "\tProductBuild: %" PRIu16 "", versionInfo->ProductBuild); + WLog_VRB(TAG, "\tReserved: 0x%02" PRIX8 "%02" PRIX8 "%02" PRIX8 "", versionInfo->Reserved[0], + versionInfo->Reserved[1], versionInfo->Reserved[2]); + WLog_VRB(TAG, "\tNTLMRevisionCurrent: 0x%02" PRIX8 "", versionInfo->NTLMRevisionCurrent); +} +#endif + +static BOOL ntlm_read_ntlm_v2_client_challenge(wStream* s, NTLMv2_CLIENT_CHALLENGE* challenge) +{ + size_t size = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(challenge); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 28)) + return FALSE; + + Stream_Read_UINT8(s, challenge->RespType); + Stream_Read_UINT8(s, challenge->HiRespType); + Stream_Read_UINT16(s, challenge->Reserved1); + Stream_Read_UINT32(s, challenge->Reserved2); + Stream_Read(s, challenge->Timestamp, 8); + Stream_Read(s, challenge->ClientChallenge, 8); + Stream_Read_UINT32(s, challenge->Reserved3); + size = Stream_Length(s) - Stream_GetPosition(s); + + if (size > UINT32_MAX) + { + WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::cbAvPairs too large, got %" PRIuz "bytes", size); + return FALSE; + } + + challenge->cbAvPairs = (UINT32)size; + challenge->AvPairs = (NTLM_AV_PAIR*)malloc(challenge->cbAvPairs); + + if (!challenge->AvPairs) + { + WLog_ERR(TAG, "NTLMv2_CLIENT_CHALLENGE::AvPairs failed to allocate %" PRIu32 "bytes", + challenge->cbAvPairs); + return FALSE; + } + + Stream_Read(s, challenge->AvPairs, size); + return TRUE; +} + +static BOOL ntlm_write_ntlm_v2_client_challenge(wStream* s, + const NTLMv2_CLIENT_CHALLENGE* challenge) +{ + ULONG length = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(challenge); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 28, "NTLMv2_CLIENT_CHALLENGE")) + return FALSE; + + Stream_Write_UINT8(s, challenge->RespType); + Stream_Write_UINT8(s, challenge->HiRespType); + Stream_Write_UINT16(s, challenge->Reserved1); + Stream_Write_UINT32(s, challenge->Reserved2); + Stream_Write(s, challenge->Timestamp, 8); + Stream_Write(s, challenge->ClientChallenge, 8); + Stream_Write_UINT32(s, challenge->Reserved3); + length = ntlm_av_pair_list_length(challenge->AvPairs, challenge->cbAvPairs); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, length)) + return FALSE; + + Stream_Write(s, challenge->AvPairs, length); + return TRUE; +} + +BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(response); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + return FALSE; + + Stream_Read(s, response->Response, 16); + return ntlm_read_ntlm_v2_client_challenge(s, &(response->Challenge)); +} + +BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(response); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16ull, "NTLMv2_RESPONSE")) + return FALSE; + + Stream_Write(s, response->Response, 16); + return ntlm_write_ntlm_v2_client_challenge(s, &(response->Challenge)); +} + +/** + * Get current time, in tenths of microseconds since midnight of January 1, 1601. + * @param[out] timestamp 64-bit little-endian timestamp + */ + +void ntlm_current_time(BYTE* timestamp) +{ + FILETIME filetime = { 0 }; + ULARGE_INTEGER time64 = { 0 }; + + WINPR_ASSERT(timestamp); + + GetSystemTimeAsFileTime(&filetime); + time64.u.LowPart = filetime.dwLowDateTime; + time64.u.HighPart = filetime.dwHighDateTime; + CopyMemory(timestamp, &(time64.QuadPart), 8); +} + +/** + * Generate timestamp for AUTHENTICATE_MESSAGE. + * + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_timestamp(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + if (memcmp(context->ChallengeTimestamp, NTLM_NULL_BUFFER, 8) != 0) + CopyMemory(context->Timestamp, context->ChallengeTimestamp, 8); + else + ntlm_current_time(context->Timestamp); +} + +static BOOL ntlm_fetch_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash) +{ + BOOL rc = FALSE; + WINPR_SAM* sam = NULL; + WINPR_SAM_ENTRY* entry = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(hash); + + credentials = context->credentials; + sam = SamOpen(context->SamFile, TRUE); + + if (!sam) + goto fail; + + entry = SamLookupUserW( + sam, (LPWSTR)credentials->identity.User, credentials->identity.UserLength * sizeof(WCHAR), + (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * sizeof(WCHAR)); + + if (!entry) + { + entry = SamLookupUserW(sam, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * sizeof(WCHAR), NULL, 0); + } + + if (!entry) + goto fail; + +#ifdef WITH_DEBUG_NTLM + WLog_VRB(TAG, "NTLM Hash:"); + winpr_HexDump(TAG, WLOG_DEBUG, entry->NtHash, 16); +#endif + NTOWFv2FromHashW(entry->NtHash, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * sizeof(WCHAR), + (LPWSTR)credentials->identity.Domain, + credentials->identity.DomainLength * sizeof(WCHAR), (BYTE*)hash); + + rc = TRUE; + +fail: + SamFreeEntry(sam, entry); + SamClose(sam); + if (!rc) + WLog_ERR(TAG, "Error: Could not find user in SAM database"); + + return rc; +} + +static int ntlm_convert_password_hash(NTLM_CONTEXT* context, BYTE* hash) +{ + char PasswordHash[32] = { 0 }; + INT64 PasswordHashLength = 0; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(hash); + + credentials = context->credentials; + /* Password contains a password hash of length (PasswordLength - + * SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) */ + PasswordHashLength = credentials->identity.PasswordLength - SSPI_CREDENTIALS_HASH_LENGTH_OFFSET; + + WINPR_ASSERT(PasswordHashLength >= 0); + WINPR_ASSERT((size_t)PasswordHashLength < ARRAYSIZE(PasswordHash)); + if (ConvertWCharNToUtf8(credentials->identity.Password, PasswordHashLength, PasswordHash, + ARRAYSIZE(PasswordHash)) <= 0) + return -1; + + CharUpperBuffA(PasswordHash, (DWORD)PasswordHashLength); + + for (size_t i = 0; i < ARRAYSIZE(PasswordHash); i += 2) + { + BYTE hn = + (BYTE)(PasswordHash[i] > '9' ? PasswordHash[i] - 'A' + 10 : PasswordHash[i] - '0'); + BYTE ln = (BYTE)(PasswordHash[i + 1] > '9' ? PasswordHash[i + 1] - 'A' + 10 + : PasswordHash[i + 1] - '0'); + hash[i / 2] = (BYTE)((hn << 4) | ln); + } + + return 1; +} + +static BOOL ntlm_compute_ntlm_v2_hash(NTLM_CONTEXT* context, BYTE* hash) +{ + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(hash); + + credentials = context->credentials; +#ifdef WITH_DEBUG_NTLM + + if (credentials) + { + WLog_VRB(TAG, "Password (length = %" PRIu32 ")", credentials->identity.PasswordLength * 2); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Password, + credentials->identity.PasswordLength * 2); + WLog_VRB(TAG, "Username (length = %" PRIu32 ")", credentials->identity.UserLength * 2); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.User, + credentials->identity.UserLength * 2); + WLog_VRB(TAG, "Domain (length = %" PRIu32 ")", credentials->identity.DomainLength * 2); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)credentials->identity.Domain, + credentials->identity.DomainLength * 2); + } + else + WLog_VRB(TAG, "Strange, NTLM_CONTEXT is missing valid credentials..."); + + WLog_VRB(TAG, "Workstation (length = %" PRIu16 ")", context->Workstation.Length); + winpr_HexDump(TAG, WLOG_TRACE, (BYTE*)context->Workstation.Buffer, context->Workstation.Length); + WLog_VRB(TAG, "NTOWFv2, NTLMv2 Hash"); + winpr_HexDump(TAG, WLOG_TRACE, context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH); +#endif + + if (memcmp(context->NtlmV2Hash, NTLM_NULL_BUFFER, 16) != 0) + return TRUE; + + if (!credentials) + return FALSE; + else if (memcmp(context->NtlmHash, NTLM_NULL_BUFFER, 16) != 0) + { + NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain, + credentials->identity.DomainLength * 2, (BYTE*)hash); + } + else if (credentials->identity.PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) + { + /* Special case for WinPR: password hash */ + if (ntlm_convert_password_hash(context, context->NtlmHash) < 0) + return FALSE; + + NTOWFv2FromHashW(context->NtlmHash, (LPWSTR)credentials->identity.User, + credentials->identity.UserLength * 2, (LPWSTR)credentials->identity.Domain, + credentials->identity.DomainLength * 2, (BYTE*)hash); + } + else if (credentials->identity.Password) + { + NTOWFv2W((LPWSTR)credentials->identity.Password, credentials->identity.PasswordLength * 2, + (LPWSTR)credentials->identity.User, credentials->identity.UserLength * 2, + (LPWSTR)credentials->identity.Domain, credentials->identity.DomainLength * 2, + (BYTE*)hash); + } + else if (context->HashCallback) + { + int ret = 0; + SecBuffer proofValue; + SecBuffer micValue; + + if (ntlm_computeProofValue(context, &proofValue) != SEC_E_OK) + return FALSE; + + if (ntlm_computeMicValue(context, &micValue) != SEC_E_OK) + { + sspi_SecBufferFree(&proofValue); + return FALSE; + } + + ret = context->HashCallback(context->HashCallbackArg, &credentials->identity, &proofValue, + context->EncryptedRandomSessionKey, + context->AUTHENTICATE_MESSAGE.MessageIntegrityCheck, &micValue, + hash); + sspi_SecBufferFree(&proofValue); + sspi_SecBufferFree(&micValue); + return ret ? TRUE : FALSE; + } + else if (context->UseSamFileDatabase) + { + return ntlm_fetch_ntlm_v2_hash(context, hash); + } + + return TRUE; +} + +BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context) +{ + BYTE* response = NULL; + BYTE value[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + + WINPR_ASSERT(context); + + if (context->LmCompatibilityLevel < 2) + { + if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24)) + return FALSE; + + ZeroMemory(context->LmChallengeResponse.pvBuffer, 24); + return TRUE; + } + + /* Compute the NTLMv2 hash */ + + if (!ntlm_compute_ntlm_v2_hash(context, context->NtlmV2Hash)) + return FALSE; + + /* Concatenate the server and client challenges */ + CopyMemory(value, context->ServerChallenge, 8); + CopyMemory(&value[8], context->ClientChallenge, 8); + + if (!sspi_SecBufferAlloc(&context->LmChallengeResponse, 24)) + return FALSE; + + response = (BYTE*)context->LmChallengeResponse.pvBuffer; + /* Compute the HMAC-MD5 hash of the resulting value using the NTLMv2 hash as the key */ + winpr_HMAC(WINPR_MD_MD5, (void*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, (BYTE*)value, + WINPR_MD5_DIGEST_LENGTH, (BYTE*)response, WINPR_MD5_DIGEST_LENGTH); + /* Concatenate the resulting HMAC-MD5 hash and the client challenge, giving us the LMv2 response + * (24 bytes) */ + CopyMemory(&response[16], context->ClientChallenge, 8); + return TRUE; +} + +/** + * Compute NTLMv2 Response. + * + * NTLMv2_RESPONSE msdn{cc236653} + * NTLMv2 Authentication msdn{cc236700} + * + * @param context A pointer to the NTLM context + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context) +{ + BYTE* blob = NULL; + SecBuffer ntlm_v2_temp = { 0 }; + SecBuffer ntlm_v2_temp_chal = { 0 }; + PSecBuffer TargetInfo = NULL; + + WINPR_ASSERT(context); + + TargetInfo = &context->ChallengeTargetInfo; + BOOL ret = FALSE; + + if (!sspi_SecBufferAlloc(&ntlm_v2_temp, TargetInfo->cbBuffer + 28)) + goto exit; + + ZeroMemory(ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); + blob = (BYTE*)ntlm_v2_temp.pvBuffer; + + /* Compute the NTLMv2 hash */ + if (!ntlm_compute_ntlm_v2_hash(context, (BYTE*)context->NtlmV2Hash)) + goto exit; + + /* Construct temp */ + blob[0] = 1; /* RespType (1 byte) */ + blob[1] = 1; /* HighRespType (1 byte) */ + /* Reserved1 (2 bytes) */ + /* Reserved2 (4 bytes) */ + CopyMemory(&blob[8], context->Timestamp, 8); /* Timestamp (8 bytes) */ + CopyMemory(&blob[16], context->ClientChallenge, 8); /* ClientChallenge (8 bytes) */ + /* Reserved3 (4 bytes) */ + CopyMemory(&blob[28], TargetInfo->pvBuffer, TargetInfo->cbBuffer); +#ifdef WITH_DEBUG_NTLM + WLog_VRB(TAG, "NTLMv2 Response Temp Blob"); + winpr_HexDump(TAG, WLOG_TRACE, ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); +#endif + + /* Concatenate server challenge with temp */ + + if (!sspi_SecBufferAlloc(&ntlm_v2_temp_chal, ntlm_v2_temp.cbBuffer + 8)) + goto exit; + + blob = (BYTE*)ntlm_v2_temp_chal.pvBuffer; + CopyMemory(blob, context->ServerChallenge, 8); + CopyMemory(&blob[8], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); + winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, + (BYTE*)ntlm_v2_temp_chal.pvBuffer, ntlm_v2_temp_chal.cbBuffer, + context->NtProofString, WINPR_MD5_DIGEST_LENGTH); + + /* NtChallengeResponse, Concatenate NTProofStr with temp */ + + if (!sspi_SecBufferAlloc(&context->NtChallengeResponse, ntlm_v2_temp.cbBuffer + 16)) + goto exit; + + blob = (BYTE*)context->NtChallengeResponse.pvBuffer; + CopyMemory(blob, context->NtProofString, WINPR_MD5_DIGEST_LENGTH); + CopyMemory(&blob[16], ntlm_v2_temp.pvBuffer, ntlm_v2_temp.cbBuffer); + /* Compute SessionBaseKey, the HMAC-MD5 hash of NTProofStr using the NTLMv2 hash as the key */ + winpr_HMAC(WINPR_MD_MD5, (BYTE*)context->NtlmV2Hash, WINPR_MD5_DIGEST_LENGTH, + context->NtProofString, WINPR_MD5_DIGEST_LENGTH, context->SessionBaseKey, + WINPR_MD5_DIGEST_LENGTH); + ret = TRUE; +exit: + sspi_SecBufferFree(&ntlm_v2_temp); + sspi_SecBufferFree(&ntlm_v2_temp_chal); + return ret; +} + +/** + * Encrypt the given plain text using RC4 and the given key. + * @param key RC4 key + * @param length text length + * @param plaintext plain text + * @param ciphertext cipher text + */ + +void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext) +{ + WINPR_RC4_CTX* rc4 = winpr_RC4_New(key, 16); + + if (rc4) + { + winpr_RC4_Update(rc4, length, plaintext, ciphertext); + winpr_RC4_Free(rc4); + } +} + +/** + * Generate client challenge (8-byte nonce). + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_client_challenge(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + /* ClientChallenge is used in computation of LMv2 and NTLMv2 responses */ + if (memcmp(context->ClientChallenge, NTLM_NULL_BUFFER, sizeof(context->ClientChallenge)) == 0) + winpr_RAND(context->ClientChallenge, sizeof(context->ClientChallenge)); +} + +/** + * Generate server challenge (8-byte nonce). + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_server_challenge(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + if (memcmp(context->ServerChallenge, NTLM_NULL_BUFFER, sizeof(context->ServerChallenge)) == 0) + winpr_RAND(context->ServerChallenge, sizeof(context->ServerChallenge)); +} + +/** + * Generate KeyExchangeKey (the 128-bit SessionBaseKey). msdn{cc236710} + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(sizeof(context->KeyExchangeKey) == sizeof(context->SessionBaseKey)); + + /* In NTLMv2, KeyExchangeKey is the 128-bit SessionBaseKey */ + CopyMemory(context->KeyExchangeKey, context->SessionBaseKey, sizeof(context->KeyExchangeKey)); +} + +/** + * Generate RandomSessionKey (16-byte nonce). + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_random_session_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + winpr_RAND(context->RandomSessionKey, sizeof(context->RandomSessionKey)); +} + +/** + * Generate ExportedSessionKey (the RandomSessionKey, exported) + * @param context A pointer to the NTLM context + */ + +void ntlm_generate_exported_session_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + CopyMemory(context->ExportedSessionKey, context->RandomSessionKey, + sizeof(context->ExportedSessionKey)); +} + +/** + * Encrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key). + * @param context A pointer to the NTLM context + */ + +void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context) +{ + /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the + * KeyExchangeKey */ + WINPR_ASSERT(context); + ntlm_rc4k(context->KeyExchangeKey, 16, context->RandomSessionKey, + context->EncryptedRandomSessionKey); +} + +/** + * Decrypt RandomSessionKey (RC4-encrypted RandomSessionKey, using KeyExchangeKey as the key). + * @param context A pointer to the NTLM context + */ + +void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + /* In NTLMv2, EncryptedRandomSessionKey is the ExportedSessionKey RC4-encrypted with the + * KeyExchangeKey */ + + /** + * if (NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) + * Set RandomSessionKey to RC4K(KeyExchangeKey, + * AUTHENTICATE_MESSAGE.EncryptedRandomSessionKey) else Set RandomSessionKey to KeyExchangeKey + */ + if (context->NegotiateKeyExchange) + { + WINPR_ASSERT(sizeof(context->EncryptedRandomSessionKey) == + sizeof(context->RandomSessionKey)); + ntlm_rc4k(context->KeyExchangeKey, sizeof(context->EncryptedRandomSessionKey), + context->EncryptedRandomSessionKey, context->RandomSessionKey); + } + else + { + WINPR_ASSERT(sizeof(context->RandomSessionKey) == sizeof(context->KeyExchangeKey)); + CopyMemory(context->RandomSessionKey, context->KeyExchangeKey, + sizeof(context->RandomSessionKey)); + } +} + +/** + * Generate signing key msdn{cc236711} + * + * @param exported_session_key ExportedSessionKey + * @param sign_magic Sign magic string + * @param signing_key Destination signing key + * + * @return \b TRUE for success, \b FALSE for failure + */ + +static BOOL ntlm_generate_signing_key(BYTE* exported_session_key, const SecBuffer* sign_magic, + BYTE* signing_key) +{ + BOOL rc = FALSE; + size_t length = 0; + BYTE* value = NULL; + + WINPR_ASSERT(exported_session_key); + WINPR_ASSERT(sign_magic); + WINPR_ASSERT(signing_key); + + length = WINPR_MD5_DIGEST_LENGTH + sign_magic->cbBuffer; + value = (BYTE*)malloc(length); + + if (!value) + goto out; + + /* Concatenate ExportedSessionKey with sign magic */ + CopyMemory(value, exported_session_key, WINPR_MD5_DIGEST_LENGTH); + CopyMemory(&value[WINPR_MD5_DIGEST_LENGTH], sign_magic->pvBuffer, sign_magic->cbBuffer); + + rc = winpr_Digest(WINPR_MD_MD5, value, length, signing_key, WINPR_MD5_DIGEST_LENGTH); + +out: + free(value); + return rc; +} + +/** + * Generate client signing key (ClientSigningKey). msdn{cc236711} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context) +{ + const SecBuffer signMagic = { sizeof(NTLM_CLIENT_SIGN_MAGIC), 0, NTLM_CLIENT_SIGN_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic, + context->ClientSigningKey); +} + +/** + * Generate server signing key (ServerSigningKey). msdn{cc236711} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context) +{ + const SecBuffer signMagic = { sizeof(NTLM_SERVER_SIGN_MAGIC), 0, NTLM_SERVER_SIGN_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &signMagic, + context->ServerSigningKey); +} + +/** + * Generate client sealing key (ClientSealingKey). msdn{cc236712} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context) +{ + const SecBuffer sealMagic = { sizeof(NTLM_CLIENT_SEAL_MAGIC), 0, NTLM_CLIENT_SEAL_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic, + context->ClientSealingKey); +} + +/** + * Generate server sealing key (ServerSealingKey). msdn{cc236712} + * @param context A pointer to the NTLM context + * + * @return \b TRUE for success, \b FALSE for failure + */ + +BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context) +{ + const SecBuffer sealMagic = { sizeof(NTLM_SERVER_SEAL_MAGIC), 0, NTLM_SERVER_SEAL_MAGIC }; + + WINPR_ASSERT(context); + return ntlm_generate_signing_key(context->ExportedSessionKey, &sealMagic, + context->ServerSealingKey); +} + +/** + * Initialize RC4 stream cipher states for sealing. + * @param context A pointer to the NTLM context + */ + +BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + if (context->server) + { + context->SendSigningKey = context->ServerSigningKey; + context->RecvSigningKey = context->ClientSigningKey; + context->SendSealingKey = context->ClientSealingKey; + context->RecvSealingKey = context->ServerSealingKey; + context->SendRc4Seal = + winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey)); + context->RecvRc4Seal = + winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey)); + } + else + { + context->SendSigningKey = context->ClientSigningKey; + context->RecvSigningKey = context->ServerSigningKey; + context->SendSealingKey = context->ServerSealingKey; + context->RecvSealingKey = context->ClientSealingKey; + context->SendRc4Seal = + winpr_RC4_New(context->ClientSealingKey, sizeof(context->ClientSealingKey)); + context->RecvRc4Seal = + winpr_RC4_New(context->ServerSealingKey, sizeof(context->ServerSealingKey)); + } + if (!context->SendRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->SendRc4Seal"); + return FALSE; + } + if (!context->RecvRc4Seal) + { + WLog_ERR(TAG, "Failed to allocate context->RecvRc4Seal"); + return FALSE; + } + return TRUE; +} + +BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size) +{ + BOOL rc = FALSE; + /* + * Compute the HMAC-MD5 hash of ConcatenationOf(NEGOTIATE_MESSAGE, + * CHALLENGE_MESSAGE, AUTHENTICATE_MESSAGE) using the ExportedSessionKey + */ + WINPR_HMAC_CTX* hmac = winpr_HMAC_New(); + + WINPR_ASSERT(context); + WINPR_ASSERT(mic); + WINPR_ASSERT(size >= WINPR_MD5_DIGEST_LENGTH); + + memset(mic, 0, size); + if (!hmac) + return FALSE; + + if (winpr_HMAC_Init(hmac, WINPR_MD_MD5, context->ExportedSessionKey, WINPR_MD5_DIGEST_LENGTH)) + { + winpr_HMAC_Update(hmac, (BYTE*)context->NegotiateMessage.pvBuffer, + context->NegotiateMessage.cbBuffer); + winpr_HMAC_Update(hmac, (BYTE*)context->ChallengeMessage.pvBuffer, + context->ChallengeMessage.cbBuffer); + + if (context->MessageIntegrityCheckOffset > 0) + { + const BYTE* auth = (BYTE*)context->AuthenticateMessage.pvBuffer; + const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + const size_t rest = context->MessageIntegrityCheckOffset + sizeof(data); + + WINPR_ASSERT(rest <= context->AuthenticateMessage.cbBuffer); + winpr_HMAC_Update(hmac, &auth[0], context->MessageIntegrityCheckOffset); + winpr_HMAC_Update(hmac, data, sizeof(data)); + winpr_HMAC_Update(hmac, &auth[rest], context->AuthenticateMessage.cbBuffer - rest); + } + else + { + winpr_HMAC_Update(hmac, (BYTE*)context->AuthenticateMessage.pvBuffer, + context->AuthenticateMessage.cbBuffer); + } + winpr_HMAC_Final(hmac, mic, WINPR_MD5_DIGEST_LENGTH); + rc = TRUE; + } + + winpr_HMAC_Free(hmac); + return rc; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_compute.h b/winpr/libwinpr/sspi/NTLM/ntlm_compute.h new file mode 100644 index 0000000..006a449 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_compute.h @@ -0,0 +1,64 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (Compute) + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_NTLM_COMPUTE_H +#define WINPR_SSPI_NTLM_COMPUTE_H + +#include "ntlm.h" + +#include "ntlm_av_pairs.h" + +BOOL ntlm_get_version_info(NTLM_VERSION_INFO* versionInfo); +BOOL ntlm_read_version_info(wStream* s, NTLM_VERSION_INFO* versionInfo); +BOOL ntlm_write_version_info(wStream* s, const NTLM_VERSION_INFO* versionInfo); + +#ifdef WITH_DEBUG_NTLM +void ntlm_print_version_info(const NTLM_VERSION_INFO* versionInfo); +#endif + +BOOL ntlm_read_ntlm_v2_response(wStream* s, NTLMv2_RESPONSE* response); +BOOL ntlm_write_ntlm_v2_response(wStream* s, const NTLMv2_RESPONSE* response); + +void ntlm_output_target_name(NTLM_CONTEXT* context); +void ntlm_output_channel_bindings(NTLM_CONTEXT* context); + +void ntlm_current_time(BYTE* timestamp); +void ntlm_generate_timestamp(NTLM_CONTEXT* context); + +BOOL ntlm_compute_lm_v2_response(NTLM_CONTEXT* context); +BOOL ntlm_compute_ntlm_v2_response(NTLM_CONTEXT* context); + +void ntlm_rc4k(BYTE* key, size_t length, BYTE* plaintext, BYTE* ciphertext); +void ntlm_generate_client_challenge(NTLM_CONTEXT* context); +void ntlm_generate_server_challenge(NTLM_CONTEXT* context); +void ntlm_generate_key_exchange_key(NTLM_CONTEXT* context); +void ntlm_generate_random_session_key(NTLM_CONTEXT* context); +void ntlm_generate_exported_session_key(NTLM_CONTEXT* context); +void ntlm_encrypt_random_session_key(NTLM_CONTEXT* context); +void ntlm_decrypt_random_session_key(NTLM_CONTEXT* context); + +BOOL ntlm_generate_client_signing_key(NTLM_CONTEXT* context); +BOOL ntlm_generate_server_signing_key(NTLM_CONTEXT* context); +BOOL ntlm_generate_client_sealing_key(NTLM_CONTEXT* context); +BOOL ntlm_generate_server_sealing_key(NTLM_CONTEXT* context); +BOOL ntlm_init_rc4_seal_states(NTLM_CONTEXT* context); + +BOOL ntlm_compute_message_integrity_check(NTLM_CONTEXT* context, BYTE* mic, UINT32 size); + +#endif /* WINPR_AUTH_NTLM_COMPUTE_H */ diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_export.h b/winpr/libwinpr/sspi/NTLM/ntlm_export.h new file mode 100644 index 0000000..5249be2 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_export.h @@ -0,0 +1,40 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package + * + * Copyright 2021 Armin Novak <armin.novak@thincast.com> + * Copyright 2021 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_NTLM_EXPORT_H +#define WINPR_SSPI_NTLM_EXPORT_H + +#include <winpr/sspi.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + extern const SecPkgInfoA NTLM_SecPkgInfoA; + extern const SecPkgInfoW NTLM_SecPkgInfoW; + extern const SecurityFunctionTableA NTLM_SecurityFunctionTableA; + extern const SecurityFunctionTableW NTLM_SecurityFunctionTableW; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_message.c b/winpr/libwinpr/sspi/NTLM/ntlm_message.c new file mode 100644 index 0000000..511ca47 --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_message.c @@ -0,0 +1,1397 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (Message) + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include "ntlm.h" +#include "../sspi.h" + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/print.h> +#include <winpr/stream.h> +#include <winpr/sysinfo.h> + +#include "ntlm_compute.h" + +#include "ntlm_message.h" + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.NTLM") + +#define NTLM_CheckAndLogRequiredCapacity(tag, s, nmemb, what) \ + Stream_CheckAndLogRequiredCapacityEx(tag, WLOG_WARN, s, nmemb, 1, "%s(%s:%" PRIuz ") " what, \ + __func__, __FILE__, (size_t)__LINE__) + +static const char NTLM_SIGNATURE[8] = { 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0' }; + +static void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields); + +const char* ntlm_get_negotiate_string(UINT32 flag) +{ + if (flag & NTLMSSP_NEGOTIATE_56) + return "NTLMSSP_NEGOTIATE_56"; + if (flag & NTLMSSP_NEGOTIATE_KEY_EXCH) + return "NTLMSSP_NEGOTIATE_KEY_EXCH"; + if (flag & NTLMSSP_NEGOTIATE_128) + return "NTLMSSP_NEGOTIATE_128"; + if (flag & NTLMSSP_RESERVED1) + return "NTLMSSP_RESERVED1"; + if (flag & NTLMSSP_RESERVED2) + return "NTLMSSP_RESERVED2"; + if (flag & NTLMSSP_RESERVED3) + return "NTLMSSP_RESERVED3"; + if (flag & NTLMSSP_NEGOTIATE_VERSION) + return "NTLMSSP_NEGOTIATE_VERSION"; + if (flag & NTLMSSP_RESERVED4) + return "NTLMSSP_RESERVED4"; + if (flag & NTLMSSP_NEGOTIATE_TARGET_INFO) + return "NTLMSSP_NEGOTIATE_TARGET_INFO"; + if (flag & NTLMSSP_REQUEST_NON_NT_SESSION_KEY) + return "NTLMSSP_REQUEST_NON_NT_SESSION_KEY"; + if (flag & NTLMSSP_RESERVED5) + return "NTLMSSP_RESERVED5"; + if (flag & NTLMSSP_NEGOTIATE_IDENTIFY) + return "NTLMSSP_NEGOTIATE_IDENTIFY"; + if (flag & NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY) + return "NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY"; + if (flag & NTLMSSP_RESERVED6) + return "NTLMSSP_RESERVED6"; + if (flag & NTLMSSP_TARGET_TYPE_SERVER) + return "NTLMSSP_TARGET_TYPE_SERVER"; + if (flag & NTLMSSP_TARGET_TYPE_DOMAIN) + return "NTLMSSP_TARGET_TYPE_DOMAIN"; + if (flag & NTLMSSP_NEGOTIATE_ALWAYS_SIGN) + return "NTLMSSP_NEGOTIATE_ALWAYS_SIGN"; + if (flag & NTLMSSP_RESERVED7) + return "NTLMSSP_RESERVED7"; + if (flag & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + return "NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED"; + if (flag & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + return "NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED"; + if (flag & NTLMSSP_NEGOTIATE_ANONYMOUS) + return "NTLMSSP_NEGOTIATE_ANONYMOUS"; + if (flag & NTLMSSP_RESERVED8) + return "NTLMSSP_RESERVED8"; + if (flag & NTLMSSP_NEGOTIATE_NTLM) + return "NTLMSSP_NEGOTIATE_NTLM"; + if (flag & NTLMSSP_RESERVED9) + return "NTLMSSP_RESERVED9"; + if (flag & NTLMSSP_NEGOTIATE_LM_KEY) + return "NTLMSSP_NEGOTIATE_LM_KEY"; + if (flag & NTLMSSP_NEGOTIATE_DATAGRAM) + return "NTLMSSP_NEGOTIATE_DATAGRAM"; + if (flag & NTLMSSP_NEGOTIATE_SEAL) + return "NTLMSSP_NEGOTIATE_SEAL"; + if (flag & NTLMSSP_NEGOTIATE_SIGN) + return "NTLMSSP_NEGOTIATE_SIGN"; + if (flag & NTLMSSP_RESERVED10) + return "NTLMSSP_RESERVED10"; + if (flag & NTLMSSP_REQUEST_TARGET) + return "NTLMSSP_REQUEST_TARGET"; + if (flag & NTLMSSP_NEGOTIATE_OEM) + return "NTLMSSP_NEGOTIATE_OEM"; + if (flag & NTLMSSP_NEGOTIATE_UNICODE) + return "NTLMSSP_NEGOTIATE_UNICODE"; + return "NTLMSSP_NEGOTIATE_UNKNOWN"; +} + +#if defined(WITH_DEBUG_NTLM) +static void ntlm_print_message_fields(const NTLM_MESSAGE_FIELDS* fields, const char* name) +{ + WINPR_ASSERT(fields); + WINPR_ASSERT(name); + + WLog_VRB(TAG, "%s (Len: %" PRIu16 " MaxLen: %" PRIu16 " BufferOffset: %" PRIu32 ")", name, + fields->Len, fields->MaxLen, fields->BufferOffset); + + if (fields->Len > 0) + winpr_HexDump(TAG, WLOG_TRACE, fields->Buffer, fields->Len); +} + +static void ntlm_print_negotiate_flags(UINT32 flags) +{ + WLog_VRB(TAG, "negotiateFlags \"0x%08" PRIX32 "\"", flags); + + for (int i = 31; i >= 0; i--) + { + if ((flags >> i) & 1) + { + const char* str = ntlm_get_negotiate_string(1 << i); + WLog_VRB(TAG, "\t%s (%d),", str, (31 - i)); + } + } +} + +static void ntlm_print_negotiate_message(const SecBuffer* NegotiateMessage, + const NTLM_NEGOTIATE_MESSAGE* message) +{ + WINPR_ASSERT(NegotiateMessage); + WINPR_ASSERT(message); + + WLog_VRB(TAG, "NEGOTIATE_MESSAGE (length = %" PRIu32 ")", NegotiateMessage->cbBuffer); + winpr_HexDump(TAG, WLOG_TRACE, NegotiateMessage->pvBuffer, NegotiateMessage->cbBuffer); + ntlm_print_negotiate_flags(message->NegotiateFlags); + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_print_version_info(&(message->Version)); +} + +static void ntlm_print_challenge_message(const SecBuffer* ChallengeMessage, + const NTLM_CHALLENGE_MESSAGE* message, + const SecBuffer* ChallengeTargetInfo) +{ + WINPR_ASSERT(ChallengeMessage); + WINPR_ASSERT(message); + + WLog_VRB(TAG, "CHALLENGE_MESSAGE (length = %" PRIu32 ")", ChallengeMessage->cbBuffer); + winpr_HexDump(TAG, WLOG_TRACE, ChallengeMessage->pvBuffer, ChallengeMessage->cbBuffer); + ntlm_print_negotiate_flags(message->NegotiateFlags); + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_print_version_info(&(message->Version)); + + ntlm_print_message_fields(&(message->TargetName), "TargetName"); + ntlm_print_message_fields(&(message->TargetInfo), "TargetInfo"); + + if (ChallengeTargetInfo && (ChallengeTargetInfo->cbBuffer > 0)) + { + WLog_VRB(TAG, "ChallengeTargetInfo (%" PRIu32 "):", ChallengeTargetInfo->cbBuffer); + ntlm_print_av_pair_list(ChallengeTargetInfo->pvBuffer, ChallengeTargetInfo->cbBuffer); + } +} + +static void ntlm_print_authenticate_message(const SecBuffer* AuthenticateMessage, + const NTLM_AUTHENTICATE_MESSAGE* message, UINT32 flags, + const SecBuffer* AuthenticateTargetInfo) +{ + WINPR_ASSERT(AuthenticateMessage); + WINPR_ASSERT(message); + + WLog_VRB(TAG, "AUTHENTICATE_MESSAGE (length = %" PRIu32 ")", AuthenticateMessage->cbBuffer); + winpr_HexDump(TAG, WLOG_TRACE, AuthenticateMessage->pvBuffer, AuthenticateMessage->cbBuffer); + ntlm_print_negotiate_flags(message->NegotiateFlags); + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_print_version_info(&(message->Version)); + + if (AuthenticateTargetInfo && (AuthenticateTargetInfo->cbBuffer > 0)) + { + WLog_VRB(TAG, "AuthenticateTargetInfo (%" PRIu32 "):", AuthenticateTargetInfo->cbBuffer); + ntlm_print_av_pair_list(AuthenticateTargetInfo->pvBuffer, AuthenticateTargetInfo->cbBuffer); + } + + ntlm_print_message_fields(&(message->DomainName), "DomainName"); + ntlm_print_message_fields(&(message->UserName), "UserName"); + ntlm_print_message_fields(&(message->Workstation), "Workstation"); + ntlm_print_message_fields(&(message->LmChallengeResponse), "LmChallengeResponse"); + ntlm_print_message_fields(&(message->NtChallengeResponse), "NtChallengeResponse"); + ntlm_print_message_fields(&(message->EncryptedRandomSessionKey), "EncryptedRandomSessionKey"); + + if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK) + { + WLog_VRB(TAG, "MessageIntegrityCheck (length = 16)"); + winpr_HexDump(TAG, WLOG_TRACE, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck)); + } +} + +static void ntlm_print_authentication_complete(const NTLM_CONTEXT* context) +{ + WINPR_ASSERT(context); + + WLog_VRB(TAG, "ClientChallenge"); + winpr_HexDump(TAG, WLOG_TRACE, context->ClientChallenge, 8); + WLog_VRB(TAG, "ServerChallenge"); + winpr_HexDump(TAG, WLOG_TRACE, context->ServerChallenge, 8); + WLog_VRB(TAG, "SessionBaseKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->SessionBaseKey, 16); + WLog_VRB(TAG, "KeyExchangeKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->KeyExchangeKey, 16); + WLog_VRB(TAG, "ExportedSessionKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->ExportedSessionKey, 16); + WLog_VRB(TAG, "RandomSessionKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->RandomSessionKey, 16); + WLog_VRB(TAG, "ClientSigningKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->ClientSigningKey, 16); + WLog_VRB(TAG, "ClientSealingKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->ClientSealingKey, 16); + WLog_VRB(TAG, "ServerSigningKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->ServerSigningKey, 16); + WLog_VRB(TAG, "ServerSealingKey"); + winpr_HexDump(TAG, WLOG_TRACE, context->ServerSealingKey, 16); + WLog_VRB(TAG, "Timestamp"); + winpr_HexDump(TAG, WLOG_TRACE, context->Timestamp, 8); +} +#endif + +static BOOL ntlm_read_message_header(wStream* s, NTLM_MESSAGE_HEADER* header, UINT32 expected) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return FALSE; + + Stream_Read(s, header->Signature, 8); + Stream_Read_UINT32(s, header->MessageType); + + if (strncmp((char*)header->Signature, NTLM_SIGNATURE, 8) != 0) + { + WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid signature, got %s, expected %s", + header->Signature, NTLM_SIGNATURE); + return FALSE; + } + + if (header->MessageType != expected) + { + WLog_ERR(TAG, "NTLM_MESSAGE_HEADER Invalid message tyep, got %s, expected %s", + ntlm_message_type_string(header->MessageType), ntlm_message_type_string(expected)); + return FALSE; + } + + return TRUE; +} + +static BOOL ntlm_write_message_header(wStream* s, const NTLM_MESSAGE_HEADER* header) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(header); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, sizeof(NTLM_SIGNATURE) + 4ull, + "NTLM_MESSAGE_HEADER::header")) + return FALSE; + + Stream_Write(s, header->Signature, sizeof(NTLM_SIGNATURE)); + Stream_Write_UINT32(s, header->MessageType); + + return TRUE; +} + +static BOOL ntlm_populate_message_header(NTLM_MESSAGE_HEADER* header, UINT32 MessageType) +{ + WINPR_ASSERT(header); + + CopyMemory(header->Signature, NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); + header->MessageType = MessageType; + return TRUE; +} + +static BOOL ntlm_read_message_fields(wStream* s, NTLM_MESSAGE_FIELDS* fields) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return FALSE; + + ntlm_free_message_fields_buffer(fields); + + Stream_Read_UINT16(s, fields->Len); /* Len (2 bytes) */ + Stream_Read_UINT16(s, fields->MaxLen); /* MaxLen (2 bytes) */ + Stream_Read_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */ + return TRUE; +} + +static BOOL ntlm_write_message_fields(wStream* s, const NTLM_MESSAGE_FIELDS* fields) +{ + UINT16 MaxLen = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + MaxLen = fields->MaxLen; + if (fields->MaxLen < 1) + MaxLen = fields->Len; + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), 8, "NTLM_MESSAGE_FIELDS::header")) + return FALSE; + + Stream_Write_UINT16(s, fields->Len); /* Len (2 bytes) */ + Stream_Write_UINT16(s, MaxLen); /* MaxLen (2 bytes) */ + Stream_Write_UINT32(s, fields->BufferOffset); /* BufferOffset (4 bytes) */ + return TRUE; +} + +static BOOL ntlm_read_message_fields_buffer(wStream* s, NTLM_MESSAGE_FIELDS* fields) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + if (fields->Len > 0) + { + const UINT32 offset = fields->BufferOffset + fields->Len; + + if (fields->BufferOffset > UINT32_MAX - fields->Len) + { + WLog_ERR(TAG, + "NTLM_MESSAGE_FIELDS::BufferOffset %" PRIu32 + " too large, maximum allowed is %" PRIu32, + fields->BufferOffset, UINT32_MAX - fields->Len); + return FALSE; + } + + if (offset > Stream_Length(s)) + { + WLog_ERR(TAG, + "NTLM_MESSAGE_FIELDS::Buffer offset %" PRIu32 " beyond received data %" PRIuz, + offset, Stream_Length(s)); + return FALSE; + } + + fields->Buffer = (PBYTE)malloc(fields->Len); + + if (!fields->Buffer) + { + WLog_ERR(TAG, "NTLM_MESSAGE_FIELDS::Buffer allocation of %" PRIu16 "bytes failed", + fields->Len); + return FALSE; + } + + Stream_SetPosition(s, fields->BufferOffset); + Stream_Read(s, fields->Buffer, fields->Len); + } + + return TRUE; +} + +static BOOL ntlm_write_message_fields_buffer(wStream* s, const NTLM_MESSAGE_FIELDS* fields) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(fields); + + if (fields->Len > 0) + { + Stream_SetPosition(s, fields->BufferOffset); + if (!NTLM_CheckAndLogRequiredCapacity(TAG, (s), fields->Len, "NTLM_MESSAGE_FIELDS::Len")) + return FALSE; + + Stream_Write(s, fields->Buffer, fields->Len); + } + return TRUE; +} + +void ntlm_free_message_fields_buffer(NTLM_MESSAGE_FIELDS* fields) +{ + if (fields) + { + if (fields->Buffer) + { + free(fields->Buffer); + fields->Len = 0; + fields->MaxLen = 0; + fields->Buffer = NULL; + fields->BufferOffset = 0; + } + } +} + +static BOOL ntlm_read_negotiate_flags(wStream* s, UINT32* flags, UINT32 required, const char* name) +{ + UINT32 NegotiateFlags = 0; + char buffer[1024] = { 0 }; + WINPR_ASSERT(s); + WINPR_ASSERT(flags); + WINPR_ASSERT(name); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, NegotiateFlags); /* NegotiateFlags (4 bytes) */ + + if ((NegotiateFlags & required) != required) + { + WLog_ERR(TAG, "%s::NegotiateFlags invalid flags 0x08%" PRIx32 ", 0x%08" PRIx32 " required", + name, NegotiateFlags, required); + return FALSE; + } + + WLog_DBG(TAG, "Read flags %s", + ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), NegotiateFlags)); + *flags = NegotiateFlags; + return TRUE; +} + +static BOOL ntlm_write_negotiate_flags(wStream* s, UINT32 flags, const char* name) +{ + char buffer[1024] = { 0 }; + WINPR_ASSERT(s); + WINPR_ASSERT(name); + + if (!Stream_CheckAndLogRequiredCapacityEx(TAG, WLOG_WARN, s, 4ull, 1ull, + "%s(%s:%" PRIuz ") %s::NegotiateFlags", __func__, + __FILE__, (size_t)__LINE__, name)) + return FALSE; + + WLog_DBG(TAG, "Write flags %s", ntlm_negotiate_flags_string(buffer, ARRAYSIZE(buffer), flags)); + Stream_Write_UINT32(s, flags); /* NegotiateFlags (4 bytes) */ + return TRUE; +} + +static BOOL ntlm_read_message_integrity_check(wStream* s, size_t* offset, BYTE* data, size_t size, + const char* name) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(offset); + WINPR_ASSERT(data); + WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH); + WINPR_ASSERT(name); + + *offset = Stream_GetPosition(s); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, size)) + return FALSE; + + Stream_Read(s, data, size); + return TRUE; +} + +static BOOL ntlm_write_message_integrity_check(wStream* s, size_t offset, const BYTE* data, + size_t size, const char* name) +{ + size_t pos = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(data); + WINPR_ASSERT(size == WINPR_MD5_DIGEST_LENGTH); + WINPR_ASSERT(name); + + pos = Stream_GetPosition(s); + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, offset, "MessageIntegrityCheck::offset")) + return FALSE; + + Stream_SetPosition(s, offset); + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, size, "MessageIntegrityCheck::size")) + return FALSE; + + Stream_Write(s, data, size); + Stream_SetPosition(s, pos); + return TRUE; +} + +SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + const NTLM_NEGOTIATE_MESSAGE empty = { 0 }; + NTLM_NEGOTIATE_MESSAGE* message = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + message = &context->NEGOTIATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_NEGOTIATE)) + return SEC_E_INVALID_TOKEN; + + if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, + NTLMSSP_REQUEST_TARGET | NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_UNICODE, + "NTLM_NEGOTIATE_MESSAGE")) + return SEC_E_INVALID_TOKEN; + + context->NegotiateFlags = message->NegotiateFlags; + + /* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */ + // if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + { + if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */ + return SEC_E_INVALID_TOKEN; + } + + /* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */ + // if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + { + if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */ + return SEC_E_INVALID_TOKEN; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */ + return SEC_E_INVALID_TOKEN; + } + + if (!ntlm_read_message_fields_buffer(s, &message->DomainName)) + return SEC_E_INVALID_TOKEN; + + if (!ntlm_read_message_fields_buffer(s, &message->Workstation)) + return SEC_E_INVALID_TOKEN; + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + buffer->cbBuffer = (ULONG)length; + + if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer); + context->NegotiateMessage.BufferType = buffer->BufferType; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_negotiate_message(&context->NegotiateMessage, message); +#endif + ntlm_change_state(context, NTLM_STATE_CHALLENGE); + return SEC_I_CONTINUE_NEEDED; +} + +SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + const NTLM_NEGOTIATE_MESSAGE empty = { 0 }; + NTLM_NEGOTIATE_MESSAGE* message = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + message = &context->NEGOTIATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_NEGOTIATE)) + return SEC_E_INTERNAL_ERROR; + + if (context->NTLMv2) + { + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_LM_KEY; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_OEM; + } + + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN; + message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE; + + if (context->confidentiality) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL; + + if (context->SendVersionInfo) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_get_version_info(&(message->Version)); + + context->NegotiateFlags = message->NegotiateFlags; + /* Message Header (12 bytes) */ + if (!ntlm_write_message_header(s, &message->header)) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_NEGOTIATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + + /* only set if NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED is set */ + /* DomainNameFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->DomainName))) + return SEC_E_INTERNAL_ERROR; + + /* only set if NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED is set */ + /* WorkstationFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->Workstation))) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_write_version_info(s, &(message->Version))) + return SEC_E_INTERNAL_ERROR; + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + buffer->cbBuffer = (ULONG)length; + + if (!sspi_SecBufferAlloc(&context->NegotiateMessage, (ULONG)length)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->NegotiateMessage.pvBuffer, buffer->pvBuffer, buffer->cbBuffer); + context->NegotiateMessage.BufferType = buffer->BufferType; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_negotiate_message(&context->NegotiateMessage, message); +#endif + ntlm_change_state(context, NTLM_STATE_CHALLENGE); + return SEC_I_CONTINUE_NEEDED; +} + +SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer) +{ + SECURITY_STATUS status = SEC_E_INVALID_TOKEN; + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + size_t StartOffset = 0; + size_t PayloadOffset = 0; + NTLM_AV_PAIR* AvTimestamp = NULL; + const NTLM_CHALLENGE_MESSAGE empty = { 0 }; + NTLM_CHALLENGE_MESSAGE* message = NULL; + + if (!context || !buffer) + return SEC_E_INTERNAL_ERROR; + + ntlm_generate_client_challenge(context); + message = &context->CHALLENGE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + StartOffset = Stream_GetPosition(s); + + if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_CHALLENGE)) + goto fail; + + if (!ntlm_read_message_fields(s, &(message->TargetName))) /* TargetNameFields (8 bytes) */ + goto fail; + + if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_CHALLENGE_MESSAGE")) + goto fail; + + context->NegotiateFlags = message->NegotiateFlags; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 16)) + goto fail; + + Stream_Read(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */ + CopyMemory(context->ServerChallenge, message->ServerChallenge, 8); + Stream_Read(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */ + + if (!ntlm_read_message_fields(s, &(message->TargetInfo))) /* TargetInfoFields (8 bytes) */ + goto fail; + + if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */ + goto fail; + } + + /* Payload (variable) */ + PayloadOffset = Stream_GetPosition(s); + + status = SEC_E_INTERNAL_ERROR; + if (message->TargetName.Len > 0) + { + if (!ntlm_read_message_fields_buffer(s, &(message->TargetName))) + goto fail; + } + + if (message->TargetInfo.Len > 0) + { + size_t cbAvTimestamp = 0; + + if (!ntlm_read_message_fields_buffer(s, &(message->TargetInfo))) + goto fail; + + context->ChallengeTargetInfo.pvBuffer = message->TargetInfo.Buffer; + context->ChallengeTargetInfo.cbBuffer = message->TargetInfo.Len; + AvTimestamp = ntlm_av_pair_get((NTLM_AV_PAIR*)message->TargetInfo.Buffer, + message->TargetInfo.Len, MsvAvTimestamp, &cbAvTimestamp); + + if (AvTimestamp) + { + PBYTE ptr = ntlm_av_pair_get_value_pointer(AvTimestamp); + + if (!ptr) + goto fail; + + if (context->NTLMv2) + context->UseMIC = TRUE; + + CopyMemory(context->ChallengeTimestamp, ptr, 8); + } + } + + length = (PayloadOffset - StartOffset) + message->TargetName.Len + message->TargetInfo.Len; + if (length > buffer->cbBuffer) + goto fail; + + if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length)) + goto fail; + + if (context->ChallengeMessage.pvBuffer) + CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s) + StartOffset, length); +#if defined(WITH_DEBUG_NTLM) + ntlm_print_challenge_message(&context->ChallengeMessage, message, NULL); +#endif + /* AV_PAIRs */ + + if (context->NTLMv2) + { + if (!ntlm_construct_authenticate_target_info(context)) + goto fail; + + sspi_SecBufferFree(&context->ChallengeTargetInfo); + context->ChallengeTargetInfo.pvBuffer = context->AuthenticateTargetInfo.pvBuffer; + context->ChallengeTargetInfo.cbBuffer = context->AuthenticateTargetInfo.cbBuffer; + } + + ntlm_generate_timestamp(context); /* Timestamp */ + + if (!ntlm_compute_lm_v2_response(context)) /* LmChallengeResponse */ + goto fail; + + if (!ntlm_compute_ntlm_v2_response(context)) /* NtChallengeResponse */ + goto fail; + + ntlm_generate_key_exchange_key(context); /* KeyExchangeKey */ + ntlm_generate_random_session_key(context); /* RandomSessionKey */ + ntlm_generate_exported_session_key(context); /* ExportedSessionKey */ + ntlm_encrypt_random_session_key(context); /* EncryptedRandomSessionKey */ + /* Generate signing keys */ + if (!ntlm_generate_client_signing_key(context)) + goto fail; + if (!ntlm_generate_server_signing_key(context)) + goto fail; + /* Generate sealing keys */ + if (!ntlm_generate_client_sealing_key(context)) + goto fail; + if (!ntlm_generate_server_sealing_key(context)) + goto fail; + /* Initialize RC4 seal state using client sealing key */ + if (!ntlm_init_rc4_seal_states(context)) + goto fail; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authentication_complete(context); +#endif + ntlm_change_state(context, NTLM_STATE_AUTHENTICATE); + status = SEC_I_CONTINUE_NEEDED; +fail: + ntlm_free_message_fields_buffer(&(message->TargetName)); + return status; +} + +SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, const PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + UINT32 PayloadOffset = 0; + const NTLM_CHALLENGE_MESSAGE empty = { 0 }; + NTLM_CHALLENGE_MESSAGE* message = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + message = &context->CHALLENGE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + ntlm_get_version_info(&(message->Version)); /* Version */ + ntlm_generate_server_challenge(context); /* Server Challenge */ + ntlm_generate_timestamp(context); /* Timestamp */ + + if (!ntlm_construct_challenge_target_info(context)) /* TargetInfo */ + return SEC_E_INTERNAL_ERROR; + + CopyMemory(message->ServerChallenge, context->ServerChallenge, 8); /* ServerChallenge */ + message->NegotiateFlags = context->NegotiateFlags; + if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_CHALLENGE)) + return SEC_E_INTERNAL_ERROR; + + /* Message Header (12 bytes) */ + if (!ntlm_write_message_header(s, &message->header)) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET) + { + message->TargetName.Len = (UINT16)context->TargetName.cbBuffer; + message->TargetName.Buffer = (PBYTE)context->TargetName.pvBuffer; + } + + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO) + { + message->TargetInfo.Len = (UINT16)context->ChallengeTargetInfo.cbBuffer; + message->TargetInfo.Buffer = (PBYTE)context->ChallengeTargetInfo.pvBuffer; + } + + PayloadOffset = 48; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + PayloadOffset += 8; + + message->TargetName.BufferOffset = PayloadOffset; + message->TargetInfo.BufferOffset = message->TargetName.BufferOffset + message->TargetName.Len; + /* TargetNameFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->TargetName))) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_CHALLENGE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + + if (!NTLM_CheckAndLogRequiredCapacity(TAG, s, 16, "NTLM_CHALLENGE_MESSAGE::ServerChallenge")) + return SEC_E_INTERNAL_ERROR; + + Stream_Write(s, message->ServerChallenge, 8); /* ServerChallenge (8 bytes) */ + Stream_Write(s, message->Reserved, 8); /* Reserved (8 bytes), should be ignored */ + + /* TargetInfoFields (8 bytes) */ + if (!ntlm_write_message_fields(s, &(message->TargetInfo))) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + } + + /* Payload (variable) */ + if (message->NegotiateFlags & NTLMSSP_REQUEST_TARGET) + { + if (!ntlm_write_message_fields_buffer(s, &(message->TargetName))) + return SEC_E_INTERNAL_ERROR; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_TARGET_INFO) + { + if (!ntlm_write_message_fields_buffer(s, &(message->TargetInfo))) + return SEC_E_INTERNAL_ERROR; + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + buffer->cbBuffer = (ULONG)length; + + if (!sspi_SecBufferAlloc(&context->ChallengeMessage, (ULONG)length)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->ChallengeMessage.pvBuffer, Stream_Buffer(s), length); +#if defined(WITH_DEBUG_NTLM) + ntlm_print_challenge_message(&context->ChallengeMessage, message, + &context->ChallengeTargetInfo); +#endif + ntlm_change_state(context, NTLM_STATE_AUTHENTICATE); + return SEC_I_CONTINUE_NEEDED; +} + +SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer) +{ + SECURITY_STATUS status = SEC_E_INVALID_TOKEN; + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + UINT32 flags = 0; + NTLM_AV_PAIR* AvFlags = NULL; + size_t PayloadBufferOffset = 0; + const NTLM_AUTHENTICATE_MESSAGE empty = { 0 }; + NTLM_AUTHENTICATE_MESSAGE* message = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + credentials = context->credentials; + WINPR_ASSERT(credentials); + + message = &context->AUTHENTICATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticConstInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (!ntlm_read_message_header(s, &message->header, MESSAGE_TYPE_AUTHENTICATE)) + goto fail; + + if (!ntlm_read_message_fields( + s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields( + s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */ + goto fail; + + if (!ntlm_read_message_fields( + s, + &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */ + goto fail; + + if (!ntlm_read_negotiate_flags(s, &message->NegotiateFlags, 0, "NTLM_AUTHENTICATE_MESSAGE")) + goto fail; + + context->NegotiateKeyExchange = + (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) ? TRUE : FALSE; + + if ((context->NegotiateKeyExchange && !message->EncryptedRandomSessionKey.Len) || + (!context->NegotiateKeyExchange && message->EncryptedRandomSessionKey.Len)) + goto fail; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_read_version_info(s, &(message->Version))) /* Version (8 bytes) */ + goto fail; + } + + PayloadBufferOffset = Stream_GetPosition(s); + + status = SEC_E_INTERNAL_ERROR; + if (!ntlm_read_message_fields_buffer(s, &(message->DomainName))) /* DomainName */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, &(message->UserName))) /* UserName */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, &(message->Workstation))) /* Workstation */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, + &(message->LmChallengeResponse))) /* LmChallengeResponse */ + goto fail; + + if (!ntlm_read_message_fields_buffer(s, + &(message->NtChallengeResponse))) /* NtChallengeResponse */ + goto fail; + + if (message->NtChallengeResponse.Len > 0) + { + size_t cbAvFlags = 0; + wStream ssbuffer; + wStream* snt = Stream_StaticConstInit(&ssbuffer, message->NtChallengeResponse.Buffer, + message->NtChallengeResponse.Len); + + if (!snt) + goto fail; + + status = SEC_E_INVALID_TOKEN; + if (!ntlm_read_ntlm_v2_response(snt, &(context->NTLMv2Response))) + goto fail; + status = SEC_E_INTERNAL_ERROR; + + context->NtChallengeResponse.pvBuffer = message->NtChallengeResponse.Buffer; + context->NtChallengeResponse.cbBuffer = message->NtChallengeResponse.Len; + sspi_SecBufferFree(&(context->ChallengeTargetInfo)); + context->ChallengeTargetInfo.pvBuffer = (void*)context->NTLMv2Response.Challenge.AvPairs; + context->ChallengeTargetInfo.cbBuffer = message->NtChallengeResponse.Len - (28 + 16); + CopyMemory(context->ClientChallenge, context->NTLMv2Response.Challenge.ClientChallenge, 8); + AvFlags = + ntlm_av_pair_get(context->NTLMv2Response.Challenge.AvPairs, + context->NTLMv2Response.Challenge.cbAvPairs, MsvAvFlags, &cbAvFlags); + + if (AvFlags) + Data_Read_UINT32(ntlm_av_pair_get_value_pointer(AvFlags), flags); + } + + if (!ntlm_read_message_fields_buffer( + s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */ + goto fail; + + if (message->EncryptedRandomSessionKey.Len > 0) + { + if (message->EncryptedRandomSessionKey.Len != 16) + goto fail; + + CopyMemory(context->EncryptedRandomSessionKey, message->EncryptedRandomSessionKey.Buffer, + 16); + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + + if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length)) + goto fail; + + CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length); + buffer->cbBuffer = (ULONG)length; + Stream_SetPosition(s, PayloadBufferOffset); + + if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK) + { + status = SEC_E_INVALID_TOKEN; + if (!ntlm_read_message_integrity_check( + s, &context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE")) + goto fail; + } + + status = SEC_E_INTERNAL_ERROR; + +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authenticate_message(&context->AuthenticateMessage, message, flags, NULL); +#endif + + if (message->UserName.Len > 0) + { + credentials->identity.User = (UINT16*)malloc(message->UserName.Len); + + if (!credentials->identity.User) + goto fail; + + CopyMemory(credentials->identity.User, message->UserName.Buffer, message->UserName.Len); + credentials->identity.UserLength = message->UserName.Len / 2; + } + + if (message->DomainName.Len > 0) + { + credentials->identity.Domain = (UINT16*)malloc(message->DomainName.Len); + + if (!credentials->identity.Domain) + goto fail; + + CopyMemory(credentials->identity.Domain, message->DomainName.Buffer, + message->DomainName.Len); + credentials->identity.DomainLength = message->DomainName.Len / 2; + } + + if (context->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) + { + if (!ntlm_compute_lm_v2_response(context)) /* LmChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + } + + if (!ntlm_compute_ntlm_v2_response(context)) /* NtChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + + /* KeyExchangeKey */ + ntlm_generate_key_exchange_key(context); + /* EncryptedRandomSessionKey */ + ntlm_decrypt_random_session_key(context); + /* ExportedSessionKey */ + ntlm_generate_exported_session_key(context); + + if (flags & MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK) + { + BYTE messageIntegrityCheck[16] = { 0 }; + + ntlm_compute_message_integrity_check(context, messageIntegrityCheck, + sizeof(messageIntegrityCheck)); + CopyMemory( + &((PBYTE)context->AuthenticateMessage.pvBuffer)[context->MessageIntegrityCheckOffset], + message->MessageIntegrityCheck, sizeof(message->MessageIntegrityCheck)); + + if (memcmp(messageIntegrityCheck, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck)) != 0) + { + WLog_ERR(TAG, "Message Integrity Check (MIC) verification failed!"); +#ifdef WITH_DEBUG_NTLM + WLog_ERR(TAG, "Expected MIC:"); + winpr_HexDump(TAG, WLOG_ERROR, messageIntegrityCheck, sizeof(messageIntegrityCheck)); + WLog_ERR(TAG, "Actual MIC:"); + winpr_HexDump(TAG, WLOG_ERROR, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck)); +#endif + return SEC_E_MESSAGE_ALTERED; + } + } + else + { + /* no mic message was present + + https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/f9e6fbc4-a953-4f24-b229-ccdcc213b9ec + the mic is optional, as not supported in Windows NT, Windows 2000, Windows XP, and + Windows Server 2003 and, as it seems, in the NTLMv2 implementation of Qt5. + + now check the NtProofString, to detect if the entered client password matches the + expected password. + */ + +#ifdef WITH_DEBUG_NTLM + WLog_VRB(TAG, "No MIC present, using NtProofString for verification."); +#endif + + if (memcmp(context->NTLMv2Response.Response, context->NtProofString, 16) != 0) + { + WLog_ERR(TAG, "NtProofString verification failed!"); +#ifdef WITH_DEBUG_NTLM + WLog_ERR(TAG, "Expected NtProofString:"); + winpr_HexDump(TAG, WLOG_ERROR, context->NtProofString, sizeof(context->NtProofString)); + WLog_ERR(TAG, "Actual NtProofString:"); + winpr_HexDump(TAG, WLOG_ERROR, context->NTLMv2Response.Response, + sizeof(context->NTLMv2Response)); +#endif + return SEC_E_LOGON_DENIED; + } + } + + /* Generate signing keys */ + if (!ntlm_generate_client_signing_key(context)) + return SEC_E_INTERNAL_ERROR; + if (!ntlm_generate_server_signing_key(context)) + return SEC_E_INTERNAL_ERROR; + /* Generate sealing keys */ + if (!ntlm_generate_client_sealing_key(context)) + return SEC_E_INTERNAL_ERROR; + if (!ntlm_generate_server_sealing_key(context)) + return SEC_E_INTERNAL_ERROR; + /* Initialize RC4 seal state */ + if (!ntlm_init_rc4_seal_states(context)) + return SEC_E_INTERNAL_ERROR; +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authentication_complete(context); +#endif + ntlm_change_state(context, NTLM_STATE_FINAL); + ntlm_free_message_fields_buffer(&(message->DomainName)); + ntlm_free_message_fields_buffer(&(message->UserName)); + ntlm_free_message_fields_buffer(&(message->Workstation)); + ntlm_free_message_fields_buffer(&(message->LmChallengeResponse)); + ntlm_free_message_fields_buffer(&(message->NtChallengeResponse)); + ntlm_free_message_fields_buffer(&(message->EncryptedRandomSessionKey)); + return SEC_E_OK; + +fail: + return status; +} + +/** + * Send NTLMSSP AUTHENTICATE_MESSAGE. msdn{cc236643} + * + * @param context Pointer to the NTLM context + * @param buffer The buffer to write + */ + +SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer) +{ + wStream sbuffer; + wStream* s = NULL; + size_t length = 0; + UINT32 PayloadBufferOffset = 0; + const NTLM_AUTHENTICATE_MESSAGE empty = { 0 }; + NTLM_AUTHENTICATE_MESSAGE* message = NULL; + SSPI_CREDENTIALS* credentials = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(buffer); + + credentials = context->credentials; + WINPR_ASSERT(credentials); + + message = &context->AUTHENTICATE_MESSAGE; + WINPR_ASSERT(message); + + *message = empty; + + s = Stream_StaticInit(&sbuffer, buffer->pvBuffer, buffer->cbBuffer); + + if (!s) + return SEC_E_INTERNAL_ERROR; + + if (context->NTLMv2) + { + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_56; + + if (context->SendVersionInfo) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_VERSION; + } + + if (context->UseMIC) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_TARGET_INFO; + + if (context->SendWorkstationName) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED; + + if (context->confidentiality) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SEAL; + + if (context->CHALLENGE_MESSAGE.NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_128; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_EXTENDED_SESSION_SECURITY; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_NTLM; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_SIGN; + message->NegotiateFlags |= NTLMSSP_REQUEST_TARGET; + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_UNICODE; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + ntlm_get_version_info(&(message->Version)); + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + { + message->Workstation.Len = context->Workstation.Length; + message->Workstation.Buffer = (BYTE*)context->Workstation.Buffer; + } + + if (credentials->identity.DomainLength > 0) + { + message->NegotiateFlags |= NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED; + message->DomainName.Len = (UINT16)credentials->identity.DomainLength * 2; + message->DomainName.Buffer = (BYTE*)credentials->identity.Domain; + } + + message->UserName.Len = (UINT16)credentials->identity.UserLength * 2; + message->UserName.Buffer = (BYTE*)credentials->identity.User; + message->LmChallengeResponse.Len = (UINT16)context->LmChallengeResponse.cbBuffer; + message->LmChallengeResponse.Buffer = (BYTE*)context->LmChallengeResponse.pvBuffer; + message->NtChallengeResponse.Len = (UINT16)context->NtChallengeResponse.cbBuffer; + message->NtChallengeResponse.Buffer = (BYTE*)context->NtChallengeResponse.pvBuffer; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) + { + message->EncryptedRandomSessionKey.Len = 16; + message->EncryptedRandomSessionKey.Buffer = context->EncryptedRandomSessionKey; + } + + PayloadBufferOffset = 64; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + PayloadBufferOffset += 8; /* Version (8 bytes) */ + + if (context->UseMIC) + PayloadBufferOffset += 16; /* Message Integrity Check (16 bytes) */ + + message->DomainName.BufferOffset = PayloadBufferOffset; + message->UserName.BufferOffset = message->DomainName.BufferOffset + message->DomainName.Len; + message->Workstation.BufferOffset = message->UserName.BufferOffset + message->UserName.Len; + message->LmChallengeResponse.BufferOffset = + message->Workstation.BufferOffset + message->Workstation.Len; + message->NtChallengeResponse.BufferOffset = + message->LmChallengeResponse.BufferOffset + message->LmChallengeResponse.Len; + message->EncryptedRandomSessionKey.BufferOffset = + message->NtChallengeResponse.BufferOffset + message->NtChallengeResponse.Len; + if (!ntlm_populate_message_header(&message->header, MESSAGE_TYPE_AUTHENTICATE)) + return SEC_E_INVALID_TOKEN; + if (!ntlm_write_message_header(s, &message->header)) /* Message Header (12 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields( + s, &(message->LmChallengeResponse))) /* LmChallengeResponseFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields( + s, &(message->NtChallengeResponse))) /* NtChallengeResponseFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields(s, &(message->DomainName))) /* DomainNameFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields(s, &(message->UserName))) /* UserNameFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields(s, &(message->Workstation))) /* WorkstationFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_message_fields( + s, + &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKeyFields (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + if (!ntlm_write_negotiate_flags(s, message->NegotiateFlags, "NTLM_AUTHENTICATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_VERSION) + { + if (!ntlm_write_version_info(s, &(message->Version))) /* Version (8 bytes) */ + return SEC_E_INTERNAL_ERROR; + } + + if (context->UseMIC) + { + const BYTE data[WINPR_MD5_DIGEST_LENGTH] = { 0 }; + + context->MessageIntegrityCheckOffset = Stream_GetPosition(s); + if (!ntlm_write_message_integrity_check(s, Stream_GetPosition(s), data, sizeof(data), + "NTLM_AUTHENTICATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED) + { + if (!ntlm_write_message_fields_buffer(s, &(message->DomainName))) /* DomainName */ + return SEC_E_INTERNAL_ERROR; + } + + if (!ntlm_write_message_fields_buffer(s, &(message->UserName))) /* UserName */ + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED) + { + if (!ntlm_write_message_fields_buffer(s, &(message->Workstation))) /* Workstation */ + return SEC_E_INTERNAL_ERROR; + } + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_LM_KEY) + { + if (!ntlm_write_message_fields_buffer( + s, &(message->LmChallengeResponse))) /* LmChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + } + if (!ntlm_write_message_fields_buffer( + s, &(message->NtChallengeResponse))) /* NtChallengeResponse */ + return SEC_E_INTERNAL_ERROR; + + if (message->NegotiateFlags & NTLMSSP_NEGOTIATE_KEY_EXCH) + { + if (!ntlm_write_message_fields_buffer( + s, &(message->EncryptedRandomSessionKey))) /* EncryptedRandomSessionKey */ + return SEC_E_INTERNAL_ERROR; + } + + length = Stream_GetPosition(s); + WINPR_ASSERT(length <= ULONG_MAX); + + if (!sspi_SecBufferAlloc(&context->AuthenticateMessage, (ULONG)length)) + return SEC_E_INTERNAL_ERROR; + + CopyMemory(context->AuthenticateMessage.pvBuffer, Stream_Buffer(s), length); + buffer->cbBuffer = (ULONG)length; + + if (context->UseMIC) + { + /* Message Integrity Check */ + ntlm_compute_message_integrity_check(context, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck)); + if (!ntlm_write_message_integrity_check( + s, context->MessageIntegrityCheckOffset, message->MessageIntegrityCheck, + sizeof(message->MessageIntegrityCheck), "NTLM_AUTHENTICATE_MESSAGE")) + return SEC_E_INTERNAL_ERROR; + } + +#if defined(WITH_DEBUG_NTLM) + ntlm_print_authenticate_message(&context->AuthenticateMessage, message, + context->UseMIC ? MSV_AV_FLAGS_MESSAGE_INTEGRITY_CHECK : 0, + &context->AuthenticateTargetInfo); +#endif + ntlm_change_state(context, NTLM_STATE_FINAL); + return SEC_E_OK; +} diff --git a/winpr/libwinpr/sspi/NTLM/ntlm_message.h b/winpr/libwinpr/sspi/NTLM/ntlm_message.h new file mode 100644 index 0000000..58ff35d --- /dev/null +++ b/winpr/libwinpr/sspi/NTLM/ntlm_message.h @@ -0,0 +1,36 @@ +/** + * WinPR: Windows Portable Runtime + * NTLM Security Package (Message) + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_NTLM_MESSAGE_H +#define WINPR_SSPI_NTLM_MESSAGE_H + +#include "ntlm.h" + +SECURITY_STATUS ntlm_read_NegotiateMessage(NTLM_CONTEXT* context, PSecBuffer buffer); +SECURITY_STATUS ntlm_write_NegotiateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer); +SECURITY_STATUS ntlm_read_ChallengeMessage(NTLM_CONTEXT* context, PSecBuffer buffer); +SECURITY_STATUS ntlm_write_ChallengeMessage(NTLM_CONTEXT* context, const PSecBuffer buffer); +SECURITY_STATUS ntlm_read_AuthenticateMessage(NTLM_CONTEXT* context, PSecBuffer buffer); +SECURITY_STATUS ntlm_write_AuthenticateMessage(NTLM_CONTEXT* context, const PSecBuffer buffer); + +SECURITY_STATUS ntlm_server_AuthenticateComplete(NTLM_CONTEXT* context); + +const char* ntlm_get_negotiate_string(UINT32 flag); + +#endif /* WINPR_SSPI_NTLM_MESSAGE_H */ diff --git a/winpr/libwinpr/sspi/Negotiate/negotiate.c b/winpr/libwinpr/sspi/Negotiate/negotiate.c new file mode 100644 index 0000000..7249399 --- /dev/null +++ b/winpr/libwinpr/sspi/Negotiate/negotiate.c @@ -0,0 +1,1660 @@ +/** + * WinPR: Windows Portable Runtime + * Negotiate Security Package + * + * Copyright 2011-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/crt.h> +#include <winpr/wtypes.h> +#include <winpr/assert.h> +#include <winpr/sspi.h> +#include <winpr/tchar.h> +#include <winpr/registry.h> +#include <winpr/build-config.h> +#include <winpr/asn1.h> + +#include "negotiate.h" + +#include "../NTLM/ntlm.h" +#include "../NTLM/ntlm_export.h" +#include "../Kerberos/kerberos.h" +#include "../sspi.h" +#include "../../log.h" +#define TAG WINPR_TAG("negotiate") + +static const char NEGO_REG_KEY[] = + "Software\\" WINPR_VENDOR_STRING "\\" WINPR_PRODUCT_STRING "\\SSPI\\Negotiate"; + +typedef struct +{ + const TCHAR* name; + const SecurityFunctionTableA* table; + const SecurityFunctionTableW* table_w; +} SecPkg; + +struct Mech_st +{ + const WinPrAsn1_OID* oid; + const SecPkg* pkg; + const UINT flags; + const BOOL preferred; +}; + +typedef struct +{ + const Mech* mech; + CredHandle cred; + BOOL valid; +} MechCred; + +const SecPkgInfoA NEGOTIATE_SecPkgInfoA = { + 0x00083BB3, /* fCapabilities */ + 1, /* wVersion */ + 0x0009, /* wRPCID */ + 0x00002FE0, /* cbMaxToken */ + "Negotiate", /* Name */ + "Microsoft Package Negotiator" /* Comment */ +}; + +static WCHAR NEGOTIATE_SecPkgInfoW_NameBuffer[32] = { 0 }; +static WCHAR NEGOTIATE_SecPkgInfoW_CommentBuffer[32] = { 0 }; + +const SecPkgInfoW NEGOTIATE_SecPkgInfoW = { + 0x00083BB3, /* fCapabilities */ + 1, /* wVersion */ + 0x0009, /* wRPCID */ + 0x00002FE0, /* cbMaxToken */ + NEGOTIATE_SecPkgInfoW_NameBuffer, /* Name */ + NEGOTIATE_SecPkgInfoW_CommentBuffer /* Comment */ +}; + +static const WinPrAsn1_OID spnego_OID = { 6, (BYTE*)"\x2b\x06\x01\x05\x05\x02" }; +static const WinPrAsn1_OID kerberos_u2u_OID = { 10, + (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x03" }; +static const WinPrAsn1_OID kerberos_OID = { 9, (BYTE*)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; +static const WinPrAsn1_OID kerberos_wrong_OID = { 9, + (BYTE*)"\x2a\x86\x48\x82\xf7\x12\x01\x02\x02" }; +static const WinPrAsn1_OID ntlm_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a" }; + +static const WinPrAsn1_OID negoex_OID = { 10, (BYTE*)"\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e" }; + +#ifdef WITH_KRB5 +static const SecPkg SecPkgTable[] = { + { KERBEROS_SSP_NAME, &KERBEROS_SecurityFunctionTableA, &KERBEROS_SecurityFunctionTableW }, + { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA, &NTLM_SecurityFunctionTableW } +}; + +static const Mech MechTable[] = { + { &kerberos_u2u_OID, &SecPkgTable[0], ISC_REQ_INTEGRITY | ISC_REQ_USE_SESSION_KEY, TRUE }, + { &kerberos_OID, &SecPkgTable[0], ISC_REQ_INTEGRITY, TRUE }, + { &ntlm_OID, &SecPkgTable[1], 0, FALSE }, +}; +#else +static const SecPkg SecPkgTable[] = { { NTLM_SSP_NAME, &NTLM_SecurityFunctionTableA, + &NTLM_SecurityFunctionTableW } }; + +static const Mech MechTable[] = { + { &ntlm_OID, &SecPkgTable[0], 0, FALSE }, +}; +#endif + +static const size_t MECH_COUNT = sizeof(MechTable) / sizeof(Mech); + +enum NegState +{ + NOSTATE = -1, + ACCEPT_COMPLETED, + ACCEPT_INCOMPLETE, + REJECT, + REQUEST_MIC +}; + +typedef struct +{ + enum NegState negState; + BOOL init; + WinPrAsn1_OID supportedMech; + SecBuffer mechTypes; + SecBuffer mechToken; + SecBuffer mic; +} NegToken; + +static const NegToken empty_neg_token = { NOSTATE, FALSE, { 0, NULL }, + { 0, 0, NULL }, { 0, 0, NULL }, { 0, 0, NULL } }; + +static NEGOTIATE_CONTEXT* negotiate_ContextNew(NEGOTIATE_CONTEXT* init_context) +{ + NEGOTIATE_CONTEXT* context = NULL; + + WINPR_ASSERT(init_context); + + context = calloc(1, sizeof(NEGOTIATE_CONTEXT)); + if (!context) + return NULL; + + if (init_context->spnego) + { + init_context->mechTypes.pvBuffer = malloc(init_context->mechTypes.cbBuffer); + if (!init_context->mechTypes.pvBuffer) + { + free(context); + return NULL; + } + } + + *context = *init_context; + + return context; +} + +static void negotiate_ContextFree(NEGOTIATE_CONTEXT* context) +{ + WINPR_ASSERT(context); + + if (context->mechTypes.pvBuffer) + free(context->mechTypes.pvBuffer); + free(context); +} + +static const char* negotiate_mech_name(const WinPrAsn1_OID* oid) +{ + if (sspi_gss_oid_compare(oid, &spnego_OID)) + return "SPNEGO (1.3.6.1.5.5.2)"; + else if (sspi_gss_oid_compare(oid, &kerberos_u2u_OID)) + return "Kerberos user to user (1.2.840.113554.1.2.2.3)"; + else if (sspi_gss_oid_compare(oid, &kerberos_OID)) + return "Kerberos (1.2.840.113554.1.2.2)"; + else if (sspi_gss_oid_compare(oid, &kerberos_wrong_OID)) + return "Kerberos [wrong OID] (1.2.840.48018.1.2.2)"; + else if (sspi_gss_oid_compare(oid, &ntlm_OID)) + return "NTLM (1.3.6.1.4.1.311.2.2.10)"; + else if (sspi_gss_oid_compare(oid, &negoex_OID)) + return "NegoEx (1.3.6.1.4.1.311.2.2.30)"; + else + return "Unknown mechanism"; +} + +static const Mech* negotiate_GetMechByOID(const WinPrAsn1_OID* oid) +{ + WINPR_ASSERT(oid); + + WinPrAsn1_OID testOid = *oid; + + if (sspi_gss_oid_compare(&testOid, &kerberos_wrong_OID)) + { + testOid.len = kerberos_OID.len; + testOid.data = kerberos_OID.data; + } + + for (size_t i = 0; i < MECH_COUNT; i++) + { + if (sspi_gss_oid_compare(&testOid, MechTable[i].oid)) + return &MechTable[i]; + } + return NULL; +} + +static PSecHandle negotiate_FindCredential(MechCred* creds, const Mech* mech) +{ + WINPR_ASSERT(creds); + + if (!mech) + return NULL; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + MechCred* cred = &creds[i]; + + if (cred->mech == mech) + { + if (cred->valid) + return &cred->cred; + return NULL; + } + } + + return NULL; +} + +static BOOL negotiate_get_dword(HKEY hKey, const char* subkey, DWORD* pdwValue) +{ + DWORD dwValue = 0; + DWORD dwType = 0; + DWORD dwSize = sizeof(dwValue); + LONG rc = RegQueryValueExA(hKey, subkey, NULL, &dwType, (BYTE*)&dwValue, &dwSize); + + if (rc != ERROR_SUCCESS) + return FALSE; + if (dwType != REG_DWORD) + return FALSE; + + *pdwValue = dwValue; + return TRUE; +} + +static BOOL negotiate_get_config_from_auth_package_list(void* pAuthData, BOOL* kerberos, BOOL* ntlm) +{ + char* tok_ctx = NULL; + char* tok_ptr = NULL; + char* PackageList = NULL; + + if (!sspi_CopyAuthPackageListA((const SEC_WINNT_AUTH_IDENTITY_INFO*)pAuthData, &PackageList)) + return FALSE; + + tok_ptr = strtok_s(PackageList, ",", &tok_ctx); + + while (tok_ptr) + { + char* PackageName = tok_ptr; + BOOL PackageInclude = TRUE; + + if (PackageName[0] == '!') + { + PackageName = &PackageName[1]; + PackageInclude = FALSE; + } + + if (!_stricmp(PackageName, "ntlm")) + { + *ntlm = PackageInclude; + } + else if (!_stricmp(PackageName, "kerberos")) + { + *kerberos = PackageInclude; + } + else + { + WLog_WARN(TAG, "Unknown authentication package name: %s", PackageName); + } + + tok_ptr = strtok_s(NULL, ",", &tok_ctx); + } + + free(PackageList); + return TRUE; +} + +static BOOL negotiate_get_config(void* pAuthData, BOOL* kerberos, BOOL* ntlm) +{ + HKEY hKey = NULL; + LONG rc = 0; + + WINPR_ASSERT(kerberos); + WINPR_ASSERT(ntlm); + +#if !defined(WITH_KRB5_NO_NTLM_FALLBACK) + *ntlm = TRUE; +#else + *ntlm = FALSE; +#endif + *kerberos = TRUE; + + if (negotiate_get_config_from_auth_package_list(pAuthData, kerberos, ntlm)) + { + return TRUE; // use explicit authentication package list + } + + rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, NEGO_REG_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + if (rc == ERROR_SUCCESS) + { + DWORD dwValue = 0; + + if (negotiate_get_dword(hKey, "kerberos", &dwValue)) + *kerberos = (dwValue != 0) ? TRUE : FALSE; + +#if !defined(WITH_KRB5_NO_NTLM_FALLBACK) + if (negotiate_get_dword(hKey, "ntlm", &dwValue)) + *ntlm = (dwValue != 0) ? TRUE : FALSE; +#endif + + RegCloseKey(hKey); + } + + return TRUE; +} + +static BOOL negotiate_write_neg_token(PSecBuffer output_buffer, NegToken* token) +{ + WINPR_ASSERT(output_buffer); + WINPR_ASSERT(token); + + BOOL ret = FALSE; + WinPrAsn1Encoder* enc = NULL; + WinPrAsn1_MemoryChunk mechTypes = { token->mechTypes.cbBuffer, token->mechTypes.pvBuffer }; + WinPrAsn1_OctetString mechToken = { token->mechToken.cbBuffer, token->mechToken.pvBuffer }; + WinPrAsn1_OctetString mechListMic = { token->mic.cbBuffer, token->mic.pvBuffer }; + wStream s; + size_t len = 0; + + enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return FALSE; + + /* For NegTokenInit wrap in an initialContextToken */ + if (token->init) + { + /* InitialContextToken [APPLICATION 0] IMPLICIT SEQUENCE */ + if (!WinPrAsn1EncAppContainer(enc, 0)) + goto cleanup; + + /* thisMech MechType OID */ + if (!WinPrAsn1EncOID(enc, &spnego_OID)) + goto cleanup; + } + + /* innerContextToken [0] NegTokenInit or [1] NegTokenResp */ + if (!WinPrAsn1EncContextualSeqContainer(enc, token->init ? 0 : 1)) + goto cleanup; + + WLog_DBG(TAG, token->init ? "Writing negTokenInit..." : "Writing negTokenResp..."); + + /* mechTypes [0] MechTypeList (mechTypes already contains the SEQUENCE tag) */ + if (token->init) + { + if (!WinPrAsn1EncContextualRawContent(enc, 0, &mechTypes)) + goto cleanup; + WLog_DBG(TAG, "\tmechTypes [0] (%li bytes)", token->mechTypes.cbBuffer); + } + /* negState [0] ENUMERATED */ + else if (token->negState != NOSTATE) + { + if (!WinPrAsn1EncContextualEnumerated(enc, 0, token->negState)) + goto cleanup; + WLog_DBG(TAG, "\tnegState [0] (%d)", token->negState); + } + + /* supportedMech [1] OID */ + if (token->supportedMech.len) + { + if (!WinPrAsn1EncContextualOID(enc, 1, &token->supportedMech)) + goto cleanup; + WLog_DBG(TAG, "\tsupportedMech [1] (%s)", negotiate_mech_name(&token->supportedMech)); + } + + /* mechToken [2] OCTET STRING */ + if (token->mechToken.cbBuffer) + { + if (WinPrAsn1EncContextualOctetString(enc, 2, &mechToken) == 0) + goto cleanup; + WLog_DBG(TAG, "\tmechToken [2] (%li bytes)", token->mechToken.cbBuffer); + } + + /* mechListMIC [3] OCTET STRING */ + if (token->mic.cbBuffer) + { + if (WinPrAsn1EncContextualOctetString(enc, 3, &mechListMic) == 0) + goto cleanup; + WLog_DBG(TAG, "\tmechListMIC [3] (%li bytes)", token->mic.cbBuffer); + } + + /* NegTokenInit or NegTokenResp */ + if (!WinPrAsn1EncEndContainer(enc)) + goto cleanup; + + if (token->init) + { + /* initialContextToken */ + if (!WinPrAsn1EncEndContainer(enc)) + goto cleanup; + } + + if (!WinPrAsn1EncStreamSize(enc, &len) || len > output_buffer->cbBuffer) + goto cleanup; + + Stream_StaticInit(&s, output_buffer->pvBuffer, len); + + if (WinPrAsn1EncToStream(enc, &s)) + { + output_buffer->cbBuffer = len; + ret = TRUE; + } + +cleanup: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +static BOOL negotiate_read_neg_token(PSecBuffer input, NegToken* token) +{ + WinPrAsn1Decoder dec; + WinPrAsn1Decoder dec2; + WinPrAsn1_OID oid; + WinPrAsn1_tagId contextual = 0; + WinPrAsn1_tag tag = 0; + size_t len = 0; + WinPrAsn1_OctetString octet_string; + BOOL err = 0; + + WINPR_ASSERT(input); + WINPR_ASSERT(token); + + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input->pvBuffer, input->cbBuffer); + + if (!WinPrAsn1DecPeekTag(&dec, &tag)) + return FALSE; + + if (tag == 0x60) + { + /* initialContextToken [APPLICATION 0] */ + if (!WinPrAsn1DecReadApp(&dec, &tag, &dec2) || tag != 0) + return FALSE; + dec = dec2; + + /* thisMech OID */ + if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE)) + return FALSE; + + if (!sspi_gss_oid_compare(&spnego_OID, &oid)) + return FALSE; + + /* [0] NegTokenInit */ + if (!WinPrAsn1DecReadContextualSequence(&dec, 0, &err, &dec2)) + return FALSE; + + token->init = TRUE; + } + /* [1] NegTokenResp */ + else if (!WinPrAsn1DecReadContextualSequence(&dec, 1, &err, &dec2)) + return FALSE; + dec = dec2; + + WLog_DBG(TAG, token->init ? "Reading negTokenInit..." : "Reading negTokenResp..."); + + /* Read NegTokenResp sequence members */ + do + { + if (!WinPrAsn1DecReadContextualTag(&dec, &contextual, &dec2)) + return FALSE; + + switch (contextual) + { + case 0: + if (token->init) + { + /* mechTypes [0] MechTypeList */ + wStream s = WinPrAsn1DecGetStream(&dec2); + token->mechTypes.BufferType = SECBUFFER_TOKEN; + token->mechTypes.cbBuffer = Stream_Length(&s); + token->mechTypes.pvBuffer = Stream_Buffer(&s); + WLog_DBG(TAG, "\tmechTypes [0] (%li bytes)", token->mechTypes.cbBuffer); + } + else + { + /* negState [0] ENUMERATED */ + WinPrAsn1_ENUMERATED rd = 0; + if (!WinPrAsn1DecReadEnumerated(&dec2, &rd)) + return FALSE; + token->negState = rd; + WLog_DBG(TAG, "\tnegState [0] (%d)", token->negState); + } + break; + case 1: + if (token->init) + { + /* reqFlags [1] ContextFlags BIT STRING (ignored) */ + if (!WinPrAsn1DecPeekTagAndLen(&dec2, &tag, &len) || (tag != ER_TAG_BIT_STRING)) + return FALSE; + WLog_DBG(TAG, "\treqFlags [1] (%li bytes)", len); + } + else + { + /* supportedMech [1] MechType */ + if (!WinPrAsn1DecReadOID(&dec2, &token->supportedMech, FALSE)) + return FALSE; + WLog_DBG(TAG, "\tsupportedMech [1] (%s)", + negotiate_mech_name(&token->supportedMech)); + } + break; + case 2: + /* mechToken [2] OCTET STRING */ + if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE)) + return FALSE; + token->mechToken.cbBuffer = octet_string.len; + token->mechToken.pvBuffer = octet_string.data; + token->mechToken.BufferType = SECBUFFER_TOKEN; + WLog_DBG(TAG, "\tmechToken [2] (%li bytes)", octet_string.len); + break; + case 3: + /* mechListMic [3] OCTET STRING */ + if (!WinPrAsn1DecReadOctetString(&dec2, &octet_string, FALSE)) + return FALSE; + token->mic.cbBuffer = octet_string.len; + token->mic.pvBuffer = octet_string.data; + token->mic.BufferType = SECBUFFER_TOKEN; + WLog_DBG(TAG, "\tmechListMIC [3] (%li bytes)", octet_string.len); + break; + default: + WLog_ERR(TAG, "unknown contextual item %d", contextual); + return FALSE; + } + } while (WinPrAsn1DecPeekTag(&dec, &tag)); + + return TRUE; +} + +static SECURITY_STATUS negotiate_mic_exchange(NEGOTIATE_CONTEXT* context, NegToken* input_token, + NegToken* output_token, PSecBuffer output_buffer) +{ + SecBuffer mic_buffers[2] = { 0 }; + SecBufferDesc mic_buffer_desc = { SECBUFFER_VERSION, 2, mic_buffers }; + SECURITY_STATUS status = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(input_token); + WINPR_ASSERT(output_token); + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + + const SecurityFunctionTableA* table = context->mech->pkg->table; + WINPR_ASSERT(table); + + mic_buffers[0] = context->mechTypes; + + /* Verify MIC if we received one */ + if (input_token->mic.cbBuffer > 0) + { + mic_buffers[1] = input_token->mic; + + status = table->VerifySignature(&context->sub_context, &mic_buffer_desc, 0, 0); + if (status != SEC_E_OK) + return status; + + output_token->negState = ACCEPT_COMPLETED; + } + + /* If peer expects a MIC then generate it */ + if (input_token->negState != ACCEPT_COMPLETED) + { + /* Store the mic token after the mech token in the output buffer */ + output_token->mic.BufferType = SECBUFFER_TOKEN; + if (output_buffer) + { + output_token->mic.cbBuffer = output_buffer->cbBuffer - output_token->mechToken.cbBuffer; + output_token->mic.pvBuffer = + (BYTE*)output_buffer->pvBuffer + output_token->mechToken.cbBuffer; + } + mic_buffers[1] = output_token->mic; + + status = table->MakeSignature(&context->sub_context, 0, &mic_buffer_desc, 0); + if (status != SEC_E_OK) + return status; + + output_token->mic = mic_buffers[1]; + } + + /* When using NTLM cipher states need to be reset after mic exchange */ + if (_tcscmp(sspi_SecureHandleGetUpperPointer(&context->sub_context), NTLM_SSP_NAME) == 0) + { + if (!ntlm_reset_cipher_state(&context->sub_context)) + return SEC_E_INTERNAL_ERROR; + } + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + NEGOTIATE_CONTEXT* context = NULL; + NEGOTIATE_CONTEXT init_context = { 0 }; + MechCred* creds = NULL; + PCtxtHandle sub_context = NULL; + PCredHandle sub_cred = NULL; + NegToken input_token = empty_neg_token; + NegToken output_token = empty_neg_token; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + PSecBuffer bindings_buffer = NULL; + SecBuffer mech_input_buffers[2] = { 0 }; + SecBufferDesc mech_input = { SECBUFFER_VERSION, 2, mech_input_buffers }; + SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken }; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + SECURITY_STATUS sub_status = SEC_E_INTERNAL_ERROR; + WinPrAsn1Encoder* enc = NULL; + wStream s; + const Mech* mech = NULL; + + if (!phCredential || !SecIsValidHandle(phCredential)) + return SEC_E_NO_CREDENTIALS; + + creds = sspi_SecureHandleGetLowerPointer(phCredential); + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = sspi_SecureHandleGetLowerPointer(phContext); + + if (pInput) + { + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + bindings_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_CHANNEL_BINDINGS); + } + if (pOutput) + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!context) + { + enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return SEC_E_INSUFFICIENT_MEMORY; + + if (!WinPrAsn1EncSeqContainer(enc)) + goto cleanup; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + MechCred* cred = &creds[i]; + const SecPkg* pkg = MechTable[i].pkg; + WINPR_ASSERT(pkg); + WINPR_ASSERT(pkg->table_w); + + if (!cred->valid) + continue; + + /* Send an optimistic token for the first valid mechanism */ + if (!init_context.mech) + { + /* Use the output buffer to store the optimistic token */ + CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer)); + + if (bindings_buffer) + mech_input_buffers[0] = *bindings_buffer; + + WINPR_ASSERT(pkg->table_w->InitializeSecurityContextW); + sub_status = pkg->table_w->InitializeSecurityContextW( + &cred->cred, NULL, pszTargetName, fContextReq | cred->mech->flags, Reserved1, + TargetDataRep, &mech_input, Reserved2, &init_context.sub_context, &mech_output, + pfContextAttr, ptsExpiry); + + /* If the mechanism failed we can't use it; skip */ + if (IsSecurityStatusError(sub_status)) + { + if (SecIsValidHandle(&init_context.sub_context)) + { + WINPR_ASSERT(pkg->table_w->DeleteSecurityContext); + pkg->table_w->DeleteSecurityContext(&init_context.sub_context); + } + cred->valid = FALSE; + continue; + } + + init_context.mech = cred->mech; + } + + if (!WinPrAsn1EncOID(enc, cred->mech->oid)) + goto cleanup; + WLog_DBG(TAG, "Available mechanism: %s", negotiate_mech_name(cred->mech->oid)); + } + + /* No usable mechanisms were found */ + if (!init_context.mech) + goto cleanup; + + /* If the only available mech is NTLM use it directly otherwise use spnego */ + if (init_context.mech->oid == &ntlm_OID) + { + init_context.spnego = FALSE; + output_buffer->cbBuffer = output_token.mechToken.cbBuffer; + WLog_DBG(TAG, "Using direct NTLM"); + } + else + { + init_context.spnego = TRUE; + init_context.mechTypes.BufferType = SECBUFFER_DATA; + init_context.mechTypes.cbBuffer = WinPrAsn1EncEndContainer(enc); + } + + /* Allocate memory for the new context */ + context = negotiate_ContextNew(&init_context); + if (!context) + { + init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context); + WinPrAsn1Encoder_Free(&enc); + return SEC_E_INSUFFICIENT_MEMORY; + } + + sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME); + sspi_SecureHandleSetLowerPointer(phNewContext, context); + + if (!context->spnego) + { + status = sub_status; + goto cleanup; + } + + /* Write mechTypesList */ + Stream_StaticInit(&s, context->mechTypes.pvBuffer, context->mechTypes.cbBuffer); + if (!WinPrAsn1EncToStream(enc, &s)) + goto cleanup; + + output_token.mechTypes.cbBuffer = context->mechTypes.cbBuffer; + output_token.mechTypes.pvBuffer = context->mechTypes.pvBuffer; + output_token.init = TRUE; + + if (sub_status == SEC_E_OK) + context->state = NEGOTIATE_STATE_FINAL_OPTIMISTIC; + } + else + { + if (!input_buffer) + return SEC_E_INVALID_TOKEN; + + sub_context = &context->sub_context; + sub_cred = negotiate_FindCredential(creds, context->mech); + + if (!context->spnego) + { + return context->mech->pkg->table_w->InitializeSecurityContextW( + sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1, + TargetDataRep, pInput, Reserved2, sub_context, pOutput, pfContextAttr, ptsExpiry); + } + + if (!negotiate_read_neg_token(input_buffer, &input_token)) + return SEC_E_INVALID_TOKEN; + + /* On first response check if the server doesn't like out prefered mech */ + if (context->state < NEGOTIATE_STATE_NEGORESP && input_token.supportedMech.len && + !sspi_gss_oid_compare(&input_token.supportedMech, context->mech->oid)) + { + mech = negotiate_GetMechByOID(&input_token.supportedMech); + if (!mech) + return SEC_E_INVALID_TOKEN; + + /* Make sure the specified mech is supported and get the appropriate credential */ + sub_cred = negotiate_FindCredential(creds, mech); + if (!sub_cred) + return SEC_E_INVALID_TOKEN; + + /* Clean up the optimistic mech */ + context->mech->pkg->table_w->DeleteSecurityContext(&context->sub_context); + sub_context = NULL; + + context->mech = mech; + context->mic = TRUE; + } + + /* Check neg_state (required on first response) */ + if (context->state < NEGOTIATE_STATE_NEGORESP) + { + switch (input_token.negState) + { + case NOSTATE: + return SEC_E_INVALID_TOKEN; + case REJECT: + return SEC_E_LOGON_DENIED; + case REQUEST_MIC: + context->mic = TRUE; + /* fallthrough */ + WINPR_FALLTHROUGH + case ACCEPT_INCOMPLETE: + context->state = NEGOTIATE_STATE_NEGORESP; + break; + case ACCEPT_COMPLETED: + if (context->state == NEGOTIATE_STATE_INITIAL) + context->state = NEGOTIATE_STATE_NEGORESP; + else + context->state = NEGOTIATE_STATE_FINAL; + break; + } + + WLog_DBG(TAG, "Negotiated mechanism: %s", negotiate_mech_name(context->mech->oid)); + } + + if (context->state == NEGOTIATE_STATE_NEGORESP) + { + /* Store the mech token in the output buffer */ + CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer)); + + mech_input_buffers[0] = input_token.mechToken; + if (bindings_buffer) + mech_input_buffers[1] = *bindings_buffer; + + status = context->mech->pkg->table_w->InitializeSecurityContextW( + sub_cred, sub_context, pszTargetName, fContextReq | context->mech->flags, Reserved1, + TargetDataRep, input_token.mechToken.cbBuffer ? &mech_input : NULL, Reserved2, + &context->sub_context, &mech_output, pfContextAttr, ptsExpiry); + + if (IsSecurityStatusError(status)) + return status; + } + + if (status == SEC_E_OK) + { + if (output_token.mechToken.cbBuffer > 0) + context->state = NEGOTIATE_STATE_MIC; + else + context->state = NEGOTIATE_STATE_FINAL; + } + + /* Check if the acceptor sent its final token without a mic */ + if (context->state == NEGOTIATE_STATE_FINAL && input_token.mic.cbBuffer == 0) + { + if (context->mic || input_token.negState != ACCEPT_COMPLETED) + return SEC_E_INVALID_TOKEN; + + if (output_buffer) + output_buffer->cbBuffer = 0; + return SEC_E_OK; + } + + if ((context->state == NEGOTIATE_STATE_MIC && context->mic) || + context->state == NEGOTIATE_STATE_FINAL) + { + status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer); + if (status != SEC_E_OK) + return status; + } + } + + if (input_token.negState == ACCEPT_COMPLETED) + { + if (output_buffer) + output_buffer->cbBuffer = 0; + return SEC_E_OK; + } + + if (output_token.negState == ACCEPT_COMPLETED) + status = SEC_E_OK; + else + status = SEC_I_CONTINUE_NEEDED; + + if (!negotiate_write_neg_token(output_buffer, &output_token)) + status = SEC_E_INTERNAL_ERROR; + +cleanup: + WinPrAsn1Encoder_Free(&enc); + return status; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SEC_WCHAR* pszTargetNameW = NULL; + + if (pszTargetName) + { + pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL); + if (!pszTargetNameW) + return SEC_E_INTERNAL_ERROR; + } + + status = negotiate_InitializeSecurityContextW( + phCredential, phContext, pszTargetNameW, fContextReq, Reserved1, TargetDataRep, pInput, + Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry); + free(pszTargetNameW); + return status; +} + +static const Mech* guessMech(PSecBuffer input_buffer, BOOL* spNego, WinPrAsn1_OID* oid) +{ + WinPrAsn1Decoder decoder; + WinPrAsn1Decoder appDecoder; + WinPrAsn1_tagId tag = 0; + + *spNego = FALSE; + + /* Check for NTLM token */ + if (input_buffer->cbBuffer >= 8 && strncmp(input_buffer->pvBuffer, "NTLMSSP", 8) == 0) + { + *oid = ntlm_OID; + return negotiate_GetMechByOID(&ntlm_OID); + } + + /* Read initialContextToken or raw Kerberos token */ + WinPrAsn1Decoder_InitMem(&decoder, WINPR_ASN1_DER, input_buffer->pvBuffer, + input_buffer->cbBuffer); + + if (!WinPrAsn1DecReadApp(&decoder, &tag, &appDecoder) || tag != 0) + return NULL; + + if (!WinPrAsn1DecReadOID(&appDecoder, oid, FALSE)) + return NULL; + + if (sspi_gss_oid_compare(oid, &spnego_OID)) + { + *spNego = TRUE; + return NULL; + } + + return negotiate_GetMechByOID(oid); +} + +static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext( + PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq, + ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, + PTimeStamp ptsTimeStamp) +{ + NEGOTIATE_CONTEXT* context = NULL; + NEGOTIATE_CONTEXT init_context = { 0 }; + MechCred* creds = NULL; + PCredHandle sub_cred = NULL; + NegToken input_token = empty_neg_token; + NegToken output_token = empty_neg_token; + PSecBuffer input_buffer = NULL; + PSecBuffer output_buffer = NULL; + SecBufferDesc mech_input = { SECBUFFER_VERSION, 1, &input_token.mechToken }; + SecBufferDesc mech_output = { SECBUFFER_VERSION, 1, &output_token.mechToken }; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + WinPrAsn1Decoder dec; + WinPrAsn1Decoder dec2; + WinPrAsn1_tagId tag = 0; + WinPrAsn1_OID oid = { 0 }; + const Mech* first_mech = NULL; + + if (!phCredential || !SecIsValidHandle(phCredential)) + return SEC_E_NO_CREDENTIALS; + + creds = sspi_SecureHandleGetLowerPointer(phCredential); + + if (!pInput) + return SEC_E_INVALID_TOKEN; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = sspi_SecureHandleGetLowerPointer(phContext); + + input_buffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + if (pOutput) + output_buffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!context) + { + init_context.mech = guessMech(input_buffer, &init_context.spnego, &oid); + if (!init_context.mech && !init_context.spnego) + return SEC_E_INVALID_TOKEN; + + WLog_DBG(TAG, "Mechanism: %s", negotiate_mech_name(&oid)); + + if (init_context.spnego) + { + /* Process spnego token */ + if (!negotiate_read_neg_token(input_buffer, &input_token)) + return SEC_E_INVALID_TOKEN; + + /* First token must be negoTokenInit and must contain a mechList */ + if (!input_token.init || input_token.mechTypes.cbBuffer == 0) + return SEC_E_INVALID_TOKEN; + + init_context.mechTypes.BufferType = SECBUFFER_DATA; + init_context.mechTypes.cbBuffer = input_token.mechTypes.cbBuffer; + + /* Prepare to read mechList */ + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, input_token.mechTypes.pvBuffer, + input_token.mechTypes.cbBuffer); + + if (!WinPrAsn1DecReadSequence(&dec, &dec2)) + return SEC_E_INVALID_TOKEN; + dec = dec2; + + /* If an optimistic token was provided pass it into the first mech */ + if (input_token.mechToken.cbBuffer) + { + if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE)) + return SEC_E_INVALID_TOKEN; + + init_context.mech = negotiate_GetMechByOID(&oid); + + if (init_context.mech) + { + if (output_buffer) + output_token.mechToken = *output_buffer; + WLog_DBG(TAG, "Requested mechanism: %s", + negotiate_mech_name(init_context.mech->oid)); + } + } + } + + if (init_context.mech) + { + sub_cred = negotiate_FindCredential(creds, init_context.mech); + + status = init_context.mech->pkg->table->AcceptSecurityContext( + sub_cred, NULL, init_context.spnego ? &mech_input : pInput, fContextReq, + TargetDataRep, &init_context.sub_context, + init_context.spnego ? &mech_output : pOutput, pfContextAttr, ptsTimeStamp); + } + + if (IsSecurityStatusError(status)) + { + if (!init_context.spnego) + return status; + + init_context.mic = TRUE; + first_mech = init_context.mech; + init_context.mech = NULL; + output_token.mechToken.cbBuffer = 0; + } + + while (!init_context.mech && WinPrAsn1DecPeekTag(&dec, &tag)) + { + /* Read each mechanism */ + if (!WinPrAsn1DecReadOID(&dec, &oid, FALSE)) + return SEC_E_INVALID_TOKEN; + + init_context.mech = negotiate_GetMechByOID(&oid); + WLog_DBG(TAG, "Requested mechanism: %s", negotiate_mech_name(&oid)); + + /* Microsoft may send two versions of the kerberos OID */ + if (init_context.mech == first_mech) + init_context.mech = NULL; + + if (init_context.mech && !negotiate_FindCredential(creds, init_context.mech)) + init_context.mech = NULL; + } + + if (!init_context.mech) + return SEC_E_INTERNAL_ERROR; + + context = negotiate_ContextNew(&init_context); + if (!context) + { + if (!IsSecurityStatusError(status)) + init_context.mech->pkg->table->DeleteSecurityContext(&init_context.sub_context); + return SEC_E_INSUFFICIENT_MEMORY; + } + + sspi_SecureHandleSetUpperPointer(phNewContext, NEGO_SSP_NAME); + sspi_SecureHandleSetLowerPointer(phNewContext, context); + + if (!init_context.spnego) + return status; + + CopyMemory(init_context.mechTypes.pvBuffer, input_token.mechTypes.pvBuffer, + input_token.mechTypes.cbBuffer); + + if (!context->mech->preferred) + { + output_token.negState = REQUEST_MIC; + context->mic = TRUE; + } + else + { + output_token.negState = ACCEPT_INCOMPLETE; + } + + if (status == SEC_E_OK) + context->state = NEGOTIATE_STATE_FINAL; + else + context->state = NEGOTIATE_STATE_NEGORESP; + + output_token.supportedMech = oid; + WLog_DBG(TAG, "Accepted mechanism: %s", negotiate_mech_name(&output_token.supportedMech)); + } + else + { + sub_cred = negotiate_FindCredential(creds, context->mech); + if (!sub_cred) + return SEC_E_NO_CREDENTIALS; + + if (!context->spnego) + { + return context->mech->pkg->table->AcceptSecurityContext( + sub_cred, &context->sub_context, pInput, fContextReq, TargetDataRep, + &context->sub_context, pOutput, pfContextAttr, ptsTimeStamp); + } + + if (!negotiate_read_neg_token(input_buffer, &input_token)) + return SEC_E_INVALID_TOKEN; + + /* Process the mechanism token */ + if (input_token.mechToken.cbBuffer > 0) + { + if (context->state != NEGOTIATE_STATE_NEGORESP) + return SEC_E_INVALID_TOKEN; + + /* Use the output buffer to store the optimistic token */ + CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer)); + + status = context->mech->pkg->table->AcceptSecurityContext( + sub_cred, &context->sub_context, &mech_input, fContextReq | context->mech->flags, + TargetDataRep, &context->sub_context, &mech_output, pfContextAttr, ptsTimeStamp); + + if (IsSecurityStatusError(status)) + return status; + + if (status == SEC_E_OK) + context->state = NEGOTIATE_STATE_FINAL; + } + else if (context->state == NEGOTIATE_STATE_NEGORESP) + return SEC_E_INVALID_TOKEN; + } + + if (context->state == NEGOTIATE_STATE_FINAL) + { + /* Check if initiator sent the last mech token without a mic and a mic was required */ + if (context->mic && output_token.mechToken.cbBuffer == 0 && input_token.mic.cbBuffer == 0) + return SEC_E_INVALID_TOKEN; + + if (context->mic || input_token.mic.cbBuffer > 0) + { + status = negotiate_mic_exchange(context, &input_token, &output_token, output_buffer); + if (status != SEC_E_OK) + return status; + } + else + output_token.negState = ACCEPT_COMPLETED; + } + + if (input_token.negState == ACCEPT_COMPLETED) + { + if (output_buffer) + output_buffer->cbBuffer = 0; + return SEC_E_OK; + } + + if (output_token.negState == ACCEPT_COMPLETED) + status = SEC_E_OK; + else + status = SEC_I_CONTINUE_NEEDED; + + if (!negotiate_write_neg_token(output_buffer, &output_token)) + return SEC_E_INTERNAL_ERROR; + + return status; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_CompleteAuthToken(PCtxtHandle phContext, + PSecBufferDesc pToken) +{ + NEGOTIATE_CONTEXT* context = NULL; + SECURITY_STATUS status = SEC_E_OK; + context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->CompleteAuthToken) + status = context->mech->pkg->table->CompleteAuthToken(&context->sub_context, pToken); + + return status; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_DeleteSecurityContext(PCtxtHandle phContext) +{ + NEGOTIATE_CONTEXT* context = NULL; + SECURITY_STATUS status = SEC_E_OK; + context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + const SecPkg* pkg = NULL; + + if (!context) + return SEC_E_INVALID_HANDLE; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + pkg = context->mech->pkg; + + if (pkg->table->DeleteSecurityContext) + status = pkg->table->DeleteSecurityContext(&context->sub_context); + + negotiate_ContextFree(context); + return status; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_ImpersonateSecurityContext(PCtxtHandle phContext) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_RevertSecurityContext(PCtxtHandle phContext) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table_w); + if (context->mech->pkg->table_w->QueryContextAttributesW) + return context->mech->pkg->table_w->QueryContextAttributesW(&context->sub_context, + ulAttribute, pBuffer); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_QueryContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->QueryContextAttributesA) + return context->mech->pkg->table->QueryContextAttributesA(&context->sub_context, + ulAttribute, pBuffer); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table_w); + if (context->mech->pkg->table_w->SetContextAttributesW) + return context->mech->pkg->table_w->SetContextAttributesW(&context->sub_context, + ulAttribute, pBuffer, cbBuffer); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_SetContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->SetContextAttributesA) + return context->mech->pkg->table->SetContextAttributesA(&context->sub_context, ulAttribute, + pBuffer, cbBuffer); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + MechCred* creds = NULL; + BOOL success = FALSE; + SECURITY_STATUS secStatus = 0; + + creds = sspi_SecureHandleGetLowerPointer(phCredential); + + if (!creds) + return SEC_E_INVALID_HANDLE; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + MechCred* cred = &creds[i]; + + WINPR_ASSERT(cred->mech); + WINPR_ASSERT(cred->mech->pkg); + WINPR_ASSERT(cred->mech->pkg->table); + WINPR_ASSERT(cred->mech->pkg->table_w->SetCredentialsAttributesW); + secStatus = cred->mech->pkg->table_w->SetCredentialsAttributesW(&cred->cred, ulAttribute, + pBuffer, cbBuffer); + + if (secStatus == SEC_E_OK) + { + success = TRUE; + } + } + + // return success if at least one submodule accepts the credential attribute + return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION); +} + +static SECURITY_STATUS SEC_ENTRY negotiate_SetCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + MechCred* creds = NULL; + BOOL success = FALSE; + SECURITY_STATUS secStatus = 0; + + creds = sspi_SecureHandleGetLowerPointer(phCredential); + + if (!creds) + return SEC_E_INVALID_HANDLE; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + MechCred* cred = &creds[i]; + + if (!cred->valid) + continue; + + WINPR_ASSERT(cred->mech); + WINPR_ASSERT(cred->mech->pkg); + WINPR_ASSERT(cred->mech->pkg->table); + WINPR_ASSERT(cred->mech->pkg->table->SetCredentialsAttributesA); + secStatus = cred->mech->pkg->table->SetCredentialsAttributesA(&cred->cred, ulAttribute, + pBuffer, cbBuffer); + + if (secStatus == SEC_E_OK) + { + success = TRUE; + } + } + + // return success if at least one submodule accepts the credential attribute + return (success ? SEC_E_OK : SEC_E_UNSUPPORTED_FUNCTION); +} + +static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + BOOL kerberos = 0; + BOOL ntlm = 0; + + if (!negotiate_get_config(pAuthData, &kerberos, &ntlm)) + return SEC_E_INTERNAL_ERROR; + + MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred)); + + if (!creds) + return SEC_E_INTERNAL_ERROR; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + MechCred* cred = &creds[i]; + const SecPkg* pkg = MechTable[i].pkg; + cred->mech = &MechTable[i]; + + if (!kerberos && _tcscmp(pkg->name, KERBEROS_SSP_NAME) == 0) + continue; + if (!ntlm && _tcscmp(SecPkgTable[i].name, NTLM_SSP_NAME) == 0) + continue; + + WINPR_ASSERT(pkg->table_w); + WINPR_ASSERT(pkg->table_w->AcquireCredentialsHandleW); + if (pkg->table_w->AcquireCredentialsHandleW( + pszPrincipal, pszPackage, fCredentialUse, pvLogonID, pAuthData, pGetKeyFn, + pvGetKeyArgument, &cred->cred, ptsExpiry) != SEC_E_OK) + continue; + + cred->valid = TRUE; + } + + sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + BOOL kerberos = 0; + BOOL ntlm = 0; + + if (!negotiate_get_config(pAuthData, &kerberos, &ntlm)) + return SEC_E_INTERNAL_ERROR; + + MechCred* creds = calloc(MECH_COUNT, sizeof(MechCred)); + + if (!creds) + return SEC_E_INTERNAL_ERROR; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + const SecPkg* pkg = MechTable[i].pkg; + MechCred* cred = &creds[i]; + + cred->mech = &MechTable[i]; + + if (!kerberos && _tcscmp(pkg->name, KERBEROS_SSP_NAME) == 0) + continue; + if (!ntlm && _tcscmp(SecPkgTable[i].name, NTLM_SSP_NAME) == 0) + continue; + + WINPR_ASSERT(pkg->table); + WINPR_ASSERT(pkg->table->AcquireCredentialsHandleA); + if (pkg->table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, + pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument, + &cred->cred, ptsExpiry) != SEC_E_OK) + continue; + + cred->valid = TRUE; + } + + sspi_SecureHandleSetLowerPointer(phCredential, (void*)creds); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)NEGO_SSP_NAME); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + WLog_ERR(TAG, "TODO: Implement"); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_FreeCredentialsHandle(PCredHandle phCredential) +{ + MechCred* creds = NULL; + + creds = sspi_SecureHandleGetLowerPointer(phCredential); + if (!creds) + return SEC_E_INVALID_HANDLE; + + for (size_t i = 0; i < MECH_COUNT; i++) + { + MechCred* cred = &creds[i]; + + WINPR_ASSERT(cred->mech); + WINPR_ASSERT(cred->mech->pkg); + WINPR_ASSERT(cred->mech->pkg->table); + WINPR_ASSERT(cred->mech->pkg->table->FreeCredentialsHandle); + cred->mech->pkg->table->FreeCredentialsHandle(&cred->cred); + } + free(creds); + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, + ULONG MessageSeqNo) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (context->mic) + MessageSeqNo++; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->EncryptMessage) + return context->mech->pkg->table->EncryptMessage(&context->sub_context, fQOP, pMessage, + MessageSeqNo); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_DecryptMessage(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (context->mic) + MessageSeqNo++; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->DecryptMessage) + return context->mech->pkg->table->DecryptMessage(&context->sub_context, pMessage, + MessageSeqNo, pfQOP); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, + ULONG MessageSeqNo) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (context->mic) + MessageSeqNo++; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->MakeSignature) + return context->mech->pkg->table->MakeSignature(&context->sub_context, fQOP, pMessage, + MessageSeqNo); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY negotiate_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ + NEGOTIATE_CONTEXT* context = (NEGOTIATE_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + if (context->mic) + MessageSeqNo++; + + WINPR_ASSERT(context->mech); + WINPR_ASSERT(context->mech->pkg); + WINPR_ASSERT(context->mech->pkg->table); + if (context->mech->pkg->table->VerifySignature) + return context->mech->pkg->table->VerifySignature(&context->sub_context, pMessage, + MessageSeqNo, pfQOP); + + return SEC_E_UNSUPPORTED_FUNCTION; +} + +const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + negotiate_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + negotiate_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + negotiate_InitializeSecurityContextA, /* InitializeSecurityContext */ + negotiate_AcceptSecurityContext, /* AcceptSecurityContext */ + negotiate_CompleteAuthToken, /* CompleteAuthToken */ + negotiate_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + negotiate_QueryContextAttributesA, /* QueryContextAttributes */ + negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + negotiate_RevertSecurityContext, /* RevertSecurityContext */ + negotiate_MakeSignature, /* MakeSignature */ + negotiate_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + negotiate_EncryptMessage, /* EncryptMessage */ + negotiate_DecryptMessage, /* DecryptMessage */ + negotiate_SetContextAttributesA, /* SetContextAttributes */ + negotiate_SetCredentialsAttributesA, /* SetCredentialsAttributes */ +}; + +const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + negotiate_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + negotiate_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + negotiate_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + negotiate_InitializeSecurityContextW, /* InitializeSecurityContext */ + negotiate_AcceptSecurityContext, /* AcceptSecurityContext */ + negotiate_CompleteAuthToken, /* CompleteAuthToken */ + negotiate_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + negotiate_QueryContextAttributesW, /* QueryContextAttributes */ + negotiate_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + negotiate_RevertSecurityContext, /* RevertSecurityContext */ + negotiate_MakeSignature, /* MakeSignature */ + negotiate_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + negotiate_EncryptMessage, /* EncryptMessage */ + negotiate_DecryptMessage, /* DecryptMessage */ + negotiate_SetContextAttributesW, /* SetContextAttributes */ + negotiate_SetCredentialsAttributesW, /* SetCredentialsAttributes */ +}; + +BOOL NEGOTIATE_init(void) +{ + InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Name, NEGOTIATE_SecPkgInfoW_NameBuffer, + ARRAYSIZE(NEGOTIATE_SecPkgInfoW_NameBuffer)); + InitializeConstWCharFromUtf8(NEGOTIATE_SecPkgInfoA.Comment, NEGOTIATE_SecPkgInfoW_CommentBuffer, + ARRAYSIZE(NEGOTIATE_SecPkgInfoW_CommentBuffer)); + + return TRUE; +} diff --git a/winpr/libwinpr/sspi/Negotiate/negotiate.h b/winpr/libwinpr/sspi/Negotiate/negotiate.h new file mode 100644 index 0000000..767e30a --- /dev/null +++ b/winpr/libwinpr/sspi/Negotiate/negotiate.h @@ -0,0 +1,57 @@ +/** + * WinPR: Windows Portable Runtime + * Negotiate Security Package + * + * Copyright 2011-2012 Jiten Pathy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_NEGOTIATE_PRIVATE_H +#define WINPR_SSPI_NEGOTIATE_PRIVATE_H + +#include <winpr/sspi.h> + +#include "../sspi.h" + +#define NTLM_OID "1.3.6.1.4.1.311.2.2.10" + +typedef enum +{ + NEGOTIATE_STATE_INITIAL, + NEGOTIATE_STATE_FINAL_OPTIMISTIC, + NEGOTIATE_STATE_NEGORESP, + NEGOTIATE_STATE_MIC, + NEGOTIATE_STATE_FINAL, +} NEGOTIATE_STATE; + +typedef struct Mech_st Mech; + +typedef struct +{ + NEGOTIATE_STATE state; + CtxtHandle sub_context; + SecBuffer mechTypes; + const Mech* mech; + BOOL mic; + BOOL spnego; +} NEGOTIATE_CONTEXT; + +extern const SecPkgInfoA NEGOTIATE_SecPkgInfoA; +extern const SecPkgInfoW NEGOTIATE_SecPkgInfoW; +extern const SecurityFunctionTableA NEGOTIATE_SecurityFunctionTableA; +extern const SecurityFunctionTableW NEGOTIATE_SecurityFunctionTableW; + +BOOL NEGOTIATE_init(void); + +#endif /* WINPR_SSPI_NEGOTIATE_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/Schannel/schannel.c b/winpr/libwinpr/sspi/Schannel/schannel.c new file mode 100644 index 0000000..45fba37 --- /dev/null +++ b/winpr/libwinpr/sspi/Schannel/schannel.c @@ -0,0 +1,467 @@ +/** + * WinPR: Windows Portable Runtime + * Schannel Security Package + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/crt.h> +#include <winpr/sspi.h> + +#include "schannel.h" + +#include "../sspi.h" +#include "../../log.h" + +static char* SCHANNEL_PACKAGE_NAME = "Schannel"; + +#define TAG WINPR_TAG("sspi.Schannel") + +SCHANNEL_CONTEXT* schannel_ContextNew(void) +{ + SCHANNEL_CONTEXT* context = NULL; + context = (SCHANNEL_CONTEXT*)calloc(1, sizeof(SCHANNEL_CONTEXT)); + + if (!context) + return NULL; + + context->openssl = schannel_openssl_new(); + + if (!context->openssl) + { + free(context); + return NULL; + } + + return context; +} + +void schannel_ContextFree(SCHANNEL_CONTEXT* context) +{ + if (!context) + return; + + schannel_openssl_free(context->openssl); + free(context); +} + +static SCHANNEL_CREDENTIALS* schannel_CredentialsNew(void) +{ + SCHANNEL_CREDENTIALS* credentials = NULL; + credentials = (SCHANNEL_CREDENTIALS*)calloc(1, sizeof(SCHANNEL_CREDENTIALS)); + return credentials; +} + +static void schannel_CredentialsFree(SCHANNEL_CREDENTIALS* credentials) +{ + free(credentials); +} + +static ALG_ID schannel_SupportedAlgs[] = { CALG_AES_128, + CALG_AES_256, + CALG_RC4, + CALG_DES, + CALG_3DES, + CALG_MD5, + CALG_SHA1, + CALG_SHA_256, + CALG_SHA_384, + CALG_SHA_512, + CALG_RSA_SIGN, + CALG_DH_EPHEM, + (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RESERVED7 | + 6), /* what is this? */ + CALG_DSS_SIGN, + CALG_ECDSA }; + +static SECURITY_STATUS SEC_ENTRY schannel_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + if (ulAttribute == SECPKG_ATTR_SUPPORTED_ALGS) + { + PSecPkgCred_SupportedAlgs SupportedAlgs = (PSecPkgCred_SupportedAlgs)pBuffer; + SupportedAlgs->cSupportedAlgs = sizeof(schannel_SupportedAlgs) / sizeof(ALG_ID); + SupportedAlgs->palgSupportedAlgs = (ALG_ID*)schannel_SupportedAlgs; + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_CIPHER_STRENGTHS) + { + PSecPkgCred_CipherStrengths CipherStrengths = (PSecPkgCred_CipherStrengths)pBuffer; + CipherStrengths->dwMinimumCipherStrength = 40; + CipherStrengths->dwMaximumCipherStrength = 256; + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_SUPPORTED_PROTOCOLS) + { + PSecPkgCred_SupportedProtocols SupportedProtocols = (PSecPkgCred_SupportedProtocols)pBuffer; + /* Observed SupportedProtocols: 0x208A0 */ + SupportedProtocols->grbitProtocol = (SP_PROT_CLIENTS | SP_PROT_SERVERS); + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY schannel_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, + void* pBuffer) +{ + return schannel_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); +} + +static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SCHANNEL_CREDENTIALS* credentials = NULL; + + if (fCredentialUse == SECPKG_CRED_OUTBOUND) + { + SCHANNEL_CRED* cred = NULL; + credentials = schannel_CredentialsNew(); + credentials->fCredentialUse = fCredentialUse; + cred = (SCHANNEL_CRED*)pAuthData; + + if (cred) + { + CopyMemory(&credentials->cred, cred, sizeof(SCHANNEL_CRED)); + } + + sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)SCHANNEL_PACKAGE_NAME); + return SEC_E_OK; + } + else if (fCredentialUse == SECPKG_CRED_INBOUND) + { + credentials = schannel_CredentialsNew(); + credentials->fCredentialUse = fCredentialUse; + sspi_SecureHandleSetLowerPointer(phCredential, (void*)credentials); + sspi_SecureHandleSetUpperPointer(phCredential, (void*)SCHANNEL_PACKAGE_NAME); + return SEC_E_OK; + } + + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY schannel_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SEC_WCHAR* pszPrincipalW = NULL; + SEC_WCHAR* pszPackageW = NULL; + if (pszPrincipal) + pszPrincipalW = ConvertUtf8ToWCharAlloc(pszPrincipal, NULL); + if (pszPackage) + pszPackageW = ConvertUtf8ToWCharAlloc(pszPackage, NULL); + + status = schannel_AcquireCredentialsHandleW(pszPrincipalW, pszPackageW, fCredentialUse, + pvLogonID, pAuthData, pGetKeyFn, pvGetKeyArgument, + phCredential, ptsExpiry); + free(pszPrincipalW); + free(pszPackageW); + return status; +} + +static SECURITY_STATUS SEC_ENTRY schannel_FreeCredentialsHandle(PCredHandle phCredential) +{ + SCHANNEL_CREDENTIALS* credentials = NULL; + + if (!phCredential) + return SEC_E_INVALID_HANDLE; + + credentials = (SCHANNEL_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + + if (!credentials) + return SEC_E_INVALID_HANDLE; + + schannel_CredentialsFree(credentials); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SCHANNEL_CONTEXT* context = NULL; + SCHANNEL_CREDENTIALS* credentials = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + { + context = schannel_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + credentials = (SCHANNEL_CREDENTIALS*)sspi_SecureHandleGetLowerPointer(phCredential); + context->server = FALSE; + CopyMemory(&context->cred, &credentials->cred, sizeof(SCHANNEL_CRED)); + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, (void*)SCHANNEL_PACKAGE_NAME); + schannel_openssl_client_init(context->openssl); + } + + status = schannel_openssl_client_process_tokens(context->openssl, pInput, pOutput); + return status; +} + +static SECURITY_STATUS SEC_ENTRY schannel_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + SEC_WCHAR* pszTargetNameW = NULL; + + if (pszTargetName != NULL) + { + pszTargetNameW = ConvertUtf8ToWCharAlloc(pszTargetName, NULL); + if (!pszTargetNameW) + return SEC_E_INSUFFICIENT_MEMORY; + } + + status = schannel_InitializeSecurityContextW( + phCredential, phContext, pszTargetNameW, fContextReq, Reserved1, TargetDataRep, pInput, + Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry); + free(pszTargetNameW); + return status; +} + +static SECURITY_STATUS SEC_ENTRY schannel_AcceptSecurityContext( + PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, ULONG fContextReq, + ULONG TargetDataRep, PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, + PTimeStamp ptsTimeStamp) +{ + SECURITY_STATUS status = 0; + SCHANNEL_CONTEXT* context = NULL; + + /* behave like windows SSPIs that don't want empty context */ + if (phContext && !phContext->dwLower && !phContext->dwUpper) + return SEC_E_INVALID_HANDLE; + + context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + { + context = schannel_ContextNew(); + + if (!context) + return SEC_E_INSUFFICIENT_MEMORY; + + context->server = TRUE; + sspi_SecureHandleSetLowerPointer(phNewContext, context); + sspi_SecureHandleSetUpperPointer(phNewContext, (void*)SCHANNEL_PACKAGE_NAME); + schannel_openssl_server_init(context->openssl); + } + + status = schannel_openssl_server_process_tokens(context->openssl, pInput, pOutput); + return status; +} + +static SECURITY_STATUS SEC_ENTRY schannel_DeleteSecurityContext(PCtxtHandle phContext) +{ + SCHANNEL_CONTEXT* context = NULL; + context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + schannel_ContextFree(context); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY schannel_QueryContextAttributes(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + if (!phContext) + return SEC_E_INVALID_HANDLE; + + if (!pBuffer) + return SEC_E_INSUFFICIENT_MEMORY; + + if (ulAttribute == SECPKG_ATTR_SIZES) + { + SecPkgContext_Sizes* Sizes = (SecPkgContext_Sizes*)pBuffer; + Sizes->cbMaxToken = 0x6000; + Sizes->cbMaxSignature = 16; + Sizes->cbBlockSize = 0; + Sizes->cbSecurityTrailer = 16; + return SEC_E_OK; + } + else if (ulAttribute == SECPKG_ATTR_STREAM_SIZES) + { + SecPkgContext_StreamSizes* StreamSizes = (SecPkgContext_StreamSizes*)pBuffer; + StreamSizes->cbHeader = 5; + StreamSizes->cbTrailer = 36; + StreamSizes->cbMaximumMessage = 0x4000; + StreamSizes->cBuffers = 4; + StreamSizes->cbBlockSize = 16; + return SEC_E_OK; + } + + WLog_ERR(TAG, "TODO: Implement ulAttribute=%08" PRIx32, ulAttribute); + return SEC_E_UNSUPPORTED_FUNCTION; +} + +static SECURITY_STATUS SEC_ENTRY schannel_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY schannel_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY schannel_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, + ULONG MessageSeqNo) +{ + SECURITY_STATUS status = 0; + SCHANNEL_CONTEXT* context = NULL; + context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + status = schannel_openssl_encrypt_message(context->openssl, pMessage); + return status; +} + +static SECURITY_STATUS SEC_ENTRY schannel_DecryptMessage(PCtxtHandle phContext, + PSecBufferDesc pMessage, + ULONG MessageSeqNo, ULONG* pfQOP) +{ + SECURITY_STATUS status = 0; + SCHANNEL_CONTEXT* context = NULL; + context = (SCHANNEL_CONTEXT*)sspi_SecureHandleGetLowerPointer(phContext); + + if (!context) + return SEC_E_INVALID_HANDLE; + + status = schannel_openssl_decrypt_message(context->openssl, pMessage); + return status; +} + +const SecurityFunctionTableA SCHANNEL_SecurityFunctionTableA = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + schannel_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + schannel_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + schannel_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + schannel_InitializeSecurityContextA, /* InitializeSecurityContext */ + schannel_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + schannel_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + schannel_QueryContextAttributes, /* QueryContextAttributes */ + NULL, /* ImpersonateSecurityContext */ + NULL, /* RevertSecurityContext */ + schannel_MakeSignature, /* MakeSignature */ + schannel_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + schannel_EncryptMessage, /* EncryptMessage */ + schannel_DecryptMessage, /* DecryptMessage */ + NULL, /* SetContextAttributes */ + NULL, /* SetCredentialsAttributes */ +}; + +const SecurityFunctionTableW SCHANNEL_SecurityFunctionTableW = { + 3, /* dwVersion */ + NULL, /* EnumerateSecurityPackages */ + schannel_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + schannel_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + schannel_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + schannel_InitializeSecurityContextW, /* InitializeSecurityContext */ + schannel_AcceptSecurityContext, /* AcceptSecurityContext */ + NULL, /* CompleteAuthToken */ + schannel_DeleteSecurityContext, /* DeleteSecurityContext */ + NULL, /* ApplyControlToken */ + schannel_QueryContextAttributes, /* QueryContextAttributes */ + NULL, /* ImpersonateSecurityContext */ + NULL, /* RevertSecurityContext */ + schannel_MakeSignature, /* MakeSignature */ + schannel_VerifySignature, /* VerifySignature */ + NULL, /* FreeContextBuffer */ + NULL, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + NULL, /* ExportSecurityContext */ + NULL, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + NULL, /* QuerySecurityContextToken */ + schannel_EncryptMessage, /* EncryptMessage */ + schannel_DecryptMessage, /* DecryptMessage */ + NULL, /* SetContextAttributes */ + NULL, /* SetCredentialsAttributes */ +}; + +const SecPkgInfoA SCHANNEL_SecPkgInfoA = { + 0x000107B3, /* fCapabilities */ + 1, /* wVersion */ + 0x000E, /* wRPCID */ + SCHANNEL_CB_MAX_TOKEN, /* cbMaxToken */ + "Schannel", /* Name */ + "Schannel Security Package" /* Comment */ +}; + +static WCHAR SCHANNEL_SecPkgInfoW_NameBuffer[32] = { 0 }; +static WCHAR SCHANNEL_SecPkgInfoW_CommentBuffer[32] = { 0 }; + +const SecPkgInfoW SCHANNEL_SecPkgInfoW = { + 0x000107B3, /* fCapabilities */ + 1, /* wVersion */ + 0x000E, /* wRPCID */ + SCHANNEL_CB_MAX_TOKEN, /* cbMaxToken */ + SCHANNEL_SecPkgInfoW_NameBuffer, /* Name */ + SCHANNEL_SecPkgInfoW_CommentBuffer /* Comment */ +}; + +BOOL SCHANNEL_init(void) +{ + InitializeConstWCharFromUtf8(SCHANNEL_SecPkgInfoA.Name, SCHANNEL_SecPkgInfoW_NameBuffer, + ARRAYSIZE(SCHANNEL_SecPkgInfoW_NameBuffer)); + InitializeConstWCharFromUtf8(SCHANNEL_SecPkgInfoA.Comment, SCHANNEL_SecPkgInfoW_CommentBuffer, + ARRAYSIZE(SCHANNEL_SecPkgInfoW_CommentBuffer)); + return TRUE; +} diff --git a/winpr/libwinpr/sspi/Schannel/schannel.h b/winpr/libwinpr/sspi/Schannel/schannel.h new file mode 100644 index 0000000..e4d170a --- /dev/null +++ b/winpr/libwinpr/sspi/Schannel/schannel.h @@ -0,0 +1,53 @@ +/** + * WinPR: Windows Portable Runtime + * Schannel Security Package + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_SCHANNEL_PRIVATE_H +#define WINPR_SSPI_SCHANNEL_PRIVATE_H + +#include <winpr/sspi.h> +#include <winpr/schannel.h> + +#include "../sspi.h" + +#include "schannel_openssl.h" + +typedef struct +{ + SCHANNEL_CRED cred; + ULONG fCredentialUse; +} SCHANNEL_CREDENTIALS; + +typedef struct +{ + BOOL server; + SCHANNEL_CRED cred; + SCHANNEL_OPENSSL* openssl; +} SCHANNEL_CONTEXT; + +SCHANNEL_CONTEXT* schannel_ContextNew(void); +void schannel_ContextFree(SCHANNEL_CONTEXT* context); + +extern const SecPkgInfoA SCHANNEL_SecPkgInfoA; +extern const SecPkgInfoW SCHANNEL_SecPkgInfoW; +extern const SecurityFunctionTableA SCHANNEL_SecurityFunctionTableA; +extern const SecurityFunctionTableW SCHANNEL_SecurityFunctionTableW; + +BOOL SCHANNEL_init(void); + +#endif /* WINPR_SSPI_SCHANNEL_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/Schannel/schannel_openssl.c b/winpr/libwinpr/sspi/Schannel/schannel_openssl.c new file mode 100644 index 0000000..63c17d7 --- /dev/null +++ b/winpr/libwinpr/sspi/Schannel/schannel_openssl.c @@ -0,0 +1,649 @@ +/** + * WinPR: Windows Portable Runtime + * Schannel Security Package (OpenSSL) + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include "schannel_openssl.h" + +#ifdef WITH_OPENSSL + +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/ssl.h> +#include <winpr/print.h> +#include <winpr/crypto.h> + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/bio.h> + +struct S_SCHANNEL_OPENSSL +{ + SSL* ssl; + SSL_CTX* ctx; + BOOL connected; + BIO* bioRead; + BIO* bioWrite; + BYTE* ReadBuffer; + BYTE* WriteBuffer; +}; + +#include "../../log.h" +#define TAG WINPR_TAG("sspi.schannel") + +static char* openssl_get_ssl_error_string(int ssl_error) +{ + switch (ssl_error) + { + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + + case SSL_ERROR_SSL: + return "SSL_ERROR_SSL"; + } + + return "SSL_ERROR_UNKNOWN"; +} + +static void schannel_context_cleanup(SCHANNEL_OPENSSL* context) +{ + WINPR_ASSERT(context); + + free(context->ReadBuffer); + context->ReadBuffer = NULL; + + if (context->bioWrite) + BIO_free_all(context->bioWrite); + context->bioWrite = NULL; + + if (context->bioRead) + BIO_free_all(context->bioRead); + context->bioRead = NULL; + + if (context->ssl) + SSL_free(context->ssl); + context->ssl = NULL; + + if (context->ctx) + SSL_CTX_free(context->ctx); + context->ctx = NULL; +} + +static const SSL_METHOD* get_method(BOOL server) +{ + if (server) + { +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + return SSLv23_server_method(); +#else + return TLS_server_method(); +#endif + } + else + { +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + return SSLv23_client_method(); +#else + return TLS_client_method(); +#endif + } +} +int schannel_openssl_client_init(SCHANNEL_OPENSSL* context) +{ + int status = 0; + long options = 0; + context->ctx = SSL_CTX_new(get_method(FALSE)); + + if (!context->ctx) + { + WLog_ERR(TAG, "SSL_CTX_new failed"); + return -1; + } + + /** + * SSL_OP_NO_COMPRESSION: + * + * The Microsoft RDP server does not advertise support + * for TLS compression, but alternative servers may support it. + * This was observed between early versions of the FreeRDP server + * and the FreeRDP client, and caused major performance issues, + * which is why we're disabling it. + */ +#ifdef SSL_OP_NO_COMPRESSION + options |= SSL_OP_NO_COMPRESSION; +#endif + /** + * SSL_OP_TLS_BLOCK_PADDING_BUG: + * + * The Microsoft RDP server does *not* support TLS padding. + * It absolutely needs to be disabled otherwise it won't work. + */ + options |= SSL_OP_TLS_BLOCK_PADDING_BUG; + /** + * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: + * + * Just like TLS padding, the Microsoft RDP server does not + * support empty fragments. This needs to be disabled. + */ + options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + SSL_CTX_set_options(context->ctx, options); + context->ssl = SSL_new(context->ctx); + + if (!context->ssl) + { + WLog_ERR(TAG, "SSL_new failed"); + goto fail; + } + + context->bioRead = BIO_new(BIO_s_mem()); + + if (!context->bioRead) + { + WLog_ERR(TAG, "BIO_new failed"); + goto fail; + } + + status = BIO_set_write_buf_size(context->bioRead, SCHANNEL_CB_MAX_TOKEN); + + if (status != 1) + { + WLog_ERR(TAG, "BIO_set_write_buf_size on bioRead failed"); + goto fail; + } + + context->bioWrite = BIO_new(BIO_s_mem()); + + if (!context->bioWrite) + { + WLog_ERR(TAG, "BIO_new failed"); + goto fail; + } + + status = BIO_set_write_buf_size(context->bioWrite, SCHANNEL_CB_MAX_TOKEN); + + if (status != 1) + { + WLog_ERR(TAG, "BIO_set_write_buf_size on bioWrite failed"); + goto fail; + } + + status = BIO_make_bio_pair(context->bioRead, context->bioWrite); + + if (status != 1) + { + WLog_ERR(TAG, "BIO_make_bio_pair failed"); + goto fail; + } + + SSL_set_bio(context->ssl, context->bioRead, context->bioWrite); + context->ReadBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN); + + if (!context->ReadBuffer) + { + WLog_ERR(TAG, "Failed to allocate ReadBuffer"); + goto fail; + } + + context->WriteBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN); + + if (!context->WriteBuffer) + { + WLog_ERR(TAG, "Failed to allocate ReadBuffer"); + goto fail; + } + + return 0; +fail: + schannel_context_cleanup(context); + return -1; +} + +int schannel_openssl_server_init(SCHANNEL_OPENSSL* context) +{ + int status = 0; + unsigned long options = 0; + + context->ctx = SSL_CTX_new(get_method(TRUE)); + + if (!context->ctx) + { + WLog_ERR(TAG, "SSL_CTX_new failed"); + return -1; + } + + /* + * SSL_OP_NO_SSLv2: + * + * We only want SSLv3 and TLSv1, so disable SSLv2. + * SSLv3 is used by, eg. Microsoft RDC for Mac OS X. + */ + options |= SSL_OP_NO_SSLv2; + /** + * SSL_OP_NO_COMPRESSION: + * + * The Microsoft RDP server does not advertise support + * for TLS compression, but alternative servers may support it. + * This was observed between early versions of the FreeRDP server + * and the FreeRDP client, and caused major performance issues, + * which is why we're disabling it. + */ +#ifdef SSL_OP_NO_COMPRESSION + options |= SSL_OP_NO_COMPRESSION; +#endif + /** + * SSL_OP_TLS_BLOCK_PADDING_BUG: + * + * The Microsoft RDP server does *not* support TLS padding. + * It absolutely needs to be disabled otherwise it won't work. + */ + options |= SSL_OP_TLS_BLOCK_PADDING_BUG; + /** + * SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: + * + * Just like TLS padding, the Microsoft RDP server does not + * support empty fragments. This needs to be disabled. + */ + options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; + SSL_CTX_set_options(context->ctx, options); + +#if defined(WITH_DEBUG_SCHANNEL) + if (SSL_CTX_use_RSAPrivateKey_file(context->ctx, "/tmp/localhost.key", SSL_FILETYPE_PEM) <= 0) + { + WLog_ERR(TAG, "SSL_CTX_use_RSAPrivateKey_file failed"); + goto fail; + } +#endif + + context->ssl = SSL_new(context->ctx); + + if (!context->ssl) + { + WLog_ERR(TAG, "SSL_new failed"); + goto fail; + } + + if (SSL_use_certificate_file(context->ssl, "/tmp/localhost.crt", SSL_FILETYPE_PEM) <= 0) + { + WLog_ERR(TAG, "SSL_use_certificate_file failed"); + goto fail; + } + + context->bioRead = BIO_new(BIO_s_mem()); + + if (!context->bioRead) + { + WLog_ERR(TAG, "BIO_new failed"); + goto fail; + } + + status = BIO_set_write_buf_size(context->bioRead, SCHANNEL_CB_MAX_TOKEN); + + if (status != 1) + { + WLog_ERR(TAG, "BIO_set_write_buf_size failed for bioRead"); + goto fail; + } + + context->bioWrite = BIO_new(BIO_s_mem()); + + if (!context->bioWrite) + { + WLog_ERR(TAG, "BIO_new failed"); + goto fail; + } + + status = BIO_set_write_buf_size(context->bioWrite, SCHANNEL_CB_MAX_TOKEN); + + if (status != 1) + { + WLog_ERR(TAG, "BIO_set_write_buf_size failed for bioWrite"); + goto fail; + } + + status = BIO_make_bio_pair(context->bioRead, context->bioWrite); + + if (status != 1) + { + WLog_ERR(TAG, "BIO_make_bio_pair failed"); + goto fail; + } + + SSL_set_bio(context->ssl, context->bioRead, context->bioWrite); + context->ReadBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN); + + if (!context->ReadBuffer) + { + WLog_ERR(TAG, "Failed to allocate memory for ReadBuffer"); + goto fail; + } + + context->WriteBuffer = (BYTE*)malloc(SCHANNEL_CB_MAX_TOKEN); + + if (!context->WriteBuffer) + { + WLog_ERR(TAG, "Failed to allocate memory for WriteBuffer"); + goto fail; + } + + return 0; +fail: + schannel_context_cleanup(context); + return -1; +} + +SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context, + PSecBufferDesc pInput, + PSecBufferDesc pOutput) +{ + int status = 0; + int ssl_error = 0; + PSecBuffer pBuffer = NULL; + + if (!context->connected) + { + if (pInput) + { + if (pInput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + pBuffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + + if (!pBuffer) + return SEC_E_INVALID_TOKEN; + + ERR_clear_error(); + status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer); + if (status < 0) + return SEC_E_INVALID_TOKEN; + } + + status = SSL_connect(context->ssl); + + if (status < 0) + { + ssl_error = SSL_get_error(context->ssl, status); + WLog_ERR(TAG, "SSL_connect error: %s", openssl_get_ssl_error_string(ssl_error)); + } + + if (status == 1) + context->connected = TRUE; + + ERR_clear_error(); + status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN); + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + pBuffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!pBuffer) + return SEC_E_INVALID_TOKEN; + + if (status > 0) + { + if (pBuffer->cbBuffer < (unsigned long)status) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(pBuffer->pvBuffer, context->ReadBuffer, status); + pBuffer->cbBuffer = status; + return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED; + } + else + { + pBuffer->cbBuffer = 0; + return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED; + } + } + + return SEC_E_OK; +} + +SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context, + PSecBufferDesc pInput, + PSecBufferDesc pOutput) +{ + int status = 0; + int ssl_error = 0; + PSecBuffer pBuffer = NULL; + + if (!context->connected) + { + if (pInput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + pBuffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN); + + if (!pBuffer) + return SEC_E_INVALID_TOKEN; + + ERR_clear_error(); + status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer); + if (status >= 0) + status = SSL_accept(context->ssl); + + if (status < 0) + { + ssl_error = SSL_get_error(context->ssl, status); + WLog_ERR(TAG, "SSL_accept error: %s", openssl_get_ssl_error_string(ssl_error)); + return SEC_E_INVALID_TOKEN; + } + + if (status == 1) + context->connected = TRUE; + + ERR_clear_error(); + status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN); + if (status < 0) + { + ssl_error = SSL_get_error(context->ssl, status); + WLog_ERR(TAG, "BIO_read: %s", openssl_get_ssl_error_string(ssl_error)); + return SEC_E_INVALID_TOKEN; + } + + if (pOutput->cBuffers < 1) + return SEC_E_INVALID_TOKEN; + + pBuffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN); + + if (!pBuffer) + return SEC_E_INVALID_TOKEN; + + if (status > 0) + { + if (pBuffer->cbBuffer < (unsigned long)status) + return SEC_E_INSUFFICIENT_MEMORY; + + CopyMemory(pBuffer->pvBuffer, context->ReadBuffer, status); + pBuffer->cbBuffer = status; + return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED; + } + else + { + pBuffer->cbBuffer = 0; + return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED; + } + } + + return SEC_E_OK; +} + +SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage) +{ + int status = 0; + int ssl_error = 0; + PSecBuffer pStreamBodyBuffer = NULL; + PSecBuffer pStreamHeaderBuffer = NULL; + PSecBuffer pStreamTrailerBuffer = NULL; + pStreamHeaderBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_STREAM_HEADER); + pStreamBodyBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); + pStreamTrailerBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_STREAM_TRAILER); + + if ((!pStreamHeaderBuffer) || (!pStreamBodyBuffer) || (!pStreamTrailerBuffer)) + return SEC_E_INVALID_TOKEN; + + status = SSL_write(context->ssl, pStreamBodyBuffer->pvBuffer, pStreamBodyBuffer->cbBuffer); + + if (status < 0) + { + ssl_error = SSL_get_error(context->ssl, status); + WLog_ERR(TAG, "SSL_write: %s", openssl_get_ssl_error_string(ssl_error)); + } + + ERR_clear_error(); + status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN); + + if (status > 0) + { + size_t ustatus = (size_t)status; + size_t length = 0; + size_t offset = 0; + + length = + (pStreamHeaderBuffer->cbBuffer > ustatus) ? ustatus : pStreamHeaderBuffer->cbBuffer; + CopyMemory(pStreamHeaderBuffer->pvBuffer, &context->ReadBuffer[offset], length); + ustatus -= length; + offset += length; + length = (pStreamBodyBuffer->cbBuffer > ustatus) ? ustatus : pStreamBodyBuffer->cbBuffer; + CopyMemory(pStreamBodyBuffer->pvBuffer, &context->ReadBuffer[offset], length); + ustatus -= length; + offset += length; + length = + (pStreamTrailerBuffer->cbBuffer > ustatus) ? ustatus : pStreamTrailerBuffer->cbBuffer; + CopyMemory(pStreamTrailerBuffer->pvBuffer, &context->ReadBuffer[offset], length); + } + + return SEC_E_OK; +} + +SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage) +{ + int status = 0; + int length = 0; + BYTE* buffer = NULL; + int ssl_error = 0; + PSecBuffer pBuffer = NULL; + pBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA); + + if (!pBuffer) + return SEC_E_INVALID_TOKEN; + + ERR_clear_error(); + status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer); + if (status > 0) + status = SSL_read(context->ssl, pBuffer->pvBuffer, pBuffer->cbBuffer); + + if (status < 0) + { + ssl_error = SSL_get_error(context->ssl, status); + WLog_ERR(TAG, "SSL_read: %s", openssl_get_ssl_error_string(ssl_error)); + } + + length = status; + buffer = pBuffer->pvBuffer; + pMessage->pBuffers[0].BufferType = SECBUFFER_STREAM_HEADER; + pMessage->pBuffers[0].cbBuffer = 5; + pMessage->pBuffers[1].BufferType = SECBUFFER_DATA; + pMessage->pBuffers[1].pvBuffer = buffer; + pMessage->pBuffers[1].cbBuffer = length; + pMessage->pBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + pMessage->pBuffers[2].cbBuffer = 36; + pMessage->pBuffers[3].BufferType = SECBUFFER_EMPTY; + pMessage->pBuffers[3].cbBuffer = 0; + return SEC_E_OK; +} + +SCHANNEL_OPENSSL* schannel_openssl_new(void) +{ + SCHANNEL_OPENSSL* context = NULL; + context = (SCHANNEL_OPENSSL*)calloc(1, sizeof(SCHANNEL_OPENSSL)); + + if (context != NULL) + { + winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); + context->connected = FALSE; + } + + return context; +} + +void schannel_openssl_free(SCHANNEL_OPENSSL* context) +{ + if (context) + { + free(context->ReadBuffer); + free(context->WriteBuffer); + free(context); + } +} + +#else + +int schannel_openssl_client_init(SCHANNEL_OPENSSL* context) +{ + return 0; +} + +int schannel_openssl_server_init(SCHANNEL_OPENSSL* context) +{ + return 0; +} + +SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context, + PSecBufferDesc pInput, + PSecBufferDesc pOutput) +{ + return SEC_E_OK; +} + +SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context, + PSecBufferDesc pInput, + PSecBufferDesc pOutput) +{ + return SEC_E_OK; +} + +SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage) +{ + return SEC_E_OK; +} + +SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage) +{ + return SEC_E_OK; +} + +SCHANNEL_OPENSSL* schannel_openssl_new(void) +{ + return NULL; +} + +void schannel_openssl_free(SCHANNEL_OPENSSL* context) +{ +} + +#endif diff --git a/winpr/libwinpr/sspi/Schannel/schannel_openssl.h b/winpr/libwinpr/sspi/Schannel/schannel_openssl.h new file mode 100644 index 0000000..31993e9 --- /dev/null +++ b/winpr/libwinpr/sspi/Schannel/schannel_openssl.h @@ -0,0 +1,52 @@ +/** + * WinPR: Windows Portable Runtime + * Schannel Security Package (OpenSSL) + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_SCHANNEL_OPENSSL_H +#define WINPR_SSPI_SCHANNEL_OPENSSL_H + +#include <winpr/sspi.h> + +#include "../sspi.h" + +/* OpenSSL includes windows.h */ +#include <winpr/windows.h> + +typedef struct S_SCHANNEL_OPENSSL SCHANNEL_OPENSSL; + +int schannel_openssl_client_init(SCHANNEL_OPENSSL* context); +int schannel_openssl_server_init(SCHANNEL_OPENSSL* context); + +SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context, + PSecBufferDesc pInput, + PSecBufferDesc pOutput); +SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context, + PSecBufferDesc pInput, + PSecBufferDesc pOutput); + +SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, + PSecBufferDesc pMessage); +SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, + PSecBufferDesc pMessage); + +void schannel_openssl_free(SCHANNEL_OPENSSL* context); + +WINPR_ATTR_MALLOC(schannel_openssl_free, 1) +SCHANNEL_OPENSSL* schannel_openssl_new(void); + +#endif /* WINPR_SSPI_SCHANNEL_OPENSSL_H */ diff --git a/winpr/libwinpr/sspi/sspi.c b/winpr/libwinpr/sspi/sspi.c new file mode 100644 index 0000000..acecb5b --- /dev/null +++ b/winpr/libwinpr/sspi/sspi.c @@ -0,0 +1,1129 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Security Support Provider Interface (SSPI) + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/platform.h> +#include <winpr/config.h> + +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO + +#define _NO_KSECDD_IMPORT_ 1 + +WINPR_PRAGMA_DIAG_POP + +#include <winpr/sspi.h> + +#include <winpr/crt.h> +#include <winpr/synch.h> +#include <winpr/wlog.h> +#include <winpr/library.h> +#include <winpr/environment.h> + +#include "sspi.h" + +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES + +static wLog* g_Log = NULL; + +static INIT_ONCE g_Initialized = INIT_ONCE_STATIC_INIT; +#if defined(WITH_NATIVE_SSPI) +static HMODULE g_SspiModule = NULL; +static SecurityFunctionTableW windows_SecurityFunctionTableW = { 0 }; +static SecurityFunctionTableA windows_SecurityFunctionTableA = { 0 }; +#endif + +static SecurityFunctionTableW* g_SspiW = NULL; +static SecurityFunctionTableA* g_SspiA = NULL; + +#if defined(WITH_NATIVE_SSPI) +static BOOL ShouldUseNativeSspi(void); +static BOOL InitializeSspiModule_Native(void); +#endif + +#if defined(WITH_NATIVE_SSPI) +BOOL ShouldUseNativeSspi(void) +{ + BOOL status = FALSE; +#ifdef _WIN32 + LPCSTR sspi = "WINPR_NATIVE_SSPI"; + DWORD nSize; + char* env = NULL; + nSize = GetEnvironmentVariableA(sspi, NULL, 0); + + if (!nSize) + return TRUE; + + env = (LPSTR)malloc(nSize); + + if (!env) + return TRUE; + + if (GetEnvironmentVariableA(sspi, env, nSize) != nSize - 1) + { + free(env); + return TRUE; + } + + if (strcmp(env, "0") == 0) + status = FALSE; + else + status = TRUE; + + free(env); +#endif + return status; +} +#endif + +#if defined(WITH_NATIVE_SSPI) +BOOL InitializeSspiModule_Native(void) +{ + SecurityFunctionTableW* pSspiW = NULL; + SecurityFunctionTableA* pSspiA = NULL; + INIT_SECURITY_INTERFACE_W pInitSecurityInterfaceW; + INIT_SECURITY_INTERFACE_A pInitSecurityInterfaceA; + g_SspiModule = LoadLibraryA("secur32.dll"); + + if (!g_SspiModule) + g_SspiModule = LoadLibraryA("sspicli.dll"); + + if (!g_SspiModule) + return FALSE; + + pInitSecurityInterfaceW = + (INIT_SECURITY_INTERFACE_W)GetProcAddress(g_SspiModule, "InitSecurityInterfaceW"); + pInitSecurityInterfaceA = + (INIT_SECURITY_INTERFACE_A)GetProcAddress(g_SspiModule, "InitSecurityInterfaceA"); + + if (pInitSecurityInterfaceW) + { + pSspiW = pInitSecurityInterfaceW(); + + if (pSspiW) + { + g_SspiW = &windows_SecurityFunctionTableW; + CopyMemory(g_SspiW, pSspiW, + FIELD_OFFSET(SecurityFunctionTableW, SetContextAttributesW)); + + g_SspiW->dwVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_3; + + g_SspiW->SetContextAttributesW = + (SET_CONTEXT_ATTRIBUTES_FN_W)GetProcAddress(g_SspiModule, "SetContextAttributesW"); + + g_SspiW->SetCredentialsAttributesW = (SET_CREDENTIALS_ATTRIBUTES_FN_W)GetProcAddress( + g_SspiModule, "SetCredentialsAttributesW"); + } + } + + if (pInitSecurityInterfaceA) + { + pSspiA = pInitSecurityInterfaceA(); + + if (pSspiA) + { + g_SspiA = &windows_SecurityFunctionTableA; + CopyMemory(g_SspiA, pSspiA, + FIELD_OFFSET(SecurityFunctionTableA, SetContextAttributesA)); + + g_SspiA->dwVersion = SECURITY_SUPPORT_PROVIDER_INTERFACE_VERSION_3; + + g_SspiA->SetContextAttributesA = + (SET_CONTEXT_ATTRIBUTES_FN_W)GetProcAddress(g_SspiModule, "SetContextAttributesA"); + + g_SspiA->SetCredentialsAttributesA = (SET_CREDENTIALS_ATTRIBUTES_FN_W)GetProcAddress( + g_SspiModule, "SetCredentialsAttributesA"); + } + } + + return TRUE; +} +#endif + +static BOOL CALLBACK InitializeSspiModuleInt(PINIT_ONCE once, PVOID param, PVOID* context) +{ + BOOL status = FALSE; +#if defined(WITH_NATIVE_SSPI) + DWORD flags = 0; + + if (param) + flags = *(DWORD*)param; + +#endif + sspi_GlobalInit(); + g_Log = WLog_Get("com.winpr.sspi"); +#if defined(WITH_NATIVE_SSPI) + + if (flags && (flags & SSPI_INTERFACE_NATIVE)) + { + status = InitializeSspiModule_Native(); + } + else if (flags && (flags & SSPI_INTERFACE_WINPR)) + { + g_SspiW = winpr_InitSecurityInterfaceW(); + g_SspiA = winpr_InitSecurityInterfaceA(); + status = TRUE; + } + + if (!status && ShouldUseNativeSspi()) + { + status = InitializeSspiModule_Native(); + } + +#endif + + if (!status) + { + g_SspiW = winpr_InitSecurityInterfaceW(); + g_SspiA = winpr_InitSecurityInterfaceA(); + } + + return TRUE; +} + +const char* GetSecurityStatusString(SECURITY_STATUS status) +{ + switch (status) + { + case SEC_E_OK: + return "SEC_E_OK"; + + case SEC_E_INSUFFICIENT_MEMORY: + return "SEC_E_INSUFFICIENT_MEMORY"; + + case SEC_E_INVALID_HANDLE: + return "SEC_E_INVALID_HANDLE"; + + case SEC_E_UNSUPPORTED_FUNCTION: + return "SEC_E_UNSUPPORTED_FUNCTION"; + + case SEC_E_TARGET_UNKNOWN: + return "SEC_E_TARGET_UNKNOWN"; + + case SEC_E_INTERNAL_ERROR: + return "SEC_E_INTERNAL_ERROR"; + + case SEC_E_SECPKG_NOT_FOUND: + return "SEC_E_SECPKG_NOT_FOUND"; + + case SEC_E_NOT_OWNER: + return "SEC_E_NOT_OWNER"; + + case SEC_E_CANNOT_INSTALL: + return "SEC_E_CANNOT_INSTALL"; + + case SEC_E_INVALID_TOKEN: + return "SEC_E_INVALID_TOKEN"; + + case SEC_E_CANNOT_PACK: + return "SEC_E_CANNOT_PACK"; + + case SEC_E_QOP_NOT_SUPPORTED: + return "SEC_E_QOP_NOT_SUPPORTED"; + + case SEC_E_NO_IMPERSONATION: + return "SEC_E_NO_IMPERSONATION"; + + case SEC_E_LOGON_DENIED: + return "SEC_E_LOGON_DENIED"; + + case SEC_E_UNKNOWN_CREDENTIALS: + return "SEC_E_UNKNOWN_CREDENTIALS"; + + case SEC_E_NO_CREDENTIALS: + return "SEC_E_NO_CREDENTIALS"; + + case SEC_E_MESSAGE_ALTERED: + return "SEC_E_MESSAGE_ALTERED"; + + case SEC_E_OUT_OF_SEQUENCE: + return "SEC_E_OUT_OF_SEQUENCE"; + + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + return "SEC_E_NO_AUTHENTICATING_AUTHORITY"; + + case SEC_E_BAD_PKGID: + return "SEC_E_BAD_PKGID"; + + case SEC_E_CONTEXT_EXPIRED: + return "SEC_E_CONTEXT_EXPIRED"; + + case SEC_E_INCOMPLETE_MESSAGE: + return "SEC_E_INCOMPLETE_MESSAGE"; + + case SEC_E_INCOMPLETE_CREDENTIALS: + return "SEC_E_INCOMPLETE_CREDENTIALS"; + + case SEC_E_BUFFER_TOO_SMALL: + return "SEC_E_BUFFER_TOO_SMALL"; + + case SEC_E_WRONG_PRINCIPAL: + return "SEC_E_WRONG_PRINCIPAL"; + + case SEC_E_TIME_SKEW: + return "SEC_E_TIME_SKEW"; + + case SEC_E_UNTRUSTED_ROOT: + return "SEC_E_UNTRUSTED_ROOT"; + + case SEC_E_ILLEGAL_MESSAGE: + return "SEC_E_ILLEGAL_MESSAGE"; + + case SEC_E_CERT_UNKNOWN: + return "SEC_E_CERT_UNKNOWN"; + + case SEC_E_CERT_EXPIRED: + return "SEC_E_CERT_EXPIRED"; + + case SEC_E_ENCRYPT_FAILURE: + return "SEC_E_ENCRYPT_FAILURE"; + + case SEC_E_DECRYPT_FAILURE: + return "SEC_E_DECRYPT_FAILURE"; + + case SEC_E_ALGORITHM_MISMATCH: + return "SEC_E_ALGORITHM_MISMATCH"; + + case SEC_E_SECURITY_QOS_FAILED: + return "SEC_E_SECURITY_QOS_FAILED"; + + case SEC_E_UNFINISHED_CONTEXT_DELETED: + return "SEC_E_UNFINISHED_CONTEXT_DELETED"; + + case SEC_E_NO_TGT_REPLY: + return "SEC_E_NO_TGT_REPLY"; + + case SEC_E_NO_IP_ADDRESSES: + return "SEC_E_NO_IP_ADDRESSES"; + + case SEC_E_WRONG_CREDENTIAL_HANDLE: + return "SEC_E_WRONG_CREDENTIAL_HANDLE"; + + case SEC_E_CRYPTO_SYSTEM_INVALID: + return "SEC_E_CRYPTO_SYSTEM_INVALID"; + + case SEC_E_MAX_REFERRALS_EXCEEDED: + return "SEC_E_MAX_REFERRALS_EXCEEDED"; + + case SEC_E_MUST_BE_KDC: + return "SEC_E_MUST_BE_KDC"; + + case SEC_E_STRONG_CRYPTO_NOT_SUPPORTED: + return "SEC_E_STRONG_CRYPTO_NOT_SUPPORTED"; + + case SEC_E_TOO_MANY_PRINCIPALS: + return "SEC_E_TOO_MANY_PRINCIPALS"; + + case SEC_E_NO_PA_DATA: + return "SEC_E_NO_PA_DATA"; + + case SEC_E_PKINIT_NAME_MISMATCH: + return "SEC_E_PKINIT_NAME_MISMATCH"; + + case SEC_E_SMARTCARD_LOGON_REQUIRED: + return "SEC_E_SMARTCARD_LOGON_REQUIRED"; + + case SEC_E_SHUTDOWN_IN_PROGRESS: + return "SEC_E_SHUTDOWN_IN_PROGRESS"; + + case SEC_E_KDC_INVALID_REQUEST: + return "SEC_E_KDC_INVALID_REQUEST"; + + case SEC_E_KDC_UNABLE_TO_REFER: + return "SEC_E_KDC_UNABLE_TO_REFER"; + + case SEC_E_KDC_UNKNOWN_ETYPE: + return "SEC_E_KDC_UNKNOWN_ETYPE"; + + case SEC_E_UNSUPPORTED_PREAUTH: + return "SEC_E_UNSUPPORTED_PREAUTH"; + + case SEC_E_DELEGATION_REQUIRED: + return "SEC_E_DELEGATION_REQUIRED"; + + case SEC_E_BAD_BINDINGS: + return "SEC_E_BAD_BINDINGS"; + + case SEC_E_MULTIPLE_ACCOUNTS: + return "SEC_E_MULTIPLE_ACCOUNTS"; + + case SEC_E_NO_KERB_KEY: + return "SEC_E_NO_KERB_KEY"; + + case SEC_E_CERT_WRONG_USAGE: + return "SEC_E_CERT_WRONG_USAGE"; + + case SEC_E_DOWNGRADE_DETECTED: + return "SEC_E_DOWNGRADE_DETECTED"; + + case SEC_E_SMARTCARD_CERT_REVOKED: + return "SEC_E_SMARTCARD_CERT_REVOKED"; + + case SEC_E_ISSUING_CA_UNTRUSTED: + return "SEC_E_ISSUING_CA_UNTRUSTED"; + + case SEC_E_REVOCATION_OFFLINE_C: + return "SEC_E_REVOCATION_OFFLINE_C"; + + case SEC_E_PKINIT_CLIENT_FAILURE: + return "SEC_E_PKINIT_CLIENT_FAILURE"; + + case SEC_E_SMARTCARD_CERT_EXPIRED: + return "SEC_E_SMARTCARD_CERT_EXPIRED"; + + case SEC_E_NO_S4U_PROT_SUPPORT: + return "SEC_E_NO_S4U_PROT_SUPPORT"; + + case SEC_E_CROSSREALM_DELEGATION_FAILURE: + return "SEC_E_CROSSREALM_DELEGATION_FAILURE"; + + case SEC_E_REVOCATION_OFFLINE_KDC: + return "SEC_E_REVOCATION_OFFLINE_KDC"; + + case SEC_E_ISSUING_CA_UNTRUSTED_KDC: + return "SEC_E_ISSUING_CA_UNTRUSTED_KDC"; + + case SEC_E_KDC_CERT_EXPIRED: + return "SEC_E_KDC_CERT_EXPIRED"; + + case SEC_E_KDC_CERT_REVOKED: + return "SEC_E_KDC_CERT_REVOKED"; + + case SEC_E_INVALID_PARAMETER: + return "SEC_E_INVALID_PARAMETER"; + + case SEC_E_DELEGATION_POLICY: + return "SEC_E_DELEGATION_POLICY"; + + case SEC_E_POLICY_NLTM_ONLY: + return "SEC_E_POLICY_NLTM_ONLY"; + + case SEC_E_NO_CONTEXT: + return "SEC_E_NO_CONTEXT"; + + case SEC_E_PKU2U_CERT_FAILURE: + return "SEC_E_PKU2U_CERT_FAILURE"; + + case SEC_E_MUTUAL_AUTH_FAILED: + return "SEC_E_MUTUAL_AUTH_FAILED"; + + case SEC_I_CONTINUE_NEEDED: + return "SEC_I_CONTINUE_NEEDED"; + + case SEC_I_COMPLETE_NEEDED: + return "SEC_I_COMPLETE_NEEDED"; + + case SEC_I_COMPLETE_AND_CONTINUE: + return "SEC_I_COMPLETE_AND_CONTINUE"; + + case SEC_I_LOCAL_LOGON: + return "SEC_I_LOCAL_LOGON"; + + case SEC_I_CONTEXT_EXPIRED: + return "SEC_I_CONTEXT_EXPIRED"; + + case SEC_I_INCOMPLETE_CREDENTIALS: + return "SEC_I_INCOMPLETE_CREDENTIALS"; + + case SEC_I_RENEGOTIATE: + return "SEC_I_RENEGOTIATE"; + + case SEC_I_NO_LSA_CONTEXT: + return "SEC_I_NO_LSA_CONTEXT"; + + case SEC_I_SIGNATURE_NEEDED: + return "SEC_I_SIGNATURE_NEEDED"; + + case SEC_I_NO_RENEGOTIATION: + return "SEC_I_NO_RENEGOTIATION"; + } + + return NtStatus2Tag((DWORD)status); +} + +BOOL IsSecurityStatusError(SECURITY_STATUS status) +{ + BOOL error = TRUE; + + switch (status) + { + case SEC_E_OK: + case SEC_I_CONTINUE_NEEDED: + case SEC_I_COMPLETE_NEEDED: + case SEC_I_COMPLETE_AND_CONTINUE: + case SEC_I_LOCAL_LOGON: + case SEC_I_CONTEXT_EXPIRED: + case SEC_I_INCOMPLETE_CREDENTIALS: + case SEC_I_RENEGOTIATE: + case SEC_I_NO_LSA_CONTEXT: + case SEC_I_SIGNATURE_NEEDED: + case SEC_I_NO_RENEGOTIATION: + error = FALSE; + break; + } + + return error; +} + +SecurityFunctionTableW* SEC_ENTRY InitSecurityInterfaceExW(DWORD flags) +{ + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, &flags, NULL); + WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceExW"); + return g_SspiW; +} + +SecurityFunctionTableA* SEC_ENTRY InitSecurityInterfaceExA(DWORD flags) +{ + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, &flags, NULL); + WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceExA"); + return g_SspiA; +} + +/** + * Standard SSPI API + */ + +/* Package Management */ + +SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesW(ULONG* pcPackages, + PSecPkgInfoW* ppPackageInfo) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->EnumerateSecurityPackagesW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->EnumerateSecurityPackagesW(pcPackages, ppPackageInfo); + WLog_Print(g_Log, WLOG_DEBUG, "EnumerateSecurityPackagesW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesA(ULONG* pcPackages, + PSecPkgInfoA* ppPackageInfo) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->EnumerateSecurityPackagesA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->EnumerateSecurityPackagesA(pcPackages, ppPackageInfo); + WLog_Print(g_Log, WLOG_DEBUG, "EnumerateSecurityPackagesA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SecurityFunctionTableW* SEC_ENTRY sspi_InitSecurityInterfaceW(void) +{ + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceW"); + return g_SspiW; +} + +SecurityFunctionTableA* SEC_ENTRY sspi_InitSecurityInterfaceA(void) +{ + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + WLog_Print(g_Log, WLOG_DEBUG, "InitSecurityInterfaceA"); + return g_SspiA; +} + +SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoW(SEC_WCHAR* pszPackageName, + PSecPkgInfoW* ppPackageInfo) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->QuerySecurityPackageInfoW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->QuerySecurityPackageInfoW(pszPackageName, ppPackageInfo); + WLog_Print(g_Log, WLOG_DEBUG, "QuerySecurityPackageInfoW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoA(SEC_CHAR* pszPackageName, + PSecPkgInfoA* ppPackageInfo) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->QuerySecurityPackageInfoA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->QuerySecurityPackageInfoA(pszPackageName, ppPackageInfo); + WLog_Print(g_Log, WLOG_DEBUG, "QuerySecurityPackageInfoA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +/* Credential Management */ + +SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->AcquireCredentialsHandleW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse, pvLogonID, + pAuthData, pGetKeyFn, pvGetKeyArgument, + phCredential, ptsExpiry); + WLog_Print(g_Log, WLOG_DEBUG, "AcquireCredentialsHandleW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->AcquireCredentialsHandleA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, pvLogonID, + pAuthData, pGetKeyFn, pvGetKeyArgument, + phCredential, ptsExpiry); + WLog_Print(g_Log, WLOG_DEBUG, "AcquireCredentialsHandleA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_ExportSecurityContext(PCtxtHandle phContext, ULONG fFlags, + PSecBuffer pPackedContext, HANDLE* pToken) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->ExportSecurityContext)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->ExportSecurityContext(phContext, fFlags, pPackedContext, pToken); + WLog_Print(g_Log, WLOG_DEBUG, "ExportSecurityContext: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_FreeCredentialsHandle(PCredHandle phCredential) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->FreeCredentialsHandle)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->FreeCredentialsHandle(phCredential); + WLog_Print(g_Log, WLOG_DEBUG, "FreeCredentialsHandle: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextW(SEC_WCHAR* pszPackage, + PSecBuffer pPackedContext, HANDLE pToken, + PCtxtHandle phContext) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->ImportSecurityContextW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->ImportSecurityContextW(pszPackage, pPackedContext, pToken, phContext); + WLog_Print(g_Log, WLOG_DEBUG, "ImportSecurityContextW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextA(SEC_CHAR* pszPackage, + PSecBuffer pPackedContext, HANDLE pToken, + PCtxtHandle phContext) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->ImportSecurityContextA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->ImportSecurityContextA(pszPackage, pPackedContext, pToken, phContext); + WLog_Print(g_Log, WLOG_DEBUG, "ImportSecurityContextA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->QueryCredentialsAttributesW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "QueryCredentialsAttributesW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->QueryCredentialsAttributesA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->QueryCredentialsAttributesA(phCredential, ulAttribute, pBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "QueryCredentialsAttributesA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +/* Context Management */ + +SECURITY_STATUS SEC_ENTRY sspi_AcceptSecurityContext(PCredHandle phCredential, + PCtxtHandle phContext, PSecBufferDesc pInput, + ULONG fContextReq, ULONG TargetDataRep, + PCtxtHandle phNewContext, + PSecBufferDesc pOutput, PULONG pfContextAttr, + PTimeStamp ptsTimeStamp) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->AcceptSecurityContext)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = + g_SspiW->AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep, + phNewContext, pOutput, pfContextAttr, ptsTimeStamp); + WLog_Print(g_Log, WLOG_DEBUG, "AcceptSecurityContext: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_ApplyControlToken(PCtxtHandle phContext, PSecBufferDesc pInput) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->ApplyControlToken)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->ApplyControlToken(phContext, pInput); + WLog_Print(g_Log, WLOG_DEBUG, "ApplyControlToken: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_CompleteAuthToken(PCtxtHandle phContext, PSecBufferDesc pToken) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->CompleteAuthToken)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->CompleteAuthToken(phContext, pToken); + WLog_Print(g_Log, WLOG_DEBUG, "CompleteAuthToken: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_DeleteSecurityContext(PCtxtHandle phContext) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->DeleteSecurityContext)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->DeleteSecurityContext(phContext); + WLog_Print(g_Log, WLOG_DEBUG, "DeleteSecurityContext: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_FreeContextBuffer(void* pvContextBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->FreeContextBuffer)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->FreeContextBuffer(pvContextBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "FreeContextBuffer: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_ImpersonateSecurityContext(PCtxtHandle phContext) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->ImpersonateSecurityContext)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->ImpersonateSecurityContext(phContext); + WLog_Print(g_Log, WLOG_DEBUG, "ImpersonateSecurityContext: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->InitializeSecurityContextW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->InitializeSecurityContextW( + phCredential, phContext, pszTargetName, fContextReq, Reserved1, TargetDataRep, pInput, + Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry); + WLog_Print(g_Log, WLOG_DEBUG, "InitializeSecurityContextW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->InitializeSecurityContextA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->InitializeSecurityContextA( + phCredential, phContext, pszTargetName, fContextReq, Reserved1, TargetDataRep, pInput, + Reserved2, phNewContext, pOutput, pfContextAttr, ptsExpiry); + WLog_Print(g_Log, WLOG_DEBUG, "InitializeSecurityContextA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesW(PCtxtHandle phContext, ULONG ulAttribute, + void* pBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->QueryContextAttributesW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->QueryContextAttributesW(phContext, ulAttribute, pBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "QueryContextAttributesW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesA(PCtxtHandle phContext, ULONG ulAttribute, + void* pBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->QueryContextAttributesA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->QueryContextAttributesA(phContext, ulAttribute, pBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "QueryContextAttributesA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityContextToken(PCtxtHandle phContext, HANDLE* phToken) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->QuerySecurityContextToken)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->QuerySecurityContextToken(phContext, phToken); + WLog_Print(g_Log, WLOG_DEBUG, "QuerySecurityContextToken: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesW(PCtxtHandle phContext, ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->SetContextAttributesW)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "SetContextAttributesW: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesA(PCtxtHandle phContext, ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiA && g_SspiA->SetContextAttributesA)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiA->SetContextAttributesA(phContext, ulAttribute, pBuffer, cbBuffer); + WLog_Print(g_Log, WLOG_DEBUG, "SetContextAttributesA: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_RevertSecurityContext(PCtxtHandle phContext) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->RevertSecurityContext)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->RevertSecurityContext(phContext); + WLog_Print(g_Log, WLOG_DEBUG, "RevertSecurityContext: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +/* Message Support */ + +SECURITY_STATUS SEC_ENTRY sspi_DecryptMessage(PCtxtHandle phContext, PSecBufferDesc pMessage, + ULONG MessageSeqNo, PULONG pfQOP) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->DecryptMessage)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->DecryptMessage(phContext, pMessage, MessageSeqNo, pfQOP); + WLog_Print(g_Log, WLOG_DEBUG, "DecryptMessage: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->EncryptMessage)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->EncryptMessage(phContext, fQOP, pMessage, MessageSeqNo); + WLog_Print(g_Log, WLOG_DEBUG, "EncryptMessage: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->MakeSignature)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->MakeSignature(phContext, fQOP, pMessage, MessageSeqNo); + WLog_Print(g_Log, WLOG_DEBUG, "MakeSignature: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +SECURITY_STATUS SEC_ENTRY sspi_VerifySignature(PCtxtHandle phContext, PSecBufferDesc pMessage, + ULONG MessageSeqNo, PULONG pfQOP) +{ + SECURITY_STATUS status = 0; + InitOnceExecuteOnce(&g_Initialized, InitializeSspiModuleInt, NULL, NULL); + + if (!(g_SspiW && g_SspiW->VerifySignature)) + { + WLog_Print(g_Log, WLOG_WARN, "Security module does not provide an implementation"); + + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = g_SspiW->VerifySignature(phContext, pMessage, MessageSeqNo, pfQOP); + WLog_Print(g_Log, WLOG_DEBUG, "VerifySignature: %s (0x%08" PRIX32 ")", + GetSecurityStatusString(status), status); + return status; +} + +WINPR_PRAGMA_DIAG_POP + +void sspi_FreeAuthIdentity(SEC_WINNT_AUTH_IDENTITY* identity) +{ + if (!identity) + return; + free(identity->User); + identity->UserLength = (UINT32)0; + identity->User = NULL; + + free(identity->Domain); + identity->DomainLength = (UINT32)0; + identity->Domain = NULL; + + if (identity->PasswordLength > 0) + memset(identity->Password, 0, identity->PasswordLength); + free(identity->Password); + identity->Password = NULL; + identity->PasswordLength = (UINT32)0; +} diff --git a/winpr/libwinpr/sspi/sspi.h b/winpr/libwinpr/sspi/sspi.h new file mode 100644 index 0000000..f6791f9 --- /dev/null +++ b/winpr/libwinpr/sspi/sspi.h @@ -0,0 +1,93 @@ +/** + * WinPR: Windows Portable Runtime + * Security Support Provider Interface (SSPI) + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_PRIVATE_H +#define WINPR_SSPI_PRIVATE_H + +#include <winpr/sspi.h> + +#define SCHANNEL_CB_MAX_TOKEN 0x00006000 + +#define SSPI_CREDENTIALS_PASSWORD_HASH 0x00000001 + +#define SSPI_CREDENTIALS_HASH_LENGTH_OFFSET 512 + +typedef struct +{ + DWORD flags; + ULONG fCredentialUse; + SEC_GET_KEY_FN pGetKeyFn; + void* pvGetKeyArgument; + SEC_WINNT_AUTH_IDENTITY identity; + SEC_WINPR_NTLM_SETTINGS ntlmSettings; + SEC_WINPR_KERBEROS_SETTINGS kerbSettings; +} SSPI_CREDENTIALS; + +SSPI_CREDENTIALS* sspi_CredentialsNew(void); +void sspi_CredentialsFree(SSPI_CREDENTIALS* credentials); + +PSecBuffer sspi_FindSecBuffer(PSecBufferDesc pMessage, ULONG BufferType); + +SecHandle* sspi_SecureHandleAlloc(void); +void sspi_SecureHandleInvalidate(SecHandle* handle); +void* sspi_SecureHandleGetLowerPointer(SecHandle* handle); +void sspi_SecureHandleSetLowerPointer(SecHandle* handle, void* pointer); +void* sspi_SecureHandleGetUpperPointer(SecHandle* handle); +void sspi_SecureHandleSetUpperPointer(SecHandle* handle, void* pointer); +void sspi_SecureHandleFree(SecHandle* handle); + +enum SecurityFunctionTableIndex +{ + EnumerateSecurityPackagesIndex = 1, + Reserved1Index = 2, + QueryCredentialsAttributesIndex = 3, + AcquireCredentialsHandleIndex = 4, + FreeCredentialsHandleIndex = 5, + Reserved2Index = 6, + InitializeSecurityContextIndex = 7, + AcceptSecurityContextIndex = 8, + CompleteAuthTokenIndex = 9, + DeleteSecurityContextIndex = 10, + ApplyControlTokenIndex = 11, + QueryContextAttributesIndex = 12, + ImpersonateSecurityContextIndex = 13, + RevertSecurityContextIndex = 14, + MakeSignatureIndex = 15, + VerifySignatureIndex = 16, + FreeContextBufferIndex = 17, + QuerySecurityPackageInfoIndex = 18, + Reserved3Index = 19, + Reserved4Index = 20, + ExportSecurityContextIndex = 21, + ImportSecurityContextIndex = 22, + AddCredentialsIndex = 23, + Reserved8Index = 24, + QuerySecurityContextTokenIndex = 25, + EncryptMessageIndex = 26, + DecryptMessageIndex = 27, + SetContextAttributesIndex = 28, + SetCredentialsAttributesIndex = 29 +}; + +BOOL IsSecurityStatusError(SECURITY_STATUS status); + +#include "sspi_gss.h" +#include "sspi_winpr.h" + +#endif /* WINPR_SSPI_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/sspi_export.c b/winpr/libwinpr/sspi/sspi_export.c new file mode 100644 index 0000000..32258cf --- /dev/null +++ b/winpr/libwinpr/sspi/sspi_export.c @@ -0,0 +1,345 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Security Support Provider Interface (SSPI) + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/platform.h> +#include <winpr/wtypes.h> +#include <winpr/config.h> + +#ifdef _WIN32 +#define SEC_ENTRY __stdcall +#define SSPI_EXPORT __declspec(dllexport) +#else +#include <winpr/winpr.h> +#define SEC_ENTRY +#define SSPI_EXPORT WINPR_API +#endif + +#ifdef _WIN32 +typedef long LONG; +typedef unsigned long ULONG; +#endif +typedef LONG SECURITY_STATUS; + +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_MISSING_PROTOTYPES + +#ifdef SSPI_DLL + +/** + * Standard SSPI API + */ + +/* Package Management */ + +extern SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesW(void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesW(void* pcPackages, + void* ppPackageInfo) +{ + return sspi_EnumerateSecurityPackagesW(pcPackages, ppPackageInfo); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_EnumerateSecurityPackagesA(void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY EnumerateSecurityPackagesA(void* pcPackages, + void* ppPackageInfo) +{ + return sspi_EnumerateSecurityPackagesA(pcPackages, ppPackageInfo); +} + +extern void* SEC_ENTRY sspi_InitSecurityInterfaceW(void); + +SSPI_EXPORT void* SEC_ENTRY InitSecurityInterfaceW(void) +{ + return sspi_InitSecurityInterfaceW(); +} + +extern void* SEC_ENTRY sspi_InitSecurityInterfaceA(void); + +SSPI_EXPORT void* SEC_ENTRY InitSecurityInterfaceA(void) +{ + return sspi_InitSecurityInterfaceA(); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoW(void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QuerySecurityPackageInfoW(void* pszPackageName, + void* ppPackageInfo) +{ + return sspi_QuerySecurityPackageInfoW(pszPackageName, ppPackageInfo); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityPackageInfoA(void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QuerySecurityPackageInfoA(void* pszPackageName, + void* ppPackageInfo) +{ + return sspi_QuerySecurityPackageInfoA(pszPackageName, ppPackageInfo); +} + +/* Credential Management */ + +extern SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleW(void*, void*, ULONG, void*, void*, + void*, void*, void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleW( + void* pszPrincipal, void* pszPackage, ULONG fCredentialUse, void* pvLogonID, void* pAuthData, + void* pGetKeyFn, void* pvGetKeyArgument, void* phCredential, void* ptsExpiry) +{ + return sspi_AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse, pvLogonID, + pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential, + ptsExpiry); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_AcquireCredentialsHandleA(void*, void*, ULONG, void*, void*, + void*, void*, void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY AcquireCredentialsHandleA( + void* pszPrincipal, void* pszPackage, ULONG fCredentialUse, void* pvLogonID, void* pAuthData, + void* pGetKeyFn, void* pvGetKeyArgument, void* phCredential, void* ptsExpiry) +{ + return sspi_AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, pvLogonID, + pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential, + ptsExpiry); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_ExportSecurityContext(void*, ULONG, void*, void**); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ExportSecurityContext(void* phContext, ULONG fFlags, + void* pPackedContext, void** pToken) +{ + return sspi_ExportSecurityContext(phContext, fFlags, pPackedContext, pToken); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_FreeCredentialsHandle(void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY FreeCredentialsHandle(void* phCredential) +{ + return sspi_FreeCredentialsHandle(phCredential); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextW(void*, void*, void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ImportSecurityContextW(void* pszPackage, void* pPackedContext, + void* pToken, void* phContext) +{ + return sspi_ImportSecurityContextW(pszPackage, pPackedContext, pToken, phContext); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_ImportSecurityContextA(void*, void*, void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ImportSecurityContextA(void* pszPackage, void* pPackedContext, + void* pToken, void* phContext) +{ + return sspi_ImportSecurityContextA(pszPackage, pPackedContext, pToken, phContext); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesW(void*, ULONG, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryCredentialsAttributesW(void* phCredential, + ULONG ulAttribute, void* pBuffer) +{ + return sspi_QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QueryCredentialsAttributesA(void*, ULONG, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryCredentialsAttributesA(void* phCredential, + ULONG ulAttribute, void* pBuffer) +{ + return sspi_QueryCredentialsAttributesA(phCredential, ulAttribute, pBuffer); +} + +/* Context Management */ + +extern SECURITY_STATUS SEC_ENTRY sspi_AcceptSecurityContext(void*, void*, void*, ULONG, ULONG, + void*, void*, void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY AcceptSecurityContext(void* phCredential, void* phContext, + void* pInput, ULONG fContextReq, + ULONG TargetDataRep, void* phNewContext, + void* pOutput, void* pfContextAttr, + void* ptsTimeStamp) +{ + return sspi_AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep, + phNewContext, pOutput, pfContextAttr, ptsTimeStamp); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_ApplyControlToken(void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ApplyControlToken(void* phContext, void* pInput) +{ + return sspi_ApplyControlToken(phContext, pInput); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_CompleteAuthToken(void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY CompleteAuthToken(void* phContext, void* pToken) +{ + return sspi_CompleteAuthToken(phContext, pToken); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_DeleteSecurityContext(void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY DeleteSecurityContext(void* phContext) +{ + return sspi_DeleteSecurityContext(phContext); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_FreeContextBuffer(void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY FreeContextBuffer(void* pvContextBuffer) +{ + return sspi_FreeContextBuffer(pvContextBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_ImpersonateSecurityContext(void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY ImpersonateSecurityContext(void* phContext) +{ + return sspi_ImpersonateSecurityContext(phContext); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextW(void*, void*, void*, ULONG, ULONG, + ULONG, void*, ULONG, void*, void*, + void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY InitializeSecurityContextW( + void* phCredential, void* phContext, void* pszTargetName, ULONG fContextReq, ULONG Reserved1, + ULONG TargetDataRep, void* pInput, ULONG Reserved2, void* phNewContext, void* pOutput, + void* pfContextAttr, void* ptsExpiry) +{ + return sspi_InitializeSecurityContextW(phCredential, phContext, pszTargetName, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_InitializeSecurityContextA(void*, void*, void*, ULONG, ULONG, + ULONG, void*, ULONG, void*, void*, + void*, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY InitializeSecurityContextA( + void* phCredential, void* phContext, void* pszTargetName, ULONG fContextReq, ULONG Reserved1, + ULONG TargetDataRep, void* pInput, ULONG Reserved2, void* phNewContext, void* pOutput, + void* pfContextAttr, void* ptsExpiry) +{ + return sspi_InitializeSecurityContextA(phCredential, phContext, pszTargetName, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesW(void*, ULONG, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryContextAttributesW(void* phContext, ULONG ulAttribute, + void* pBuffer) +{ + return sspi_QueryContextAttributesW(phContext, ulAttribute, pBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QueryContextAttributesA(void*, ULONG, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QueryContextAttributesA(void* phContext, ULONG ulAttribute, + void* pBuffer) +{ + return sspi_QueryContextAttributesA(phContext, ulAttribute, pBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_QuerySecurityContextToken(void*, void**); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY QuerySecurityContextToken(void* phContext, void** phToken) +{ + return sspi_QuerySecurityContextToken(phContext, phToken); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesW(void*, ULONG, void*, ULONG); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY SetContextAttributesW(void* phContext, ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + return sspi_SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_SetContextAttributesA(void*, ULONG, void*, ULONG); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY SetContextAttributesA(void* phContext, ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + return sspi_SetContextAttributesA(phContext, ulAttribute, pBuffer, cbBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_SetCredentialsAttributesW(void*, ULONG, void*, ULONG); + +static SECURITY_STATUS SEC_ENTRY SetCredentialsAttributesW(void* phCredential, ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + return sspi_SetCredentialsAttributesW(phCredential, ulAttribute, pBuffer, cbBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_SetCredentialsAttributesA(void*, ULONG, void*, ULONG); + +static SECURITY_STATUS SEC_ENTRY SetCredentialsAttributesA(void* phCredential, ULONG ulAttribute, + void* pBuffer, ULONG cbBuffer) +{ + return sspi_SetCredentialsAttributesA(phCredential, ulAttribute, pBuffer, cbBuffer); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_RevertSecurityContext(void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY RevertSecurityContext(void* phContext) +{ + return sspi_RevertSecurityContext(phContext); +} + +/* Message Support */ + +extern SECURITY_STATUS SEC_ENTRY sspi_DecryptMessage(void*, void*, ULONG, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY DecryptMessage(void* phContext, void* pMessage, + ULONG MessageSeqNo, void* pfQOP) +{ + return sspi_DecryptMessage(phContext, pMessage, MessageSeqNo, pfQOP); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_EncryptMessage(void*, ULONG, void*, ULONG); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY EncryptMessage(void* phContext, ULONG fQOP, void* pMessage, + ULONG MessageSeqNo) +{ + return sspi_EncryptMessage(phContext, fQOP, pMessage, MessageSeqNo); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_MakeSignature(void*, ULONG, void*, ULONG); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY MakeSignature(void* phContext, ULONG fQOP, void* pMessage, + ULONG MessageSeqNo) +{ + return sspi_MakeSignature(phContext, fQOP, pMessage, MessageSeqNo); +} + +extern SECURITY_STATUS SEC_ENTRY sspi_VerifySignature(void*, void*, ULONG, void*); + +SSPI_EXPORT SECURITY_STATUS SEC_ENTRY VerifySignature(void* phContext, void* pMessage, + ULONG MessageSeqNo, void* pfQOP) +{ + return sspi_VerifySignature(phContext, pMessage, MessageSeqNo, pfQOP); +} + +#endif /* SSPI_DLL */ + +WINPR_PRAGMA_DIAG_POP diff --git a/winpr/libwinpr/sspi/sspi_gss.c b/winpr/libwinpr/sspi/sspi_gss.c new file mode 100644 index 0000000..749973d --- /dev/null +++ b/winpr/libwinpr/sspi/sspi_gss.c @@ -0,0 +1,120 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Generic Security Service Application Program Interface (GSSAPI) + * + * Copyright 2015 ANSSI, Author Thomas Calderon + * Copyright 2015 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/endian.h> +#include <winpr/asn1.h> +#include <winpr/stream.h> + +#include "sspi_gss.h" + +BOOL sspi_gss_wrap_token(SecBuffer* buf, const WinPrAsn1_OID* oid, uint16_t tok_id, + const sspi_gss_data* token) +{ + WinPrAsn1Encoder* enc = NULL; + BYTE tok_id_buf[2]; + WinPrAsn1_MemoryChunk mc = { 2, tok_id_buf }; + wStream s; + size_t len = 0; + BOOL ret = FALSE; + + WINPR_ASSERT(buf); + WINPR_ASSERT(oid); + WINPR_ASSERT(token); + + Data_Write_UINT16_BE(tok_id_buf, tok_id); + + enc = WinPrAsn1Encoder_New(WINPR_ASN1_DER); + if (!enc) + return FALSE; + + /* initialContextToken [APPLICATION 0] */ + if (!WinPrAsn1EncAppContainer(enc, 0)) + goto cleanup; + + /* thisMech OID */ + if (!WinPrAsn1EncOID(enc, oid)) + goto cleanup; + + /* TOK_ID */ + if (!WinPrAsn1EncRawContent(enc, &mc)) + goto cleanup; + + /* innerToken */ + mc.data = (BYTE*)token->data; + mc.len = token->length; + if (!WinPrAsn1EncRawContent(enc, &mc)) + goto cleanup; + + if (!WinPrAsn1EncEndContainer(enc)) + goto cleanup; + + if (!WinPrAsn1EncStreamSize(enc, &len) || len > buf->cbBuffer) + goto cleanup; + + Stream_StaticInit(&s, buf->pvBuffer, len); + if (WinPrAsn1EncToStream(enc, &s)) + { + buf->cbBuffer = len; + ret = TRUE; + } + +cleanup: + WinPrAsn1Encoder_Free(&enc); + return ret; +} + +BOOL sspi_gss_unwrap_token(const SecBuffer* buf, WinPrAsn1_OID* oid, uint16_t* tok_id, + sspi_gss_data* token) +{ + WinPrAsn1Decoder dec; + WinPrAsn1Decoder dec2; + WinPrAsn1_tagId tag = 0; + wStream sbuffer = { 0 }; + wStream* s = NULL; + + WINPR_ASSERT(buf); + WINPR_ASSERT(oid); + WINPR_ASSERT(token); + + WinPrAsn1Decoder_InitMem(&dec, WINPR_ASN1_DER, buf->pvBuffer, buf->cbBuffer); + + if (!WinPrAsn1DecReadApp(&dec, &tag, &dec2) || tag != 0) + return FALSE; + + if (!WinPrAsn1DecReadOID(&dec2, oid, FALSE)) + return FALSE; + + sbuffer = WinPrAsn1DecGetStream(&dec2); + s = &sbuffer; + + if (Stream_Length(s) < 2) + return FALSE; + + if (tok_id) + Stream_Read_INT16_BE(s, *tok_id); + + token->data = Stream_Pointer(s); + token->length = (UINT)Stream_GetRemainingLength(s); + + return TRUE; +} diff --git a/winpr/libwinpr/sspi/sspi_gss.h b/winpr/libwinpr/sspi/sspi_gss.h new file mode 100644 index 0000000..205f86a --- /dev/null +++ b/winpr/libwinpr/sspi/sspi_gss.h @@ -0,0 +1,85 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Generic Security Service Application Program Interface (GSSAPI) + * + * Copyright 2015 ANSSI, Author Thomas Calderon + * Copyright 2015 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_GSS_PRIVATE_H +#define WINPR_SSPI_GSS_PRIVATE_H + +#include <winpr/sspi.h> +#include <winpr/asn1.h> + +#ifdef WITH_KRB5_MIT +#include <krb5.h> +typedef krb5_data sspi_gss_data; +#elif defined(WITH_KRB5_HEIMDAL) +#include <krb5.h> +typedef krb5_data sspi_gss_data; +#else +typedef struct +{ + int32_t magic; + unsigned int length; + char* data; +} sspi_gss_data; +#endif + +#define SSPI_GSS_C_DELEG_FLAG 1 +#define SSPI_GSS_C_MUTUAL_FLAG 2 +#define SSPI_GSS_C_REPLAY_FLAG 4 +#define SSPI_GSS_C_SEQUENCE_FLAG 8 +#define SSPI_GSS_C_CONF_FLAG 16 +#define SSPI_GSS_C_INTEG_FLAG 32 + +#define FLAG_SENDER_IS_ACCEPTOR 0x01 +#define FLAG_WRAP_CONFIDENTIAL 0x02 +#define FLAG_ACCEPTOR_SUBKEY 0x04 + +#define KG_USAGE_ACCEPTOR_SEAL 22 +#define KG_USAGE_ACCEPTOR_SIGN 23 +#define KG_USAGE_INITIATOR_SEAL 24 +#define KG_USAGE_INITIATOR_SIGN 25 + +#define TOK_ID_AP_REQ 0x0100 +#define TOK_ID_AP_REP 0x0200 +#define TOK_ID_ERROR 0x0300 +#define TOK_ID_TGT_REQ 0x0400 +#define TOK_ID_TGT_REP 0x0401 + +#define TOK_ID_MIC 0x0404 +#define TOK_ID_WRAP 0x0504 +#define TOK_ID_MIC_V1 0x0101 +#define TOK_ID_WRAP_V1 0x0201 + +#define GSS_CHECKSUM_TYPE 0x8003 + +static INLINE BOOL sspi_gss_oid_compare(const WinPrAsn1_OID* oid1, const WinPrAsn1_OID* oid2) +{ + WINPR_ASSERT(oid1); + WINPR_ASSERT(oid2); + + return (oid1->len == oid2->len) && (memcmp(oid1->data, oid2->data, oid1->len) == 0); +} + +BOOL sspi_gss_wrap_token(SecBuffer* buf, const WinPrAsn1_OID* oid, uint16_t tok_id, + const sspi_gss_data* token); +BOOL sspi_gss_unwrap_token(const SecBuffer* buf, WinPrAsn1_OID* oid, uint16_t* tok_id, + sspi_gss_data* token); + +#endif /* WINPR_SSPI_GSS_PRIVATE_H */ diff --git a/winpr/libwinpr/sspi/sspi_winpr.c b/winpr/libwinpr/sspi/sspi_winpr.c new file mode 100644 index 0000000..1978650 --- /dev/null +++ b/winpr/libwinpr/sspi/sspi_winpr.c @@ -0,0 +1,2226 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Security Support Provider Interface (SSPI) + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Dorian Ducournau <dorian.ducournau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> +#include <winpr/assert.h> +#include <winpr/windows.h> + +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/ssl.h> +#include <winpr/print.h> + +#include "sspi.h" + +#include "sspi_winpr.h" + +#include "../log.h" +#define TAG WINPR_TAG("sspi") + +/* Authentication Functions: http://msdn.microsoft.com/en-us/library/windows/desktop/aa374731/ */ + +#include "NTLM/ntlm.h" +#include "NTLM/ntlm_export.h" +#include "CredSSP/credssp.h" +#include "Kerberos/kerberos.h" +#include "Negotiate/negotiate.h" +#include "Schannel/schannel.h" + +static const SecPkgInfoA* SecPkgInfoA_LIST[] = { &NTLM_SecPkgInfoA, &KERBEROS_SecPkgInfoA, + &NEGOTIATE_SecPkgInfoA, &CREDSSP_SecPkgInfoA, + &SCHANNEL_SecPkgInfoA }; + +static const SecPkgInfoW* SecPkgInfoW_LIST[] = { &NTLM_SecPkgInfoW, &KERBEROS_SecPkgInfoW, + &NEGOTIATE_SecPkgInfoW, &CREDSSP_SecPkgInfoW, + &SCHANNEL_SecPkgInfoW }; + +static SecurityFunctionTableA winpr_SecurityFunctionTableA; +static SecurityFunctionTableW winpr_SecurityFunctionTableW; + +typedef struct +{ + const SEC_CHAR* Name; + const SecurityFunctionTableA* SecurityFunctionTable; +} SecurityFunctionTableA_NAME; + +typedef struct +{ + const SEC_WCHAR* Name; + const SecurityFunctionTableW* SecurityFunctionTable; +} SecurityFunctionTableW_NAME; + +static const SecurityFunctionTableA_NAME SecurityFunctionTableA_NAME_LIST[] = { + { "NTLM", &NTLM_SecurityFunctionTableA }, + { "Kerberos", &KERBEROS_SecurityFunctionTableA }, + { "Negotiate", &NEGOTIATE_SecurityFunctionTableA }, + { "CREDSSP", &CREDSSP_SecurityFunctionTableA }, + { "Schannel", &SCHANNEL_SecurityFunctionTableA } +}; + +static WCHAR BUFFER_NAME_LIST_W[5][32] = { 0 }; + +static const SecurityFunctionTableW_NAME SecurityFunctionTableW_NAME_LIST[] = { + { BUFFER_NAME_LIST_W[0], &NTLM_SecurityFunctionTableW }, + { BUFFER_NAME_LIST_W[1], &KERBEROS_SecurityFunctionTableW }, + { BUFFER_NAME_LIST_W[2], &NEGOTIATE_SecurityFunctionTableW }, + { BUFFER_NAME_LIST_W[3], &CREDSSP_SecurityFunctionTableW }, + { BUFFER_NAME_LIST_W[4], &SCHANNEL_SecurityFunctionTableW } +}; + +#define SecHandle_LOWER_MAX 0xFFFFFFFF +#define SecHandle_UPPER_MAX 0xFFFFFFFE + +typedef struct +{ + void* contextBuffer; + UINT32 allocatorIndex; +} CONTEXT_BUFFER_ALLOC_ENTRY; + +typedef struct +{ + UINT32 cEntries; + UINT32 cMaxEntries; + CONTEXT_BUFFER_ALLOC_ENTRY* entries; +} CONTEXT_BUFFER_ALLOC_TABLE; + +static CONTEXT_BUFFER_ALLOC_TABLE ContextBufferAllocTable = { 0 }; + +static int sspi_ContextBufferAllocTableNew(void) +{ + size_t size = 0; + ContextBufferAllocTable.entries = NULL; + ContextBufferAllocTable.cEntries = 0; + ContextBufferAllocTable.cMaxEntries = 4; + size = sizeof(CONTEXT_BUFFER_ALLOC_ENTRY) * ContextBufferAllocTable.cMaxEntries; + ContextBufferAllocTable.entries = (CONTEXT_BUFFER_ALLOC_ENTRY*)calloc(1, size); + + if (!ContextBufferAllocTable.entries) + return -1; + + return 1; +} + +static int sspi_ContextBufferAllocTableGrow(void) +{ + size_t size = 0; + CONTEXT_BUFFER_ALLOC_ENTRY* entries = NULL; + ContextBufferAllocTable.cEntries = 0; + ContextBufferAllocTable.cMaxEntries *= 2; + size = sizeof(CONTEXT_BUFFER_ALLOC_ENTRY) * ContextBufferAllocTable.cMaxEntries; + + if (!size) + return -1; + + entries = (CONTEXT_BUFFER_ALLOC_ENTRY*)realloc(ContextBufferAllocTable.entries, size); + + if (!entries) + { + free(ContextBufferAllocTable.entries); + return -1; + } + + ContextBufferAllocTable.entries = entries; + ZeroMemory((void*)&ContextBufferAllocTable.entries[ContextBufferAllocTable.cMaxEntries / 2], + size / 2); + return 1; +} + +static void sspi_ContextBufferAllocTableFree(void) +{ + if (ContextBufferAllocTable.cEntries != 0) + WLog_ERR(TAG, "ContextBufferAllocTable.entries == %" PRIu32, + ContextBufferAllocTable.cEntries); + + ContextBufferAllocTable.cEntries = ContextBufferAllocTable.cMaxEntries = 0; + free(ContextBufferAllocTable.entries); + ContextBufferAllocTable.entries = NULL; +} + +static void* sspi_ContextBufferAlloc(UINT32 allocatorIndex, size_t size) +{ + void* contextBuffer = NULL; + + for (UINT32 index = 0; index < ContextBufferAllocTable.cMaxEntries; index++) + { + if (!ContextBufferAllocTable.entries[index].contextBuffer) + { + contextBuffer = calloc(1, size); + + if (!contextBuffer) + return NULL; + + ContextBufferAllocTable.cEntries++; + ContextBufferAllocTable.entries[index].contextBuffer = contextBuffer; + ContextBufferAllocTable.entries[index].allocatorIndex = allocatorIndex; + return ContextBufferAllocTable.entries[index].contextBuffer; + } + } + + /* no available entry was found, the table needs to be grown */ + + if (sspi_ContextBufferAllocTableGrow() < 0) + return NULL; + + /* the next call to sspi_ContextBufferAlloc() should now succeed */ + return sspi_ContextBufferAlloc(allocatorIndex, size); +} + +SSPI_CREDENTIALS* sspi_CredentialsNew(void) +{ + SSPI_CREDENTIALS* credentials = NULL; + credentials = (SSPI_CREDENTIALS*)calloc(1, sizeof(SSPI_CREDENTIALS)); + return credentials; +} + +void sspi_CredentialsFree(SSPI_CREDENTIALS* credentials) +{ + size_t userLength = 0; + size_t domainLength = 0; + size_t passwordLength = 0; + + if (!credentials) + return; + + if (credentials->ntlmSettings.samFile) + free(credentials->ntlmSettings.samFile); + + userLength = credentials->identity.UserLength; + domainLength = credentials->identity.DomainLength; + passwordLength = credentials->identity.PasswordLength; + + if (passwordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) /* [pth] */ + passwordLength -= SSPI_CREDENTIALS_HASH_LENGTH_OFFSET; + + if (credentials->identity.Flags & SEC_WINNT_AUTH_IDENTITY_UNICODE) + { + userLength *= 2; + domainLength *= 2; + passwordLength *= 2; + } + + if (credentials->identity.User) + memset(credentials->identity.User, 0, userLength); + if (credentials->identity.Domain) + memset(credentials->identity.Domain, 0, domainLength); + if (credentials->identity.Password) + memset(credentials->identity.Password, 0, passwordLength); + free(credentials->identity.User); + free(credentials->identity.Domain); + free(credentials->identity.Password); + free(credentials); +} + +void* sspi_SecBufferAlloc(PSecBuffer SecBuffer, ULONG size) +{ + if (!SecBuffer) + return NULL; + + SecBuffer->pvBuffer = calloc(1, size); + + if (!SecBuffer->pvBuffer) + return NULL; + + SecBuffer->cbBuffer = size; + return SecBuffer->pvBuffer; +} + +void sspi_SecBufferFree(PSecBuffer SecBuffer) +{ + if (!SecBuffer) + return; + + if (SecBuffer->pvBuffer) + memset(SecBuffer->pvBuffer, 0, SecBuffer->cbBuffer); + + free(SecBuffer->pvBuffer); + SecBuffer->pvBuffer = NULL; + SecBuffer->cbBuffer = 0; +} + +SecHandle* sspi_SecureHandleAlloc(void) +{ + SecHandle* handle = (SecHandle*)calloc(1, sizeof(SecHandle)); + + if (!handle) + return NULL; + + SecInvalidateHandle(handle); + return handle; +} + +void* sspi_SecureHandleGetLowerPointer(SecHandle* handle) +{ + void* pointer = NULL; + + if (!handle || !SecIsValidHandle(handle) || !handle->dwLower) + return NULL; + + pointer = (void*)~((size_t)handle->dwLower); + return pointer; +} + +void sspi_SecureHandleInvalidate(SecHandle* handle) +{ + if (!handle) + return; + + handle->dwLower = 0; + handle->dwUpper = 0; +} + +void sspi_SecureHandleSetLowerPointer(SecHandle* handle, void* pointer) +{ + if (!handle) + return; + + handle->dwLower = (ULONG_PTR)(~((size_t)pointer)); +} + +void* sspi_SecureHandleGetUpperPointer(SecHandle* handle) +{ + void* pointer = NULL; + + if (!handle || !SecIsValidHandle(handle) || !handle->dwUpper) + return NULL; + + pointer = (void*)~((size_t)handle->dwUpper); + return pointer; +} + +void sspi_SecureHandleSetUpperPointer(SecHandle* handle, void* pointer) +{ + if (!handle) + return; + + handle->dwUpper = (ULONG_PTR)(~((size_t)pointer)); +} + +void sspi_SecureHandleFree(SecHandle* handle) +{ + free(handle); +} + +int sspi_SetAuthIdentityW(SEC_WINNT_AUTH_IDENTITY* identity, const WCHAR* user, const WCHAR* domain, + const WCHAR* password) +{ + return sspi_SetAuthIdentityWithLengthW(identity, user, user ? _wcslen(user) : 0, domain, + domain ? _wcslen(domain) : 0, password, + password ? _wcslen(password) : 0); +} + +static BOOL copy(WCHAR** dst, ULONG* dstLen, const WCHAR* what, size_t len) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(dstLen); + + *dst = NULL; + *dstLen = 0; + + *dst = calloc(sizeof(WCHAR), len + 1); + if (!*dst) + return FALSE; + memcpy(*dst, what, len * sizeof(WCHAR)); + *dstLen = len; + return TRUE; +} + +int sspi_SetAuthIdentityWithLengthW(SEC_WINNT_AUTH_IDENTITY* identity, const WCHAR* user, + size_t userLen, const WCHAR* domain, size_t domainLen, + const WCHAR* password, size_t passwordLen) +{ + WINPR_ASSERT(identity); + sspi_FreeAuthIdentity(identity); + identity->Flags &= ~SEC_WINNT_AUTH_IDENTITY_ANSI; + identity->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE; + + if (!copy(&identity->User, &identity->UserLength, user, userLen)) + return -1; + + if (!copy(&identity->Domain, &identity->DomainLength, domain, domainLen)) + return -1; + + if (!copy(&identity->Password, &identity->PasswordLength, password, passwordLen)) + return -1; + + return 1; +} + +static void zfree(WCHAR* str, size_t len) +{ + if (str) + memset(str, 0, len * sizeof(WCHAR)); + free(str); +} + +int sspi_SetAuthIdentityA(SEC_WINNT_AUTH_IDENTITY* identity, const char* user, const char* domain, + const char* password) +{ + int rc = 0; + size_t unicodeUserLenW = 0; + size_t unicodeDomainLenW = 0; + size_t unicodePasswordLenW = 0; + LPWSTR unicodeUser = ConvertUtf8ToWCharAlloc(user, &unicodeUserLenW); + LPWSTR unicodeDomain = ConvertUtf8ToWCharAlloc(domain, &unicodeDomainLenW); + LPWSTR unicodePassword = ConvertUtf8ToWCharAlloc(password, &unicodePasswordLenW); + + rc = sspi_SetAuthIdentityWithLengthW(identity, unicodeUser, unicodeUserLenW, unicodeDomain, + unicodeDomainLenW, unicodePassword, unicodePasswordLenW); + + zfree(unicodeUser, unicodeUserLenW); + zfree(unicodeDomain, unicodeDomainLenW); + zfree(unicodePassword, unicodePasswordLenW); + return rc; +} + +UINT32 sspi_GetAuthIdentityVersion(const void* identity) +{ + UINT32 version = 0; + + if (!identity) + return 0; + + version = *((const UINT32*)identity); + + if ((version == SEC_WINNT_AUTH_IDENTITY_VERSION) || + (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2)) + { + return version; + } + + return 0; // SEC_WINNT_AUTH_IDENTITY (no version) +} + +UINT32 sspi_GetAuthIdentityFlags(const void* identity) +{ + UINT32 version = 0; + UINT32 flags = 0; + + if (!identity) + return 0; + + version = sspi_GetAuthIdentityVersion(identity); + + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + flags = ((const SEC_WINNT_AUTH_IDENTITY_EX*)identity)->Flags; + } + else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2) + { + flags = ((const SEC_WINNT_AUTH_IDENTITY_EX2*)identity)->Flags; + } + else // SEC_WINNT_AUTH_IDENTITY + { + flags = ((const SEC_WINNT_AUTH_IDENTITY*)identity)->Flags; + } + + return flags; +} + +BOOL sspi_GetAuthIdentityUserDomainW(const void* identity, const WCHAR** pUser, UINT32* pUserLength, + const WCHAR** pDomain, UINT32* pDomainLength) +{ + UINT32 version = 0; + + if (!identity) + return FALSE; + + version = sspi_GetAuthIdentityVersion(identity); + + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + const SEC_WINNT_AUTH_IDENTITY_EXW* id = (const SEC_WINNT_AUTH_IDENTITY_EXW*)identity; + *pUser = (const WCHAR*)id->User; + *pUserLength = id->UserLength; + *pDomain = (const WCHAR*)id->Domain; + *pDomainLength = id->DomainLength; + } + else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2) + { + const SEC_WINNT_AUTH_IDENTITY_EX2* id = (const SEC_WINNT_AUTH_IDENTITY_EX2*)identity; + UINT32 UserOffset = id->UserOffset; + UINT32 DomainOffset = id->DomainOffset; + *pUser = (const WCHAR*)&((const uint8_t*)identity)[UserOffset]; + *pUserLength = id->UserLength / 2; + *pDomain = (const WCHAR*)&((const uint8_t*)identity)[DomainOffset]; + *pDomainLength = id->DomainLength / 2; + } + else // SEC_WINNT_AUTH_IDENTITY + { + const SEC_WINNT_AUTH_IDENTITY_W* id = (const SEC_WINNT_AUTH_IDENTITY_W*)identity; + *pUser = (const WCHAR*)id->User; + *pUserLength = id->UserLength; + *pDomain = (const WCHAR*)id->Domain; + *pDomainLength = id->DomainLength; + } + + return TRUE; +} + +BOOL sspi_GetAuthIdentityUserDomainA(const void* identity, const char** pUser, UINT32* pUserLength, + const char** pDomain, UINT32* pDomainLength) +{ + UINT32 version = 0; + + if (!identity) + return FALSE; + + version = sspi_GetAuthIdentityVersion(identity); + + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + const SEC_WINNT_AUTH_IDENTITY_EXA* id = (const SEC_WINNT_AUTH_IDENTITY_EXA*)identity; + *pUser = (const char*)id->User; + *pUserLength = id->UserLength; + *pDomain = (const char*)id->Domain; + *pDomainLength = id->DomainLength; + } + else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2) + { + const SEC_WINNT_AUTH_IDENTITY_EX2* id = (const SEC_WINNT_AUTH_IDENTITY_EX2*)identity; + UINT32 UserOffset = id->UserOffset; + UINT32 DomainOffset = id->DomainOffset; + *pUser = (const char*)&((const uint8_t*)identity)[UserOffset]; + *pUserLength = id->UserLength; + *pDomain = (const char*)&((const uint8_t*)identity)[DomainOffset]; + *pDomainLength = id->DomainLength; + } + else // SEC_WINNT_AUTH_IDENTITY + { + const SEC_WINNT_AUTH_IDENTITY_A* id = (const SEC_WINNT_AUTH_IDENTITY_A*)identity; + *pUser = (const char*)id->User; + *pUserLength = id->UserLength; + *pDomain = (const char*)id->Domain; + *pDomainLength = id->DomainLength; + } + + return TRUE; +} + +BOOL sspi_GetAuthIdentityPasswordW(const void* identity, const WCHAR** pPassword, + UINT32* pPasswordLength) +{ + UINT32 version = 0; + + if (!identity) + return FALSE; + + version = sspi_GetAuthIdentityVersion(identity); + + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + const SEC_WINNT_AUTH_IDENTITY_EXW* id = (const SEC_WINNT_AUTH_IDENTITY_EXW*)identity; + *pPassword = (const WCHAR*)id->Password; + *pPasswordLength = id->PasswordLength; + } + else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2) + { + return FALSE; // TODO: packed credentials + } + else // SEC_WINNT_AUTH_IDENTITY + { + const SEC_WINNT_AUTH_IDENTITY_W* id = (const SEC_WINNT_AUTH_IDENTITY_W*)identity; + *pPassword = (const WCHAR*)id->Password; + *pPasswordLength = id->PasswordLength; + } + + return TRUE; +} + +BOOL sspi_GetAuthIdentityPasswordA(const void* identity, const char** pPassword, + UINT32* pPasswordLength) +{ + UINT32 version = 0; + + if (!identity) + return FALSE; + + version = sspi_GetAuthIdentityVersion(identity); + + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + const SEC_WINNT_AUTH_IDENTITY_EXA* id = (const SEC_WINNT_AUTH_IDENTITY_EXA*)identity; + *pPassword = (const char*)id->Password; + *pPasswordLength = id->PasswordLength; + } + else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2) + { + return FALSE; // TODO: packed credentials + } + else // SEC_WINNT_AUTH_IDENTITY + { + const SEC_WINNT_AUTH_IDENTITY_A* id = (const SEC_WINNT_AUTH_IDENTITY_A*)identity; + *pPassword = (const char*)id->Password; + *pPasswordLength = id->PasswordLength; + } + + return TRUE; +} + +BOOL sspi_CopyAuthIdentityFieldsA(const SEC_WINNT_AUTH_IDENTITY_INFO* identity, char** pUser, + char** pDomain, char** pPassword) +{ + BOOL success = FALSE; + const char* UserA = NULL; + const char* DomainA = NULL; + const char* PasswordA = NULL; + const WCHAR* UserW = NULL; + const WCHAR* DomainW = NULL; + const WCHAR* PasswordW = NULL; + UINT32 UserLength = 0; + UINT32 DomainLength = 0; + UINT32 PasswordLength = 0; + + if (!identity || !pUser || !pDomain || !pPassword) + return FALSE; + + *pUser = *pDomain = *pPassword = NULL; + + UINT32 identityFlags = sspi_GetAuthIdentityFlags(identity); + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI) + { + if (!sspi_GetAuthIdentityUserDomainA(identity, &UserA, &UserLength, &DomainA, + &DomainLength)) + goto cleanup; + + if (!sspi_GetAuthIdentityPasswordA(identity, &PasswordA, &PasswordLength)) + goto cleanup; + + if (UserA && UserLength) + { + *pUser = _strdup(UserA); + + if (!(*pUser)) + goto cleanup; + } + + if (DomainA && DomainLength) + { + *pDomain = _strdup(DomainA); + + if (!(*pDomain)) + goto cleanup; + } + + if (PasswordA && PasswordLength) + { + *pPassword = _strdup(PasswordA); + + if (!(*pPassword)) + goto cleanup; + } + + success = TRUE; + } + else + { + if (!sspi_GetAuthIdentityUserDomainW(identity, &UserW, &UserLength, &DomainW, + &DomainLength)) + goto cleanup; + + if (!sspi_GetAuthIdentityPasswordW(identity, &PasswordW, &PasswordLength)) + goto cleanup; + + if (UserW && (UserLength > 0)) + { + *pUser = ConvertWCharNToUtf8Alloc(UserW, UserLength, NULL); + if (!(*pUser)) + goto cleanup; + } + + if (DomainW && (DomainLength > 0)) + { + *pDomain = ConvertWCharNToUtf8Alloc(DomainW, DomainLength, NULL); + if (!(*pDomain)) + goto cleanup; + } + + if (PasswordW && (PasswordLength > 0)) + { + *pPassword = ConvertWCharNToUtf8Alloc(PasswordW, PasswordLength, NULL); + if (!(*pPassword)) + goto cleanup; + } + + success = TRUE; + } + +cleanup: + return success; +} + +BOOL sspi_CopyAuthIdentityFieldsW(const SEC_WINNT_AUTH_IDENTITY_INFO* identity, WCHAR** pUser, + WCHAR** pDomain, WCHAR** pPassword) +{ + BOOL success = FALSE; + const char* UserA = NULL; + const char* DomainA = NULL; + const char* PasswordA = NULL; + const WCHAR* UserW = NULL; + const WCHAR* DomainW = NULL; + const WCHAR* PasswordW = NULL; + UINT32 UserLength = 0; + UINT32 DomainLength = 0; + UINT32 PasswordLength = 0; + + if (!identity || !pUser || !pDomain || !pPassword) + return FALSE; + + *pUser = *pDomain = *pPassword = NULL; + + UINT32 identityFlags = sspi_GetAuthIdentityFlags(identity); + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI) + { + if (!sspi_GetAuthIdentityUserDomainA(identity, &UserA, &UserLength, &DomainA, + &DomainLength)) + goto cleanup; + + if (!sspi_GetAuthIdentityPasswordA(identity, &PasswordA, &PasswordLength)) + goto cleanup; + + if (UserA && (UserLength > 0)) + { + WCHAR* ptr = ConvertUtf8NToWCharAlloc(UserA, UserLength, NULL); + *pUser = ptr; + + if (!ptr) + goto cleanup; + } + + if (DomainA && (DomainLength > 0)) + { + WCHAR* ptr = ConvertUtf8NToWCharAlloc(DomainA, DomainLength, NULL); + *pDomain = ptr; + if (!ptr) + goto cleanup; + } + + if (PasswordA && (PasswordLength > 0)) + { + WCHAR* ptr = ConvertUtf8NToWCharAlloc(PasswordA, PasswordLength, NULL); + + *pPassword = ptr; + if (!ptr) + goto cleanup; + } + + success = TRUE; + } + else + { + if (!sspi_GetAuthIdentityUserDomainW(identity, &UserW, &UserLength, &DomainW, + &DomainLength)) + goto cleanup; + + if (!sspi_GetAuthIdentityPasswordW(identity, &PasswordW, &PasswordLength)) + goto cleanup; + + if (UserW && UserLength) + { + *pUser = _wcsdup(UserW); + + if (!(*pUser)) + goto cleanup; + } + + if (DomainW && DomainLength) + { + *pDomain = _wcsdup(DomainW); + + if (!(*pDomain)) + goto cleanup; + } + + if (PasswordW && PasswordLength) + { + *pPassword = _wcsdup(PasswordW); + + if (!(*pPassword)) + goto cleanup; + } + + success = TRUE; + } + +cleanup: + return success; +} + +BOOL sspi_CopyAuthPackageListA(const SEC_WINNT_AUTH_IDENTITY_INFO* identity, char** pPackageList) +{ + UINT32 version = 0; + UINT32 identityFlags = 0; + char* PackageList = NULL; + const char* PackageListA = NULL; + const WCHAR* PackageListW = NULL; + UINT32 PackageListLength = 0; + UINT32 PackageListOffset = 0; + const void* pAuthData = (const void*)identity; + + if (!pAuthData) + return FALSE; + + version = sspi_GetAuthIdentityVersion(pAuthData); + identityFlags = sspi_GetAuthIdentityFlags(pAuthData); + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI) + { + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + const SEC_WINNT_AUTH_IDENTITY_EXA* ad = (const SEC_WINNT_AUTH_IDENTITY_EXA*)pAuthData; + PackageListA = (const char*)ad->PackageList; + PackageListLength = ad->PackageListLength; + } + + if (PackageListA && PackageListLength) + { + PackageList = _strdup(PackageListA); + } + } + else + { + if (version == SEC_WINNT_AUTH_IDENTITY_VERSION) + { + const SEC_WINNT_AUTH_IDENTITY_EXW* ad = (const SEC_WINNT_AUTH_IDENTITY_EXW*)pAuthData; + PackageListW = (const WCHAR*)ad->PackageList; + PackageListLength = ad->PackageListLength; + } + else if (version == SEC_WINNT_AUTH_IDENTITY_VERSION_2) + { + const SEC_WINNT_AUTH_IDENTITY_EX2* ad = (const SEC_WINNT_AUTH_IDENTITY_EX2*)pAuthData; + PackageListOffset = ad->PackageListOffset; + PackageListW = (const WCHAR*)&((const uint8_t*)pAuthData)[PackageListOffset]; + PackageListLength = ad->PackageListLength / 2; + } + + if (PackageListW && (PackageListLength > 0)) + PackageList = ConvertWCharNToUtf8Alloc(PackageListW, PackageListLength, NULL); + } + + if (PackageList) + { + *pPackageList = PackageList; + return TRUE; + } + + return FALSE; +} + +int sspi_CopyAuthIdentity(SEC_WINNT_AUTH_IDENTITY* identity, + const SEC_WINNT_AUTH_IDENTITY_INFO* srcIdentity) +{ + int status = 0; + UINT32 identityFlags = 0; + const char* UserA = NULL; + const char* DomainA = NULL; + const char* PasswordA = NULL; + const WCHAR* UserW = NULL; + const WCHAR* DomainW = NULL; + const WCHAR* PasswordW = NULL; + UINT32 UserLength = 0; + UINT32 DomainLength = 0; + UINT32 PasswordLength = 0; + + sspi_FreeAuthIdentity(identity); + + identityFlags = sspi_GetAuthIdentityFlags(srcIdentity); + + identity->Flags = identityFlags; + + if (identityFlags & SEC_WINNT_AUTH_IDENTITY_ANSI) + { + if (!sspi_GetAuthIdentityUserDomainA(srcIdentity, &UserA, &UserLength, &DomainA, + &DomainLength)) + { + return -1; + } + + if (!sspi_GetAuthIdentityPasswordA(srcIdentity, &PasswordA, &PasswordLength)) + { + return -1; + } + + status = sspi_SetAuthIdentity(identity, UserA, DomainA, PasswordA); + + if (status <= 0) + return -1; + + identity->Flags &= ~SEC_WINNT_AUTH_IDENTITY_ANSI; + identity->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE; + return 1; + } + + identity->Flags |= SEC_WINNT_AUTH_IDENTITY_UNICODE; + + if (!sspi_GetAuthIdentityUserDomainW(srcIdentity, &UserW, &UserLength, &DomainW, &DomainLength)) + { + return -1; + } + + if (!sspi_GetAuthIdentityPasswordW(srcIdentity, &PasswordW, &PasswordLength)) + { + return -1; + } + + /* login/password authentication */ + identity->UserLength = UserLength; + + if (identity->UserLength > 0) + { + identity->User = (UINT16*)calloc((identity->UserLength + 1), sizeof(WCHAR)); + + if (!identity->User) + return -1; + + CopyMemory(identity->User, UserW, identity->UserLength * sizeof(WCHAR)); + identity->User[identity->UserLength] = 0; + } + + identity->DomainLength = DomainLength; + + if (identity->DomainLength > 0) + { + identity->Domain = (UINT16*)calloc((identity->DomainLength + 1), sizeof(WCHAR)); + + if (!identity->Domain) + return -1; + + CopyMemory(identity->Domain, DomainW, identity->DomainLength * sizeof(WCHAR)); + identity->Domain[identity->DomainLength] = 0; + } + + identity->PasswordLength = PasswordLength; + + if (identity->PasswordLength > SSPI_CREDENTIALS_HASH_LENGTH_OFFSET) + identity->PasswordLength -= SSPI_CREDENTIALS_HASH_LENGTH_OFFSET; + + if (PasswordW) + { + identity->Password = (UINT16*)calloc((identity->PasswordLength + 1), sizeof(WCHAR)); + + if (!identity->Password) + return -1; + + CopyMemory(identity->Password, PasswordW, identity->PasswordLength * sizeof(WCHAR)); + identity->Password[identity->PasswordLength] = 0; + } + + identity->PasswordLength = PasswordLength; + /* End of login/password authentication */ + return 1; +} + +PSecBuffer sspi_FindSecBuffer(PSecBufferDesc pMessage, ULONG BufferType) +{ + PSecBuffer pSecBuffer = NULL; + + for (UINT32 index = 0; index < pMessage->cBuffers; index++) + { + if (pMessage->pBuffers[index].BufferType == BufferType) + { + pSecBuffer = &pMessage->pBuffers[index]; + break; + } + } + + return pSecBuffer; +} + +static BOOL WINPR_init(void) +{ + + for (size_t x = 0; x < ARRAYSIZE(SecurityFunctionTableA_NAME_LIST); x++) + { + const SecurityFunctionTableA_NAME* const cur = &SecurityFunctionTableA_NAME_LIST[x]; + InitializeConstWCharFromUtf8(cur->Name, BUFFER_NAME_LIST_W[x], + ARRAYSIZE(BUFFER_NAME_LIST_W[x])); + } + return TRUE; +} + +static BOOL CALLBACK sspi_init(PINIT_ONCE InitOnce, PVOID Parameter, PVOID* Context) +{ + winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT); + sspi_ContextBufferAllocTableNew(); + if (!SCHANNEL_init()) + return FALSE; + if (!KERBEROS_init()) + return FALSE; + if (!NTLM_init()) + return FALSE; + if (!CREDSSP_init()) + return FALSE; + if (!NEGOTIATE_init()) + return FALSE; + return WINPR_init(); +} + +void sspi_GlobalInit(void) +{ + static INIT_ONCE once = INIT_ONCE_STATIC_INIT; + DWORD flags = 0; + InitOnceExecuteOnce(&once, sspi_init, &flags, NULL); +} + +void sspi_GlobalFinish(void) +{ + sspi_ContextBufferAllocTableFree(); +} + +static const SecurityFunctionTableA* sspi_GetSecurityFunctionTableAByNameA(const SEC_CHAR* Name) +{ + size_t cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST)); + + for (size_t index = 0; index < cPackages; index++) + { + if (strcmp(Name, SecurityFunctionTableA_NAME_LIST[index].Name) == 0) + { + return (const SecurityFunctionTableA*)SecurityFunctionTableA_NAME_LIST[index] + .SecurityFunctionTable; + } + } + + return NULL; +} + +static const SecurityFunctionTableW* sspi_GetSecurityFunctionTableWByNameW(const SEC_WCHAR* Name) +{ + size_t cPackages = sizeof(SecPkgInfoW_LIST) / sizeof(*(SecPkgInfoW_LIST)); + + for (size_t index = 0; index < cPackages; index++) + { + if (_wcscmp(Name, SecurityFunctionTableW_NAME_LIST[index].Name) == 0) + { + return (const SecurityFunctionTableW*)SecurityFunctionTableW_NAME_LIST[index] + .SecurityFunctionTable; + } + } + + return NULL; +} + +static const SecurityFunctionTableW* sspi_GetSecurityFunctionTableWByNameA(const SEC_CHAR* Name) +{ + SEC_WCHAR* NameW = NULL; + const SecurityFunctionTableW* table = NULL; + + if (!Name) + return NULL; + + NameW = ConvertUtf8ToWCharAlloc(Name, NULL); + + if (!NameW) + return NULL; + + table = sspi_GetSecurityFunctionTableWByNameW(NameW); + free(NameW); + return table; +} + +static void FreeContextBuffer_EnumerateSecurityPackages(void* contextBuffer); +static void FreeContextBuffer_QuerySecurityPackageInfo(void* contextBuffer); + +static void sspi_ContextBufferFree(void* contextBuffer) +{ + UINT32 allocatorIndex = 0; + + for (size_t index = 0; index < ContextBufferAllocTable.cMaxEntries; index++) + { + if (contextBuffer == ContextBufferAllocTable.entries[index].contextBuffer) + { + contextBuffer = ContextBufferAllocTable.entries[index].contextBuffer; + allocatorIndex = ContextBufferAllocTable.entries[index].allocatorIndex; + ContextBufferAllocTable.cEntries--; + ContextBufferAllocTable.entries[index].allocatorIndex = 0; + ContextBufferAllocTable.entries[index].contextBuffer = NULL; + + switch (allocatorIndex) + { + case EnumerateSecurityPackagesIndex: + FreeContextBuffer_EnumerateSecurityPackages(contextBuffer); + break; + + case QuerySecurityPackageInfoIndex: + FreeContextBuffer_QuerySecurityPackageInfo(contextBuffer); + break; + } + } + } +} + +/** + * Standard SSPI API + */ + +/* Package Management */ + +static SECURITY_STATUS SEC_ENTRY winpr_EnumerateSecurityPackagesW(ULONG* pcPackages, + PSecPkgInfoW* ppPackageInfo) +{ + size_t size = 0; + UINT32 cPackages = 0; + SecPkgInfoW* pPackageInfo = NULL; + cPackages = sizeof(SecPkgInfoW_LIST) / sizeof(*(SecPkgInfoW_LIST)); + size = sizeof(SecPkgInfoW) * cPackages; + pPackageInfo = (SecPkgInfoW*)sspi_ContextBufferAlloc(EnumerateSecurityPackagesIndex, size); + + if (!pPackageInfo) + return SEC_E_INSUFFICIENT_MEMORY; + + for (size_t index = 0; index < cPackages; index++) + { + pPackageInfo[index].fCapabilities = SecPkgInfoW_LIST[index]->fCapabilities; + pPackageInfo[index].wVersion = SecPkgInfoW_LIST[index]->wVersion; + pPackageInfo[index].wRPCID = SecPkgInfoW_LIST[index]->wRPCID; + pPackageInfo[index].cbMaxToken = SecPkgInfoW_LIST[index]->cbMaxToken; + pPackageInfo[index].Name = _wcsdup(SecPkgInfoW_LIST[index]->Name); + pPackageInfo[index].Comment = _wcsdup(SecPkgInfoW_LIST[index]->Comment); + } + + *(pcPackages) = cPackages; + *(ppPackageInfo) = pPackageInfo; + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY winpr_EnumerateSecurityPackagesA(ULONG* pcPackages, + PSecPkgInfoA* ppPackageInfo) +{ + size_t size = 0; + UINT32 cPackages = 0; + SecPkgInfoA* pPackageInfo = NULL; + cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST)); + size = sizeof(SecPkgInfoA) * cPackages; + pPackageInfo = (SecPkgInfoA*)sspi_ContextBufferAlloc(EnumerateSecurityPackagesIndex, size); + + if (!pPackageInfo) + return SEC_E_INSUFFICIENT_MEMORY; + + for (size_t index = 0; index < cPackages; index++) + { + pPackageInfo[index].fCapabilities = SecPkgInfoA_LIST[index]->fCapabilities; + pPackageInfo[index].wVersion = SecPkgInfoA_LIST[index]->wVersion; + pPackageInfo[index].wRPCID = SecPkgInfoA_LIST[index]->wRPCID; + pPackageInfo[index].cbMaxToken = SecPkgInfoA_LIST[index]->cbMaxToken; + pPackageInfo[index].Name = _strdup(SecPkgInfoA_LIST[index]->Name); + pPackageInfo[index].Comment = _strdup(SecPkgInfoA_LIST[index]->Comment); + + if (!pPackageInfo[index].Name || !pPackageInfo[index].Comment) + { + sspi_ContextBufferFree(pPackageInfo); + return SEC_E_INSUFFICIENT_MEMORY; + } + } + + *(pcPackages) = cPackages; + *(ppPackageInfo) = pPackageInfo; + return SEC_E_OK; +} + +static void FreeContextBuffer_EnumerateSecurityPackages(void* contextBuffer) +{ + UINT32 cPackages = 0; + SecPkgInfoA* pPackageInfo = (SecPkgInfoA*)contextBuffer; + cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST)); + + if (!pPackageInfo) + return; + + for (size_t index = 0; index < cPackages; index++) + { + free(pPackageInfo[index].Name); + free(pPackageInfo[index].Comment); + } + + free(pPackageInfo); +} + +SecurityFunctionTableW* SEC_ENTRY winpr_InitSecurityInterfaceW(void) +{ + return &winpr_SecurityFunctionTableW; +} + +SecurityFunctionTableA* SEC_ENTRY winpr_InitSecurityInterfaceA(void) +{ + return &winpr_SecurityFunctionTableA; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QuerySecurityPackageInfoW(SEC_WCHAR* pszPackageName, + PSecPkgInfoW* ppPackageInfo) +{ + size_t size = 0; + SecPkgInfoW* pPackageInfo = NULL; + size_t cPackages = sizeof(SecPkgInfoW_LIST) / sizeof(*(SecPkgInfoW_LIST)); + + for (size_t index = 0; index < cPackages; index++) + { + if (_wcscmp(pszPackageName, SecPkgInfoW_LIST[index]->Name) == 0) + { + size = sizeof(SecPkgInfoW); + pPackageInfo = + (SecPkgInfoW*)sspi_ContextBufferAlloc(QuerySecurityPackageInfoIndex, size); + + if (!pPackageInfo) + return SEC_E_INSUFFICIENT_MEMORY; + + pPackageInfo->fCapabilities = SecPkgInfoW_LIST[index]->fCapabilities; + pPackageInfo->wVersion = SecPkgInfoW_LIST[index]->wVersion; + pPackageInfo->wRPCID = SecPkgInfoW_LIST[index]->wRPCID; + pPackageInfo->cbMaxToken = SecPkgInfoW_LIST[index]->cbMaxToken; + pPackageInfo->Name = _wcsdup(SecPkgInfoW_LIST[index]->Name); + pPackageInfo->Comment = _wcsdup(SecPkgInfoW_LIST[index]->Comment); + *(ppPackageInfo) = pPackageInfo; + return SEC_E_OK; + } + } + + *(ppPackageInfo) = NULL; + return SEC_E_SECPKG_NOT_FOUND; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QuerySecurityPackageInfoA(SEC_CHAR* pszPackageName, + PSecPkgInfoA* ppPackageInfo) +{ + size_t size = 0; + SecPkgInfoA* pPackageInfo = NULL; + size_t cPackages = sizeof(SecPkgInfoA_LIST) / sizeof(*(SecPkgInfoA_LIST)); + + for (size_t index = 0; index < cPackages; index++) + { + if (strcmp(pszPackageName, SecPkgInfoA_LIST[index]->Name) == 0) + { + size = sizeof(SecPkgInfoA); + pPackageInfo = + (SecPkgInfoA*)sspi_ContextBufferAlloc(QuerySecurityPackageInfoIndex, size); + + if (!pPackageInfo) + return SEC_E_INSUFFICIENT_MEMORY; + + pPackageInfo->fCapabilities = SecPkgInfoA_LIST[index]->fCapabilities; + pPackageInfo->wVersion = SecPkgInfoA_LIST[index]->wVersion; + pPackageInfo->wRPCID = SecPkgInfoA_LIST[index]->wRPCID; + pPackageInfo->cbMaxToken = SecPkgInfoA_LIST[index]->cbMaxToken; + pPackageInfo->Name = _strdup(SecPkgInfoA_LIST[index]->Name); + pPackageInfo->Comment = _strdup(SecPkgInfoA_LIST[index]->Comment); + + if (!pPackageInfo->Name || !pPackageInfo->Comment) + { + sspi_ContextBufferFree(pPackageInfo); + return SEC_E_INSUFFICIENT_MEMORY; + } + + *(ppPackageInfo) = pPackageInfo; + return SEC_E_OK; + } + } + + *(ppPackageInfo) = NULL; + return SEC_E_SECPKG_NOT_FOUND; +} + +void FreeContextBuffer_QuerySecurityPackageInfo(void* contextBuffer) +{ + SecPkgInfo* pPackageInfo = (SecPkgInfo*)contextBuffer; + + if (!pPackageInfo) + return; + + free(pPackageInfo->Name); + free(pPackageInfo->Comment); + free(pPackageInfo); +} + +/* Credential Management */ + +static SECURITY_STATUS SEC_ENTRY winpr_AcquireCredentialsHandleW( + SEC_WCHAR* pszPrincipal, SEC_WCHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = sspi_GetSecurityFunctionTableWByNameW(pszPackage); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->AcquireCredentialsHandleW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->AcquireCredentialsHandleW(pszPrincipal, pszPackage, fCredentialUse, pvLogonID, + pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential, + ptsExpiry); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "AcquireCredentialsHandleW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_AcquireCredentialsHandleA( + SEC_CHAR* pszPrincipal, SEC_CHAR* pszPackage, ULONG fCredentialUse, void* pvLogonID, + void* pAuthData, SEC_GET_KEY_FN pGetKeyFn, void* pvGetKeyArgument, PCredHandle phCredential, + PTimeStamp ptsExpiry) +{ + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = sspi_GetSecurityFunctionTableAByNameA(pszPackage); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->AcquireCredentialsHandleA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->AcquireCredentialsHandleA(pszPrincipal, pszPackage, fCredentialUse, pvLogonID, + pAuthData, pGetKeyFn, pvGetKeyArgument, phCredential, + ptsExpiry); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "AcquireCredentialsHandleA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_ExportSecurityContext(PCtxtHandle phContext, ULONG fFlags, + PSecBuffer pPackedContext, + HANDLE* pToken) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->ExportSecurityContext) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->ExportSecurityContext(phContext, fFlags, pPackedContext, pToken); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "ExportSecurityContext status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_FreeCredentialsHandle(PCredHandle phCredential) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->FreeCredentialsHandle) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->FreeCredentialsHandle(phCredential); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "FreeCredentialsHandle status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_ImportSecurityContextW(SEC_WCHAR* pszPackage, + PSecBuffer pPackedContext, + HANDLE pToken, PCtxtHandle phContext) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->ImportSecurityContextW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->ImportSecurityContextW(pszPackage, pPackedContext, pToken, phContext); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "ImportSecurityContextW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_ImportSecurityContextA(SEC_CHAR* pszPackage, + PSecBuffer pPackedContext, + HANDLE pToken, PCtxtHandle phContext) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->ImportSecurityContextA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->ImportSecurityContextA(pszPackage, pPackedContext, pToken, phContext); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "ImportSecurityContextA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QueryCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + SEC_WCHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_WCHAR*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameW(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->QueryCredentialsAttributesW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->QueryCredentialsAttributesW(phCredential, ulAttribute, pBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "QueryCredentialsAttributesW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QueryCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->QueryCredentialsAttributesA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->QueryCredentialsAttributesA(phCredential, ulAttribute, pBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "QueryCredentialsAttributesA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_SetCredentialsAttributesW(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + SEC_WCHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_WCHAR*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameW(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->SetCredentialsAttributesW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->SetCredentialsAttributesW(phCredential, ulAttribute, pBuffer, cbBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "SetCredentialsAttributesW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_SetCredentialsAttributesA(PCredHandle phCredential, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->SetCredentialsAttributesA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->SetCredentialsAttributesA(phCredential, ulAttribute, pBuffer, cbBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "SetCredentialsAttributesA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +/* Context Management */ + +static SECURITY_STATUS SEC_ENTRY +winpr_AcceptSecurityContext(PCredHandle phCredential, PCtxtHandle phContext, PSecBufferDesc pInput, + ULONG fContextReq, ULONG TargetDataRep, PCtxtHandle phNewContext, + PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsTimeStamp) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->AcceptSecurityContext) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = + table->AcceptSecurityContext(phCredential, phContext, pInput, fContextReq, TargetDataRep, + phNewContext, pOutput, pfContextAttr, ptsTimeStamp); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "AcceptSecurityContext status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_ApplyControlToken(PCtxtHandle phContext, + PSecBufferDesc pInput) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->ApplyControlToken) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->ApplyControlToken(phContext, pInput); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "ApplyControlToken status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_CompleteAuthToken(PCtxtHandle phContext, + PSecBufferDesc pToken) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->CompleteAuthToken) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->CompleteAuthToken(phContext, pToken); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "CompleteAuthToken status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_DeleteSecurityContext(PCtxtHandle phContext) +{ + const char* Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + const SecurityFunctionTableA* table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->DeleteSecurityContext) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + const UINT32 status = table->DeleteSecurityContext(phContext); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "DeleteSecurityContext status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_FreeContextBuffer(void* pvContextBuffer) +{ + if (!pvContextBuffer) + return SEC_E_INVALID_HANDLE; + + sspi_ContextBufferFree(pvContextBuffer); + return SEC_E_OK; +} + +static SECURITY_STATUS SEC_ENTRY winpr_ImpersonateSecurityContext(PCtxtHandle phContext) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->ImpersonateSecurityContext) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->ImpersonateSecurityContext(phContext); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "ImpersonateSecurityContext status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_InitializeSecurityContextW( + PCredHandle phCredential, PCtxtHandle phContext, SEC_WCHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->InitializeSecurityContextW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->InitializeSecurityContextW(phCredential, phContext, pszTargetName, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "InitializeSecurityContextW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_InitializeSecurityContextA( + PCredHandle phCredential, PCtxtHandle phContext, SEC_CHAR* pszTargetName, ULONG fContextReq, + ULONG Reserved1, ULONG TargetDataRep, PSecBufferDesc pInput, ULONG Reserved2, + PCtxtHandle phNewContext, PSecBufferDesc pOutput, PULONG pfContextAttr, PTimeStamp ptsExpiry) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phCredential); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->InitializeSecurityContextA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->InitializeSecurityContextA(phCredential, phContext, pszTargetName, fContextReq, + Reserved1, TargetDataRep, pInput, Reserved2, + phNewContext, pOutput, pfContextAttr, ptsExpiry); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "InitializeSecurityContextA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QueryContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->QueryContextAttributesW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->QueryContextAttributesW(phContext, ulAttribute, pBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "QueryContextAttributesW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QueryContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->QueryContextAttributesA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->QueryContextAttributesA(phContext, ulAttribute, pBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "QueryContextAttributesA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_QuerySecurityContextToken(PCtxtHandle phContext, + HANDLE* phToken) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->QuerySecurityContextToken) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->QuerySecurityContextToken(phContext, phToken); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "QuerySecurityContextToken status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_SetContextAttributesW(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->SetContextAttributesW) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->SetContextAttributesW(phContext, ulAttribute, pBuffer, cbBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "SetContextAttributesW status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_SetContextAttributesA(PCtxtHandle phContext, + ULONG ulAttribute, void* pBuffer, + ULONG cbBuffer) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->SetContextAttributesA) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->SetContextAttributesA(phContext, ulAttribute, pBuffer, cbBuffer); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "SetContextAttributesA status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_RevertSecurityContext(PCtxtHandle phContext) +{ + SEC_CHAR* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableW* table = NULL; + Name = (SEC_CHAR*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableWByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->RevertSecurityContext) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->RevertSecurityContext(phContext); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "RevertSecurityContext status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +/* Message Support */ + +static SECURITY_STATUS SEC_ENTRY winpr_DecryptMessage(PCtxtHandle phContext, + PSecBufferDesc pMessage, ULONG MessageSeqNo, + PULONG pfQOP) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->DecryptMessage) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->DecryptMessage(phContext, pMessage, MessageSeqNo, pfQOP); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "DecryptMessage status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_EncryptMessage(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->EncryptMessage) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->EncryptMessage(phContext, fQOP, pMessage, MessageSeqNo); + + if (status != SEC_E_OK) + { + WLog_ERR(TAG, "EncryptMessage status %s [0x%08" PRIX32 "]", GetSecurityStatusString(status), + status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_MakeSignature(PCtxtHandle phContext, ULONG fQOP, + PSecBufferDesc pMessage, ULONG MessageSeqNo) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->MakeSignature) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->MakeSignature(phContext, fQOP, pMessage, MessageSeqNo); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "MakeSignature status %s [0x%08" PRIX32 "]", GetSecurityStatusString(status), + status); + } + + return status; +} + +static SECURITY_STATUS SEC_ENTRY winpr_VerifySignature(PCtxtHandle phContext, + PSecBufferDesc pMessage, ULONG MessageSeqNo, + PULONG pfQOP) +{ + char* Name = NULL; + SECURITY_STATUS status = 0; + const SecurityFunctionTableA* table = NULL; + Name = (char*)sspi_SecureHandleGetUpperPointer(phContext); + + if (!Name) + return SEC_E_SECPKG_NOT_FOUND; + + table = sspi_GetSecurityFunctionTableAByNameA(Name); + + if (!table) + return SEC_E_SECPKG_NOT_FOUND; + + if (!table->VerifySignature) + { + WLog_WARN(TAG, "Security module does not provide an implementation"); + return SEC_E_UNSUPPORTED_FUNCTION; + } + + status = table->VerifySignature(phContext, pMessage, MessageSeqNo, pfQOP); + + if (IsSecurityStatusError(status)) + { + WLog_WARN(TAG, "VerifySignature status %s [0x%08" PRIX32 "]", + GetSecurityStatusString(status), status); + } + + return status; +} + +static SecurityFunctionTableA winpr_SecurityFunctionTableA = { + 3, /* dwVersion */ + winpr_EnumerateSecurityPackagesA, /* EnumerateSecurityPackages */ + winpr_QueryCredentialsAttributesA, /* QueryCredentialsAttributes */ + winpr_AcquireCredentialsHandleA, /* AcquireCredentialsHandle */ + winpr_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + winpr_InitializeSecurityContextA, /* InitializeSecurityContext */ + winpr_AcceptSecurityContext, /* AcceptSecurityContext */ + winpr_CompleteAuthToken, /* CompleteAuthToken */ + winpr_DeleteSecurityContext, /* DeleteSecurityContext */ + winpr_ApplyControlToken, /* ApplyControlToken */ + winpr_QueryContextAttributesA, /* QueryContextAttributes */ + winpr_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + winpr_RevertSecurityContext, /* RevertSecurityContext */ + winpr_MakeSignature, /* MakeSignature */ + winpr_VerifySignature, /* VerifySignature */ + winpr_FreeContextBuffer, /* FreeContextBuffer */ + winpr_QuerySecurityPackageInfoA, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + winpr_ExportSecurityContext, /* ExportSecurityContext */ + winpr_ImportSecurityContextA, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + winpr_QuerySecurityContextToken, /* QuerySecurityContextToken */ + winpr_EncryptMessage, /* EncryptMessage */ + winpr_DecryptMessage, /* DecryptMessage */ + winpr_SetContextAttributesA, /* SetContextAttributes */ + winpr_SetCredentialsAttributesA, /* SetCredentialsAttributes */ +}; + +static SecurityFunctionTableW winpr_SecurityFunctionTableW = { + 3, /* dwVersion */ + winpr_EnumerateSecurityPackagesW, /* EnumerateSecurityPackages */ + winpr_QueryCredentialsAttributesW, /* QueryCredentialsAttributes */ + winpr_AcquireCredentialsHandleW, /* AcquireCredentialsHandle */ + winpr_FreeCredentialsHandle, /* FreeCredentialsHandle */ + NULL, /* Reserved2 */ + winpr_InitializeSecurityContextW, /* InitializeSecurityContext */ + winpr_AcceptSecurityContext, /* AcceptSecurityContext */ + winpr_CompleteAuthToken, /* CompleteAuthToken */ + winpr_DeleteSecurityContext, /* DeleteSecurityContext */ + winpr_ApplyControlToken, /* ApplyControlToken */ + winpr_QueryContextAttributesW, /* QueryContextAttributes */ + winpr_ImpersonateSecurityContext, /* ImpersonateSecurityContext */ + winpr_RevertSecurityContext, /* RevertSecurityContext */ + winpr_MakeSignature, /* MakeSignature */ + winpr_VerifySignature, /* VerifySignature */ + winpr_FreeContextBuffer, /* FreeContextBuffer */ + winpr_QuerySecurityPackageInfoW, /* QuerySecurityPackageInfo */ + NULL, /* Reserved3 */ + NULL, /* Reserved4 */ + winpr_ExportSecurityContext, /* ExportSecurityContext */ + winpr_ImportSecurityContextW, /* ImportSecurityContext */ + NULL, /* AddCredentials */ + NULL, /* Reserved8 */ + winpr_QuerySecurityContextToken, /* QuerySecurityContextToken */ + winpr_EncryptMessage, /* EncryptMessage */ + winpr_DecryptMessage, /* DecryptMessage */ + winpr_SetContextAttributesW, /* SetContextAttributes */ + winpr_SetCredentialsAttributesW, /* SetCredentialsAttributes */ +}; diff --git a/winpr/libwinpr/sspi/sspi_winpr.h b/winpr/libwinpr/sspi/sspi_winpr.h new file mode 100644 index 0000000..2f7b55a --- /dev/null +++ b/winpr/libwinpr/sspi/sspi_winpr.h @@ -0,0 +1,28 @@ +/** + * WinPR: Windows Portable Runtime + * Security Support Provider Interface (SSPI) + * + * Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef WINPR_SSPI_WINPR_H +#define WINPR_SSPI_WINPR_H + +#include <winpr/sspi.h> + +SecurityFunctionTableW* SEC_ENTRY winpr_InitSecurityInterfaceW(void); +SecurityFunctionTableA* SEC_ENTRY winpr_InitSecurityInterfaceA(void); + +#endif /* WINPR_SSPI_WINPR_H */ diff --git a/winpr/libwinpr/sspi/test/CMakeLists.txt b/winpr/libwinpr/sspi/test/CMakeLists.txt new file mode 100644 index 0000000..5e0f15d --- /dev/null +++ b/winpr/libwinpr/sspi/test/CMakeLists.txt @@ -0,0 +1,38 @@ + +set(MODULE_NAME "TestSspi") +set(MODULE_PREFIX "TEST_SSPI") + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c) + +set(${MODULE_PREFIX}_TESTS + TestQuerySecurityPackageInfo.c + TestEnumerateSecurityPackages.c + TestInitializeSecurityContext.c + TestAcquireCredentialsHandle.c + TestCredSSP.c + #TestSchannel.c + TestNTLM.c) + +create_test_sourcelist(${MODULE_PREFIX}_SRCS + ${${MODULE_PREFIX}_DRIVER} + ${${MODULE_PREFIX}_TESTS}) + +include_directories(${OPENSSL_INCLUDE_DIR}) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +if(WIN32) + set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} secur32 crypt32) +endif() + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS} winpr) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test") + diff --git a/winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c b/winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c new file mode 100644 index 0000000..05466c8 --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestAcquireCredentialsHandle.c @@ -0,0 +1,60 @@ + +#include <stdio.h> +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/winpr.h> + +static const char* test_User = "User"; +static const char* test_Domain = "Domain"; +static const char* test_Password = "Password"; + +int TestAcquireCredentialsHandle(int argc, char* argv[]) +{ + int rc = -1; + SECURITY_STATUS status = 0; + CredHandle credentials = { 0 }; + TimeStamp expiration; + SEC_WINNT_AUTH_IDENTITY identity; + SecurityFunctionTable* table = NULL; + SecPkgCredentials_Names credential_names; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + sspi_GlobalInit(); + table = InitSecurityInterfaceEx(0); + identity.User = (UINT16*)_strdup(test_User); + identity.Domain = (UINT16*)_strdup(test_Domain); + identity.Password = (UINT16*)_strdup(test_Password); + + if (!identity.User || !identity.Domain || !identity.Password) + goto fail; + + identity.UserLength = strlen(test_User); + identity.DomainLength = strlen(test_Domain); + identity.PasswordLength = strlen(test_Password); + identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + status = table->AcquireCredentialsHandle(NULL, NTLM_SSP_NAME, SECPKG_CRED_OUTBOUND, NULL, + &identity, NULL, NULL, &credentials, &expiration); + + if (status != SEC_E_OK) + goto fail; + + status = + table->QueryCredentialsAttributes(&credentials, SECPKG_CRED_ATTR_NAMES, &credential_names); + + if (status != SEC_E_OK) + goto fail; + + rc = 0; +fail: + + if (SecIsValidHandle(&credentials)) + table->FreeCredentialsHandle(&credentials); + + free(identity.User); + free(identity.Domain); + free(identity.Password); + sspi_GlobalFinish(); + return rc; +} diff --git a/winpr/libwinpr/sspi/test/TestCredSSP.c b/winpr/libwinpr/sspi/test/TestCredSSP.c new file mode 100644 index 0000000..b56d9e2 --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestCredSSP.c @@ -0,0 +1,8 @@ + +#include <winpr/crt.h> +#include <winpr/sspi.h> + +int TestCredSSP(int argc, char* argv[]) +{ + return 0; +} diff --git a/winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c b/winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c new file mode 100644 index 0000000..9de23c0 --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestEnumerateSecurityPackages.c @@ -0,0 +1,40 @@ + +#include <stdio.h> +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/winpr.h> +#include <winpr/tchar.h> + +int TestEnumerateSecurityPackages(int argc, char* argv[]) +{ + ULONG cPackages = 0; + SECURITY_STATUS status = 0; + SecPkgInfo* pPackageInfo = NULL; + SecurityFunctionTable* table = NULL; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + sspi_GlobalInit(); + table = InitSecurityInterfaceEx(0); + + status = table->EnumerateSecurityPackages(&cPackages, &pPackageInfo); + + if (status != SEC_E_OK) + { + sspi_GlobalFinish(); + return -1; + } + + _tprintf(_T("\nEnumerateSecurityPackages (%") _T(PRIu32) _T("):\n"), cPackages); + + for (size_t index = 0; index < cPackages; index++) + { + _tprintf(_T("\"%s\", \"%s\"\n"), pPackageInfo[index].Name, pPackageInfo[index].Comment); + } + + table->FreeContextBuffer(pPackageInfo); + sspi_GlobalFinish(); + + return 0; +} diff --git a/winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c b/winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c new file mode 100644 index 0000000..88f5a7c --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestInitializeSecurityContext.c @@ -0,0 +1,115 @@ + +#include <stdio.h> +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/winpr.h> + +static const char* test_User = "User"; +static const char* test_Domain = "Domain"; +static const char* test_Password = "Password"; + +int TestInitializeSecurityContext(int argc, char* argv[]) +{ + int rc = -1; + UINT32 cbMaxLen = 0; + UINT32 fContextReq = 0; + void* output_buffer = NULL; + CtxtHandle context; + ULONG pfContextAttr = 0; + SECURITY_STATUS status = 0; + CredHandle credentials = { 0 }; + TimeStamp expiration; + PSecPkgInfo pPackageInfo = NULL; + SEC_WINNT_AUTH_IDENTITY identity = { 0 }; + SecurityFunctionTable* table = NULL; + PSecBuffer p_SecBuffer = NULL; + SecBuffer output_SecBuffer; + SecBufferDesc output_SecBuffer_desc; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + sspi_GlobalInit(); + table = InitSecurityInterfaceEx(0); + status = table->QuerySecurityPackageInfo(NTLM_SSP_NAME, &pPackageInfo); + + if (status != SEC_E_OK) + { + printf("QuerySecurityPackageInfo status: 0x%08" PRIX32 "\n", status); + goto fail; + } + + cbMaxLen = pPackageInfo->cbMaxToken; + identity.User = (UINT16*)_strdup(test_User); + identity.Domain = (UINT16*)_strdup(test_Domain); + identity.Password = (UINT16*)_strdup(test_Password); + + if (!identity.User || !identity.Domain || !identity.Password) + goto fail; + + identity.UserLength = strlen(test_User); + identity.DomainLength = strlen(test_Domain); + identity.PasswordLength = strlen(test_Password); + identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + status = table->AcquireCredentialsHandle(NULL, NTLM_SSP_NAME, SECPKG_CRED_OUTBOUND, NULL, + &identity, NULL, NULL, &credentials, &expiration); + + if (status != SEC_E_OK) + { + printf("AcquireCredentialsHandle status: 0x%08" PRIX32 "\n", status); + goto fail; + } + + fContextReq = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONFIDENTIALITY | + ISC_REQ_DELEGATE; + output_buffer = malloc(cbMaxLen); + + if (!output_buffer) + { + printf("Memory allocation failed\n"); + goto fail; + } + + output_SecBuffer_desc.ulVersion = 0; + output_SecBuffer_desc.cBuffers = 1; + output_SecBuffer_desc.pBuffers = &output_SecBuffer; + output_SecBuffer.cbBuffer = cbMaxLen; + output_SecBuffer.BufferType = SECBUFFER_TOKEN; + output_SecBuffer.pvBuffer = output_buffer; + status = table->InitializeSecurityContext(&credentials, NULL, NULL, fContextReq, 0, 0, NULL, 0, + &context, &output_SecBuffer_desc, &pfContextAttr, + &expiration); + + if (status != SEC_I_CONTINUE_NEEDED) + { + printf("InitializeSecurityContext status: 0x%08" PRIX32 "\n", status); + goto fail; + } + + printf("cBuffers: %" PRIu32 " ulVersion: %" PRIu32 "\n", output_SecBuffer_desc.cBuffers, + output_SecBuffer_desc.ulVersion); + p_SecBuffer = &output_SecBuffer_desc.pBuffers[0]; + printf("BufferType: 0x%08" PRIX32 " cbBuffer: %" PRIu32 "\n", p_SecBuffer->BufferType, + p_SecBuffer->cbBuffer); + status = table->DeleteSecurityContext(&context); + + if (status != SEC_E_OK) + { + printf("DeleteSecurityContext status: 0x%08" PRIX32 "\n", status); + goto fail; + } + + rc = 0; +fail: + free(identity.User); + free(identity.Domain); + free(identity.Password); + free(output_buffer); + + if (SecIsValidHandle(&credentials)) + table->FreeCredentialsHandle(&credentials); + + table->FreeContextBuffer(pPackageInfo); + sspi_GlobalFinish(); + return rc; +} diff --git a/winpr/libwinpr/sspi/test/TestNTLM.c b/winpr/libwinpr/sspi/test/TestNTLM.c new file mode 100644 index 0000000..2ab3373 --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestNTLM.c @@ -0,0 +1,694 @@ + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/sspi.h> +#include <winpr/print.h> +#include <winpr/wlog.h> + +static BYTE TEST_NTLM_TIMESTAMP[8] = { 0x33, 0x57, 0xbd, 0xb1, 0x07, 0x8b, 0xcf, 0x01 }; + +static BYTE TEST_NTLM_CLIENT_CHALLENGE[8] = { 0x20, 0xc0, 0x2b, 0x3d, 0xc0, 0x61, 0xa7, 0x73 }; + +static BYTE TEST_NTLM_SERVER_CHALLENGE[8] = { 0xa4, 0xf1, 0xba, 0xa6, 0x7c, 0xdc, 0x1a, 0x12 }; + +static BYTE TEST_NTLM_NEGOTIATE[] = + "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x06\x03\x80\x25\x00\x00\x00\x0f"; + +static BYTE TEST_NTLM_CHALLENGE[] = + "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x02\x00\x00\x00\x00\x00\x00\x00" + "\x38\x00\x00\x00\x07\x82\x88\xa2\xa4\xf1\xba\xa6\x7c\xdc\x1a\x12" + "\x00\x00\x00\x00\x00\x00\x00\x00\x66\x00\x66\x00\x38\x00\x00\x00" + "\x06\x03\x80\x25\x00\x00\x00\x0f\x02\x00\x0e\x00\x4e\x00\x45\x00" + "\x57\x00\x59\x00\x45\x00\x41\x00\x52\x00\x01\x00\x0e\x00\x4e\x00" + "\x45\x00\x57\x00\x59\x00\x45\x00\x41\x00\x52\x00\x04\x00\x1c\x00" + "\x6c\x00\x61\x00\x62\x00\x2e\x00\x77\x00\x61\x00\x79\x00\x6b\x00" + "\x2e\x00\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x03\x00\x0e\x00" + "\x6e\x00\x65\x00\x77\x00\x79\x00\x65\x00\x61\x00\x72\x00\x07\x00" + "\x08\x00\x33\x57\xbd\xb1\x07\x8b\xcf\x01\x00\x00\x00\x00"; + +static BYTE TEST_NTLM_AUTHENTICATE[] = + "\x4e\x54\x4c\x4d\x53\x53\x50\x00\x03\x00\x00\x00\x18\x00\x18\x00" + "\x82\x00\x00\x00\x08\x01\x08\x01\x9a\x00\x00\x00\x0c\x00\x0c\x00" + "\x58\x00\x00\x00\x10\x00\x10\x00\x64\x00\x00\x00\x0e\x00\x0e\x00" + "\x74\x00\x00\x00\x00\x00\x00\x00\xa2\x01\x00\x00\x05\x82\x88\xa2" + "\x06\x03\x80\x25\x00\x00\x00\x0f\x12\xe5\x5a\xf5\x80\xee\x3f\x29" + "\xe1\xde\x90\x4d\x73\x77\x06\x25\x44\x00\x6f\x00\x6d\x00\x61\x00" + "\x69\x00\x6e\x00\x55\x00\x73\x00\x65\x00\x72\x00\x6e\x00\x61\x00" + "\x6d\x00\x65\x00\x4e\x00\x45\x00\x57\x00\x59\x00\x45\x00\x41\x00" + "\x52\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x62\x14\x68\xc8\x98\x12" + "\xe7\x39\xd8\x76\x1b\xe9\xf7\x54\xb5\xe3\x01\x01\x00\x00\x00\x00" + "\x00\x00\x33\x57\xbd\xb1\x07\x8b\xcf\x01\x20\xc0\x2b\x3d\xc0\x61" + "\xa7\x73\x00\x00\x00\x00\x02\x00\x0e\x00\x4e\x00\x45\x00\x57\x00" + "\x59\x00\x45\x00\x41\x00\x52\x00\x01\x00\x0e\x00\x4e\x00\x45\x00" + "\x57\x00\x59\x00\x45\x00\x41\x00\x52\x00\x04\x00\x1c\x00\x6c\x00" + "\x61\x00\x62\x00\x2e\x00\x77\x00\x61\x00\x79\x00\x6b\x00\x2e\x00" + "\x6c\x00\x6f\x00\x63\x00\x61\x00\x6c\x00\x03\x00\x0e\x00\x6e\x00" + "\x65\x00\x77\x00\x79\x00\x65\x00\x61\x00\x72\x00\x07\x00\x08\x00" + "\x33\x57\xbd\xb1\x07\x8b\xcf\x01\x06\x00\x04\x00\x02\x00\x00\x00" + "\x08\x00\x30\x00\x30\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00" + "\x00\x20\x00\x00\x1e\x10\xf5\x2c\x54\x2f\x2e\x77\x1c\x13\xbf\xc3" + "\x3f\xe1\x7b\x28\x7e\x0b\x93\x5a\x39\xd2\xce\x12\xd7\xbd\x8c\x4e" + "\x2b\xb5\x0b\xf5\x0a\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x1a\x00\x48\x00\x54\x00" + "\x54\x00\x50\x00\x2f\x00\x72\x00\x77\x00\x2e\x00\x6c\x00\x6f\x00" + "\x63\x00\x61\x00\x6c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00"; + +#define TEST_SSPI_INTERFACE SSPI_INTERFACE_WINPR + +static const char* TEST_NTLM_USER = "Username"; +static const char* TEST_NTLM_DOMAIN = "Domain"; +static const char* TEST_NTLM_PASSWORD = "P4ss123!"; + +// static const char* TEST_NTLM_HASH_STRING = "d5922a65c4d5c082ca444af1be0001db"; + +static const BYTE TEST_NTLM_HASH[16] = { 0xd5, 0x92, 0x2a, 0x65, 0xc4, 0xd5, 0xc0, 0x82, + 0xca, 0x44, 0x4a, 0xf1, 0xbe, 0x00, 0x01, 0xdb }; + +// static const char* TEST_NTLM_HASH_V2_STRING = "4c7f706f7dde05a9d1a0f4e7ffe3bfb8"; + +static const BYTE TEST_NTLM_V2_HASH[16] = { 0x4c, 0x7f, 0x70, 0x6f, 0x7d, 0xde, 0x05, 0xa9, + 0xd1, 0xa0, 0xf4, 0xe7, 0xff, 0xe3, 0xbf, 0xb8 }; + +#define NTLM_PACKAGE_NAME NTLM_SSP_NAME + +typedef struct +{ + CtxtHandle context; + ULONG cbMaxToken; + ULONG fContextReq; + ULONG pfContextAttr; + TimeStamp expiration; + PSecBuffer pBuffer; + SecBuffer inputBuffer[2]; + SecBuffer outputBuffer[2]; + BOOL haveContext; + BOOL haveInputBuffer; + LPTSTR ServicePrincipalName; + SecBufferDesc inputBufferDesc; + SecBufferDesc outputBufferDesc; + CredHandle credentials; + BOOL confidentiality; + SecPkgInfo* pPackageInfo; + SecurityFunctionTable* table; + SEC_WINNT_AUTH_IDENTITY identity; +} TEST_NTLM_CLIENT; + +static int test_ntlm_client_init(TEST_NTLM_CLIENT* ntlm, const char* user, const char* domain, + const char* password) +{ + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + + WINPR_ASSERT(ntlm); + + SecInvalidateHandle(&(ntlm->context)); + ntlm->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE); + sspi_SetAuthIdentity(&(ntlm->identity), user, domain, password); + status = ntlm->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &ntlm->pPackageInfo); + + if (status != SEC_E_OK) + { + fprintf(stderr, "QuerySecurityPackageInfo status: %s (0x%08" PRIX32 ")\n", + GetSecurityStatusString(status), status); + return -1; + } + + ntlm->cbMaxToken = ntlm->pPackageInfo->cbMaxToken; + status = ntlm->table->AcquireCredentialsHandle(NULL, NTLM_PACKAGE_NAME, SECPKG_CRED_OUTBOUND, + NULL, &ntlm->identity, NULL, NULL, + &ntlm->credentials, &ntlm->expiration); + + if (status != SEC_E_OK) + { + fprintf(stderr, "AcquireCredentialsHandle status: %s (0x%08" PRIX32 ")\n", + GetSecurityStatusString(status), status); + return -1; + } + + ntlm->haveContext = FALSE; + ntlm->haveInputBuffer = FALSE; + ZeroMemory(&ntlm->inputBuffer, sizeof(SecBuffer)); + ZeroMemory(&ntlm->outputBuffer, sizeof(SecBuffer)); + ntlm->fContextReq = 0; +#if 0 + /* HTTP authentication flags */ + ntlm->fContextReq |= ISC_REQ_CONFIDENTIALITY; +#endif + /* NLA authentication flags */ + ntlm->fContextReq |= ISC_REQ_MUTUAL_AUTH; + ntlm->fContextReq |= ISC_REQ_CONFIDENTIALITY; + ntlm->fContextReq |= ISC_REQ_USE_SESSION_KEY; + return 1; +} + +static void test_ntlm_client_uninit(TEST_NTLM_CLIENT* ntlm) +{ + if (!ntlm) + return; + + if (ntlm->outputBuffer[0].pvBuffer) + { + free(ntlm->outputBuffer[0].pvBuffer); + ntlm->outputBuffer[0].pvBuffer = NULL; + } + + free(ntlm->identity.User); + free(ntlm->identity.Domain); + free(ntlm->identity.Password); + free(ntlm->ServicePrincipalName); + + if (ntlm->table) + { + ntlm->table->FreeCredentialsHandle(&ntlm->credentials); + ntlm->table->FreeContextBuffer(ntlm->pPackageInfo); + ntlm->table->DeleteSecurityContext(&ntlm->context); + } +} + +/** + * SSPI Client Ceremony + * + * -------------- + * ( Client Begin ) + * -------------- + * | + * | + * \|/ + * -----------+-------------- + * | AcquireCredentialsHandle | + * -------------------------- + * | + * | + * \|/ + * -------------+-------------- + * +---------------> / InitializeSecurityContext / + * | ---------------------------- + * | | + * | | + * | \|/ + * --------------------------- ---------+------------- ---------------------- + * / Receive blob from server / < Received security blob? > --Yes-> / Send blob to server / + * -------------+------------- ----------------------- ---------------------- + * /|\ | | + * | No | + * Yes \|/ | + * | ------------+----------- | + * +---------------- < Received Continue Needed > <-----------------+ + * ------------------------ + * | + * No + * \|/ + * ------+------- + * ( Client End ) + * -------------- + */ + +static int test_ntlm_client_authenticate(TEST_NTLM_CLIENT* ntlm) +{ + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + + WINPR_ASSERT(ntlm); + if (ntlm->outputBuffer[0].pvBuffer) + { + free(ntlm->outputBuffer[0].pvBuffer); + ntlm->outputBuffer[0].pvBuffer = NULL; + } + + ntlm->outputBufferDesc.ulVersion = SECBUFFER_VERSION; + ntlm->outputBufferDesc.cBuffers = 1; + ntlm->outputBufferDesc.pBuffers = ntlm->outputBuffer; + ntlm->outputBuffer[0].BufferType = SECBUFFER_TOKEN; + ntlm->outputBuffer[0].cbBuffer = ntlm->cbMaxToken; + ntlm->outputBuffer[0].pvBuffer = malloc(ntlm->outputBuffer[0].cbBuffer); + + if (!ntlm->outputBuffer[0].pvBuffer) + return -1; + + if (ntlm->haveInputBuffer) + { + ntlm->inputBufferDesc.ulVersion = SECBUFFER_VERSION; + ntlm->inputBufferDesc.cBuffers = 1; + ntlm->inputBufferDesc.pBuffers = ntlm->inputBuffer; + ntlm->inputBuffer[0].BufferType = SECBUFFER_TOKEN; + } + + if ((!ntlm) || (!ntlm->table)) + { + fprintf(stderr, "ntlm_authenticate: invalid ntlm context\n"); + return -1; + } + + status = ntlm->table->InitializeSecurityContext( + &ntlm->credentials, (ntlm->haveContext) ? &ntlm->context : NULL, + (ntlm->ServicePrincipalName) ? ntlm->ServicePrincipalName : NULL, ntlm->fContextReq, 0, + SECURITY_NATIVE_DREP, (ntlm->haveInputBuffer) ? &ntlm->inputBufferDesc : NULL, 0, + &ntlm->context, &ntlm->outputBufferDesc, &ntlm->pfContextAttr, &ntlm->expiration); + + if ((status == SEC_I_COMPLETE_AND_CONTINUE) || (status == SEC_I_COMPLETE_NEEDED)) + { + if (ntlm->table->CompleteAuthToken) + ntlm->table->CompleteAuthToken(&ntlm->context, &ntlm->outputBufferDesc); + + if (status == SEC_I_COMPLETE_NEEDED) + status = SEC_E_OK; + else if (status == SEC_I_COMPLETE_AND_CONTINUE) + status = SEC_I_CONTINUE_NEEDED; + } + + if (ntlm->haveInputBuffer) + { + free(ntlm->inputBuffer[0].pvBuffer); + } + + ntlm->haveInputBuffer = TRUE; + ntlm->haveContext = TRUE; + return (status == SEC_I_CONTINUE_NEEDED) ? 1 : 0; +} + +static TEST_NTLM_CLIENT* test_ntlm_client_new(void) +{ + TEST_NTLM_CLIENT* ntlm = (TEST_NTLM_CLIENT*)calloc(1, sizeof(TEST_NTLM_CLIENT)); + + if (!ntlm) + return NULL; + + return ntlm; +} + +static void test_ntlm_client_free(TEST_NTLM_CLIENT* ntlm) +{ + if (!ntlm) + return; + + test_ntlm_client_uninit(ntlm); + free(ntlm); +} + +typedef struct +{ + CtxtHandle context; + ULONG cbMaxToken; + ULONG fContextReq; + ULONG pfContextAttr; + TimeStamp expiration; + PSecBuffer pBuffer; + SecBuffer inputBuffer[2]; + SecBuffer outputBuffer[2]; + BOOL haveContext; + BOOL haveInputBuffer; + BOOL UseNtlmV2Hash; + LPTSTR ServicePrincipalName; + SecBufferDesc inputBufferDesc; + SecBufferDesc outputBufferDesc; + CredHandle credentials; + BOOL confidentiality; + SecPkgInfo* pPackageInfo; + SecurityFunctionTable* table; + SEC_WINNT_AUTH_IDENTITY identity; +} TEST_NTLM_SERVER; + +static int test_ntlm_server_init(TEST_NTLM_SERVER* ntlm) +{ + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + + WINPR_ASSERT(ntlm); + + ntlm->UseNtlmV2Hash = TRUE; + SecInvalidateHandle(&(ntlm->context)); + ntlm->table = InitSecurityInterfaceEx(TEST_SSPI_INTERFACE); + status = ntlm->table->QuerySecurityPackageInfo(NTLM_PACKAGE_NAME, &ntlm->pPackageInfo); + + if (status != SEC_E_OK) + { + fprintf(stderr, "QuerySecurityPackageInfo status: %s (0x%08" PRIX32 ")\n", + GetSecurityStatusString(status), status); + return -1; + } + + ntlm->cbMaxToken = ntlm->pPackageInfo->cbMaxToken; + status = ntlm->table->AcquireCredentialsHandle(NULL, NTLM_PACKAGE_NAME, SECPKG_CRED_INBOUND, + NULL, NULL, NULL, NULL, &ntlm->credentials, + &ntlm->expiration); + + if (status != SEC_E_OK) + { + fprintf(stderr, "AcquireCredentialsHandle status: %s (0x%08" PRIX32 ")\n", + GetSecurityStatusString(status), status); + return -1; + } + + ntlm->haveContext = FALSE; + ntlm->haveInputBuffer = FALSE; + ZeroMemory(&ntlm->inputBuffer, sizeof(SecBuffer)); + ZeroMemory(&ntlm->outputBuffer, sizeof(SecBuffer)); + ntlm->fContextReq = 0; + /* NLA authentication flags */ + ntlm->fContextReq |= ASC_REQ_MUTUAL_AUTH; + ntlm->fContextReq |= ASC_REQ_CONFIDENTIALITY; + ntlm->fContextReq |= ASC_REQ_CONNECTION; + ntlm->fContextReq |= ASC_REQ_USE_SESSION_KEY; + ntlm->fContextReq |= ASC_REQ_REPLAY_DETECT; + ntlm->fContextReq |= ASC_REQ_SEQUENCE_DETECT; + ntlm->fContextReq |= ASC_REQ_EXTENDED_ERROR; + return 1; +} + +static void test_ntlm_server_uninit(TEST_NTLM_SERVER* ntlm) +{ + if (!ntlm) + return; + + if (ntlm->outputBuffer[0].pvBuffer) + { + free(ntlm->outputBuffer[0].pvBuffer); + ntlm->outputBuffer[0].pvBuffer = NULL; + } + + free(ntlm->identity.User); + free(ntlm->identity.Domain); + free(ntlm->identity.Password); + free(ntlm->ServicePrincipalName); + + if (ntlm->table) + { + ntlm->table->FreeCredentialsHandle(&ntlm->credentials); + ntlm->table->FreeContextBuffer(ntlm->pPackageInfo); + ntlm->table->DeleteSecurityContext(&ntlm->context); + } +} + +static int test_ntlm_server_authenticate(TEST_NTLM_SERVER* ntlm) +{ + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + + WINPR_ASSERT(ntlm); + + ntlm->inputBufferDesc.ulVersion = SECBUFFER_VERSION; + ntlm->inputBufferDesc.cBuffers = 1; + ntlm->inputBufferDesc.pBuffers = ntlm->inputBuffer; + ntlm->inputBuffer[0].BufferType = SECBUFFER_TOKEN; + ntlm->outputBufferDesc.ulVersion = SECBUFFER_VERSION; + ntlm->outputBufferDesc.cBuffers = 1; + ntlm->outputBufferDesc.pBuffers = &ntlm->outputBuffer[0]; + ntlm->outputBuffer[0].BufferType = SECBUFFER_TOKEN; + ntlm->outputBuffer[0].cbBuffer = ntlm->cbMaxToken; + ntlm->outputBuffer[0].pvBuffer = malloc(ntlm->outputBuffer[0].cbBuffer); + BOOL hash_set = FALSE; + + if (!ntlm->outputBuffer[0].pvBuffer) + return -1; + + status = ntlm->table->AcceptSecurityContext( + &ntlm->credentials, ntlm->haveContext ? &ntlm->context : NULL, &ntlm->inputBufferDesc, + ntlm->fContextReq, SECURITY_NATIVE_DREP, &ntlm->context, &ntlm->outputBufferDesc, + &ntlm->pfContextAttr, &ntlm->expiration); + + if (!hash_set && status == SEC_I_CONTINUE_NEEDED) + { + SecPkgContext_AuthNtlmHash AuthNtlmHash = { 0 }; + + if (ntlm->UseNtlmV2Hash) + { + AuthNtlmHash.Version = 2; + CopyMemory(AuthNtlmHash.NtlmHash, TEST_NTLM_V2_HASH, 16); + } + else + { + AuthNtlmHash.Version = 1; + CopyMemory(AuthNtlmHash.NtlmHash, TEST_NTLM_HASH, 16); + } + + status = + ntlm->table->SetContextAttributes(&ntlm->context, SECPKG_ATTR_AUTH_NTLM_HASH, + &AuthNtlmHash, sizeof(SecPkgContext_AuthNtlmHash)); + + hash_set = TRUE; + } + + if ((status != SEC_E_OK) && (status != SEC_I_CONTINUE_NEEDED)) + { + fprintf(stderr, "AcceptSecurityContext status: %s (0x%08" PRIX32 ")\n", + GetSecurityStatusString(status), status); + return -1; /* Access Denied */ + } + + ntlm->haveContext = TRUE; + return (status == SEC_I_CONTINUE_NEEDED) ? 1 : 0; +} + +static TEST_NTLM_SERVER* test_ntlm_server_new(void) +{ + TEST_NTLM_SERVER* ntlm = (TEST_NTLM_SERVER*)calloc(1, sizeof(TEST_NTLM_SERVER)); + + if (!ntlm) + return NULL; + + return ntlm; +} + +static void test_ntlm_server_free(TEST_NTLM_SERVER* ntlm) +{ + if (!ntlm) + return; + + test_ntlm_server_uninit(ntlm); + free(ntlm); +} + +static BOOL test_default(void) +{ + int status = 0; + BOOL rc = FALSE; + PSecBuffer pSecBuffer = NULL; + TEST_NTLM_CLIENT* client = NULL; + TEST_NTLM_SERVER* server = NULL; + BOOL DynamicTest = TRUE; + + /** + * Client Initialization + */ + client = test_ntlm_client_new(); + + if (!client) + { + printf("Memory allocation failed"); + goto fail; + } + + status = test_ntlm_client_init(client, TEST_NTLM_USER, TEST_NTLM_DOMAIN, TEST_NTLM_PASSWORD); + + if (status < 0) + { + printf("test_ntlm_client_init failure\n"); + goto fail; + } + + /** + * Server Initialization + */ + server = test_ntlm_server_new(); + + if (!server) + { + printf("Memory allocation failed\n"); + goto fail; + } + + status = test_ntlm_server_init(server); + + if (status < 0) + { + printf("test_ntlm_server_init failure\n"); + goto fail; + } + + /** + * Client -> Negotiate Message + */ + status = test_ntlm_client_authenticate(client); + + if (status < 0) + { + printf("test_ntlm_client_authenticate failure\n"); + goto fail; + } + + if (!DynamicTest) + { + SecPkgContext_AuthNtlmTimestamp AuthNtlmTimestamp; + SecPkgContext_AuthNtlmClientChallenge AuthNtlmClientChallenge; + SecPkgContext_AuthNtlmServerChallenge AuthNtlmServerChallenge; + CopyMemory(AuthNtlmTimestamp.Timestamp, TEST_NTLM_TIMESTAMP, 8); + AuthNtlmTimestamp.ChallengeOrResponse = TRUE; + client->table->SetContextAttributes(&client->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP, + &AuthNtlmTimestamp, + sizeof(SecPkgContext_AuthNtlmTimestamp)); + AuthNtlmTimestamp.ChallengeOrResponse = FALSE; + client->table->SetContextAttributes(&client->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP, + &AuthNtlmTimestamp, + sizeof(SecPkgContext_AuthNtlmTimestamp)); + CopyMemory(AuthNtlmClientChallenge.ClientChallenge, TEST_NTLM_CLIENT_CHALLENGE, 8); + CopyMemory(AuthNtlmServerChallenge.ServerChallenge, TEST_NTLM_SERVER_CHALLENGE, 8); + client->table->SetContextAttributes( + &client->context, SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE, &AuthNtlmClientChallenge, + sizeof(SecPkgContext_AuthNtlmClientChallenge)); + client->table->SetContextAttributes( + &client->context, SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE, &AuthNtlmServerChallenge, + sizeof(SecPkgContext_AuthNtlmServerChallenge)); + } + + pSecBuffer = &(client->outputBuffer[0]); + + if (!DynamicTest) + { + pSecBuffer->cbBuffer = sizeof(TEST_NTLM_NEGOTIATE) - 1; + pSecBuffer->pvBuffer = (void*)malloc(pSecBuffer->cbBuffer); + + if (!pSecBuffer->pvBuffer) + { + printf("Memory allocation failed\n"); + goto fail; + } + + CopyMemory(pSecBuffer->pvBuffer, TEST_NTLM_NEGOTIATE, pSecBuffer->cbBuffer); + } + + fprintf(stderr, "NTLM_NEGOTIATE (length = %" PRIu32 "):\n", pSecBuffer->cbBuffer); + winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, pSecBuffer->cbBuffer); + /** + * Server <- Negotiate Message + * Server -> Challenge Message + */ + server->haveInputBuffer = TRUE; + server->inputBuffer[0].BufferType = SECBUFFER_TOKEN; + server->inputBuffer[0].pvBuffer = pSecBuffer->pvBuffer; + server->inputBuffer[0].cbBuffer = pSecBuffer->cbBuffer; + status = test_ntlm_server_authenticate(server); + + if (status < 0) + { + printf("test_ntlm_server_authenticate failure\n"); + goto fail; + } + + if (!DynamicTest) + { + SecPkgContext_AuthNtlmTimestamp AuthNtlmTimestamp; + SecPkgContext_AuthNtlmClientChallenge AuthNtlmClientChallenge; + SecPkgContext_AuthNtlmServerChallenge AuthNtlmServerChallenge; + CopyMemory(AuthNtlmTimestamp.Timestamp, TEST_NTLM_TIMESTAMP, 8); + AuthNtlmTimestamp.ChallengeOrResponse = TRUE; + client->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP, + &AuthNtlmTimestamp, + sizeof(SecPkgContext_AuthNtlmTimestamp)); + AuthNtlmTimestamp.ChallengeOrResponse = FALSE; + client->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_TIMESTAMP, + &AuthNtlmTimestamp, + sizeof(SecPkgContext_AuthNtlmTimestamp)); + CopyMemory(AuthNtlmClientChallenge.ClientChallenge, TEST_NTLM_CLIENT_CHALLENGE, 8); + CopyMemory(AuthNtlmServerChallenge.ServerChallenge, TEST_NTLM_SERVER_CHALLENGE, 8); + server->table->SetContextAttributes( + &server->context, SECPKG_ATTR_AUTH_NTLM_CLIENT_CHALLENGE, &AuthNtlmClientChallenge, + sizeof(SecPkgContext_AuthNtlmClientChallenge)); + server->table->SetContextAttributes( + &server->context, SECPKG_ATTR_AUTH_NTLM_SERVER_CHALLENGE, &AuthNtlmServerChallenge, + sizeof(SecPkgContext_AuthNtlmServerChallenge)); + } + + pSecBuffer = &(server->outputBuffer[0]); + + if (!DynamicTest) + { + SecPkgContext_AuthNtlmMessage AuthNtlmMessage = { 0 }; + pSecBuffer->cbBuffer = sizeof(TEST_NTLM_CHALLENGE) - 1; + pSecBuffer->pvBuffer = (void*)malloc(pSecBuffer->cbBuffer); + + if (!pSecBuffer->pvBuffer) + { + printf("Memory allocation failed\n"); + goto fail; + } + + CopyMemory(pSecBuffer->pvBuffer, TEST_NTLM_CHALLENGE, pSecBuffer->cbBuffer); + AuthNtlmMessage.type = 2; + AuthNtlmMessage.length = pSecBuffer->cbBuffer; + AuthNtlmMessage.buffer = (BYTE*)pSecBuffer->pvBuffer; + server->table->SetContextAttributes(&server->context, SECPKG_ATTR_AUTH_NTLM_MESSAGE, + &AuthNtlmMessage, + sizeof(SecPkgContext_AuthNtlmMessage)); + } + + fprintf(stderr, "NTLM_CHALLENGE (length = %" PRIu32 "):\n", pSecBuffer->cbBuffer); + winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, pSecBuffer->cbBuffer); + /** + * Client <- Challenge Message + * Client -> Authenticate Message + */ + client->haveInputBuffer = TRUE; + client->inputBuffer[0].BufferType = SECBUFFER_TOKEN; + client->inputBuffer[0].pvBuffer = pSecBuffer->pvBuffer; + client->inputBuffer[0].cbBuffer = pSecBuffer->cbBuffer; + status = test_ntlm_client_authenticate(client); + + if (status < 0) + { + printf("test_ntlm_client_authenticate failure\n"); + goto fail; + } + + pSecBuffer = &(client->outputBuffer[0]); + + if (!DynamicTest) + { + pSecBuffer->cbBuffer = sizeof(TEST_NTLM_AUTHENTICATE) - 1; + pSecBuffer->pvBuffer = (void*)malloc(pSecBuffer->cbBuffer); + + if (!pSecBuffer->pvBuffer) + { + printf("Memory allocation failed\n"); + goto fail; + } + + CopyMemory(pSecBuffer->pvBuffer, TEST_NTLM_AUTHENTICATE, pSecBuffer->cbBuffer); + } + + fprintf(stderr, "NTLM_AUTHENTICATE (length = %" PRIu32 "):\n", pSecBuffer->cbBuffer); + winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, pSecBuffer->cbBuffer); + /** + * Server <- Authenticate Message + */ + server->haveInputBuffer = TRUE; + server->inputBuffer[0].BufferType = SECBUFFER_TOKEN; + server->inputBuffer[0].pvBuffer = pSecBuffer->pvBuffer; + server->inputBuffer[0].cbBuffer = pSecBuffer->cbBuffer; + status = test_ntlm_server_authenticate(server); + + if (status < 0) + { + printf("test_ntlm_server_authenticate failure\n"); + goto fail; + } + + rc = TRUE; + +fail: + /** + * Cleanup & Termination + */ + test_ntlm_client_free(client); + test_ntlm_server_free(server); + return rc; +} + +int TestNTLM(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + if (!test_default()) + return -1; + return 0; +} diff --git a/winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c b/winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c new file mode 100644 index 0000000..5d1ca00 --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestQuerySecurityPackageInfo.c @@ -0,0 +1,34 @@ + +#include <stdio.h> +#include <winpr/sspi.h> +#include <winpr/winpr.h> +#include <winpr/tchar.h> + +int TestQuerySecurityPackageInfo(int argc, char* argv[]) +{ + int rc = 0; + SECURITY_STATUS status = 0; + SecPkgInfo* pPackageInfo = NULL; + SecurityFunctionTable* table = NULL; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + sspi_GlobalInit(); + table = InitSecurityInterfaceEx(0); + + status = table->QuerySecurityPackageInfo(NTLM_SSP_NAME, &pPackageInfo); + + if (status != SEC_E_OK) + rc = -1; + else + { + _tprintf(_T("\nQuerySecurityPackageInfo:\n")); + _tprintf(_T("\"%s\", \"%s\"\n"), pPackageInfo->Name, pPackageInfo->Comment); + rc = 0; + } + + table->FreeContextBuffer(pPackageInfo); + sspi_GlobalFinish(); + return rc; +} diff --git a/winpr/libwinpr/sspi/test/TestSchannel.c b/winpr/libwinpr/sspi/test/TestSchannel.c new file mode 100644 index 0000000..3f9751a --- /dev/null +++ b/winpr/libwinpr/sspi/test/TestSchannel.c @@ -0,0 +1,854 @@ + +#include <winpr/crt.h> +#include <winpr/sspi.h> +#include <winpr/file.h> +#include <winpr/pipe.h> +#include <winpr/path.h> +#include <winpr/tchar.h> +#include <winpr/print.h> +#include <winpr/synch.h> +#include <winpr/thread.h> +#include <winpr/crypto.h> +#include <winpr/wlog.h> +#include <winpr/schannel.h> + +static BOOL g_ClientWait = FALSE; +static BOOL g_ServerWait = FALSE; + +static HANDLE g_ClientReadPipe = NULL; +static HANDLE g_ClientWritePipe = NULL; +static HANDLE g_ServerReadPipe = NULL; +static HANDLE g_ServerWritePipe = NULL; + +static const BYTE test_localhost_crt[1029] = { + 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x42, 0x45, 0x47, 0x49, 0x4E, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, + 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A, 0x4D, 0x49, 0x49, 0x43, + 0x79, 0x6A, 0x43, 0x43, 0x41, 0x62, 0x4B, 0x67, 0x41, 0x77, 0x49, 0x42, 0x41, 0x67, 0x49, 0x45, + 0x63, 0x61, 0x64, 0x63, 0x72, 0x7A, 0x41, 0x4E, 0x42, 0x67, 0x6B, 0x71, 0x68, 0x6B, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x44, 0x41, 0x55, 0x4D, 0x52, 0x49, 0x77, + 0x45, 0x41, 0x59, 0x44, 0x56, 0x51, 0x51, 0x44, 0x45, 0x77, 0x6C, 0x73, 0x0A, 0x62, 0x32, 0x4E, + 0x68, 0x62, 0x47, 0x68, 0x76, 0x63, 0x33, 0x51, 0x77, 0x48, 0x68, 0x63, 0x4E, 0x4D, 0x54, 0x4D, + 0x78, 0x4D, 0x44, 0x45, 0x78, 0x4D, 0x44, 0x59, 0x78, 0x4E, 0x7A, 0x55, 0x31, 0x57, 0x68, 0x63, + 0x4E, 0x4D, 0x54, 0x51, 0x78, 0x4D, 0x44, 0x45, 0x78, 0x4D, 0x44, 0x59, 0x78, 0x4E, 0x7A, 0x55, + 0x31, 0x57, 0x6A, 0x41, 0x55, 0x4D, 0x52, 0x49, 0x77, 0x45, 0x41, 0x59, 0x44, 0x0A, 0x56, 0x51, + 0x51, 0x44, 0x45, 0x77, 0x6C, 0x73, 0x62, 0x32, 0x4E, 0x68, 0x62, 0x47, 0x68, 0x76, 0x63, 0x33, + 0x51, 0x77, 0x67, 0x67, 0x45, 0x69, 0x4D, 0x41, 0x30, 0x47, 0x43, 0x53, 0x71, 0x47, 0x53, 0x49, + 0x62, 0x33, 0x44, 0x51, 0x45, 0x42, 0x41, 0x51, 0x55, 0x41, 0x41, 0x34, 0x49, 0x42, 0x44, 0x77, + 0x41, 0x77, 0x67, 0x67, 0x45, 0x4B, 0x41, 0x6F, 0x49, 0x42, 0x41, 0x51, 0x43, 0x33, 0x0A, 0x65, + 0x6E, 0x33, 0x68, 0x5A, 0x4F, 0x53, 0x33, 0x6B, 0x51, 0x2F, 0x55, 0x54, 0x30, 0x53, 0x45, 0x6C, + 0x30, 0x48, 0x6E, 0x50, 0x79, 0x64, 0x48, 0x75, 0x35, 0x39, 0x61, 0x69, 0x71, 0x64, 0x73, 0x64, + 0x53, 0x55, 0x74, 0x6E, 0x43, 0x41, 0x37, 0x46, 0x66, 0x74, 0x30, 0x4F, 0x36, 0x51, 0x79, 0x68, + 0x49, 0x71, 0x58, 0x7A, 0x30, 0x47, 0x32, 0x53, 0x76, 0x77, 0x4C, 0x54, 0x62, 0x79, 0x68, 0x0A, + 0x59, 0x54, 0x68, 0x31, 0x36, 0x78, 0x31, 0x72, 0x45, 0x48, 0x68, 0x31, 0x57, 0x47, 0x5A, 0x6D, + 0x36, 0x77, 0x64, 0x2B, 0x4B, 0x76, 0x38, 0x6B, 0x31, 0x6B, 0x2F, 0x36, 0x6F, 0x41, 0x2F, 0x4F, + 0x51, 0x76, 0x65, 0x61, 0x38, 0x6B, 0x63, 0x45, 0x64, 0x53, 0x72, 0x54, 0x64, 0x75, 0x71, 0x4A, + 0x33, 0x65, 0x66, 0x74, 0x48, 0x4A, 0x4A, 0x6E, 0x43, 0x4B, 0x30, 0x41, 0x62, 0x68, 0x34, 0x39, + 0x0A, 0x41, 0x47, 0x41, 0x50, 0x39, 0x79, 0x58, 0x77, 0x77, 0x59, 0x41, 0x6A, 0x51, 0x49, 0x52, + 0x6E, 0x38, 0x2B, 0x4F, 0x63, 0x63, 0x48, 0x74, 0x6F, 0x4E, 0x75, 0x75, 0x79, 0x52, 0x63, 0x6B, + 0x49, 0x50, 0x71, 0x75, 0x70, 0x78, 0x79, 0x31, 0x4A, 0x5A, 0x4B, 0x39, 0x64, 0x76, 0x76, 0x62, + 0x34, 0x79, 0x53, 0x6B, 0x49, 0x75, 0x7A, 0x62, 0x79, 0x50, 0x6F, 0x54, 0x41, 0x79, 0x61, 0x55, + 0x2B, 0x0A, 0x51, 0x72, 0x70, 0x34, 0x78, 0x67, 0x64, 0x4B, 0x46, 0x54, 0x70, 0x6B, 0x50, 0x46, + 0x34, 0x33, 0x6A, 0x32, 0x4D, 0x6D, 0x5A, 0x72, 0x46, 0x63, 0x42, 0x76, 0x79, 0x6A, 0x69, 0x35, + 0x6A, 0x4F, 0x37, 0x74, 0x66, 0x6F, 0x56, 0x61, 0x6B, 0x59, 0x47, 0x53, 0x2F, 0x4C, 0x63, 0x78, + 0x77, 0x47, 0x2B, 0x77, 0x51, 0x77, 0x63, 0x4F, 0x43, 0x54, 0x42, 0x45, 0x78, 0x2F, 0x7A, 0x31, + 0x53, 0x30, 0x0A, 0x37, 0x49, 0x2F, 0x6A, 0x62, 0x44, 0x79, 0x53, 0x4E, 0x68, 0x44, 0x35, 0x63, + 0x61, 0x63, 0x54, 0x75, 0x4E, 0x36, 0x50, 0x68, 0x33, 0x58, 0x30, 0x71, 0x70, 0x47, 0x73, 0x37, + 0x79, 0x50, 0x6B, 0x4E, 0x79, 0x69, 0x4A, 0x33, 0x57, 0x52, 0x69, 0x6C, 0x35, 0x75, 0x57, 0x73, + 0x4B, 0x65, 0x79, 0x63, 0x64, 0x71, 0x42, 0x4E, 0x72, 0x34, 0x75, 0x32, 0x62, 0x49, 0x52, 0x6E, + 0x63, 0x54, 0x51, 0x0A, 0x46, 0x72, 0x68, 0x73, 0x58, 0x39, 0x69, 0x77, 0x37, 0x35, 0x76, 0x75, + 0x53, 0x64, 0x35, 0x46, 0x39, 0x37, 0x56, 0x70, 0x41, 0x67, 0x4D, 0x42, 0x41, 0x41, 0x47, 0x6A, + 0x4A, 0x44, 0x41, 0x69, 0x4D, 0x42, 0x4D, 0x47, 0x41, 0x31, 0x55, 0x64, 0x4A, 0x51, 0x51, 0x4D, + 0x4D, 0x41, 0x6F, 0x47, 0x43, 0x43, 0x73, 0x47, 0x41, 0x51, 0x55, 0x46, 0x42, 0x77, 0x4D, 0x42, + 0x4D, 0x41, 0x73, 0x47, 0x0A, 0x41, 0x31, 0x55, 0x64, 0x44, 0x77, 0x51, 0x45, 0x41, 0x77, 0x49, + 0x45, 0x4D, 0x44, 0x41, 0x4E, 0x42, 0x67, 0x6B, 0x71, 0x68, 0x6B, 0x69, 0x47, 0x39, 0x77, 0x30, + 0x42, 0x41, 0x51, 0x55, 0x46, 0x41, 0x41, 0x4F, 0x43, 0x41, 0x51, 0x45, 0x41, 0x49, 0x51, 0x66, + 0x75, 0x2F, 0x77, 0x39, 0x45, 0x34, 0x4C, 0x6F, 0x67, 0x30, 0x71, 0x35, 0x4B, 0x53, 0x38, 0x71, + 0x46, 0x78, 0x62, 0x36, 0x6F, 0x0A, 0x36, 0x31, 0x62, 0x35, 0x37, 0x6F, 0x6D, 0x6E, 0x46, 0x59, + 0x52, 0x34, 0x47, 0x43, 0x67, 0x33, 0x6F, 0x6A, 0x4F, 0x4C, 0x54, 0x66, 0x38, 0x7A, 0x6A, 0x4D, + 0x43, 0x52, 0x6D, 0x75, 0x59, 0x32, 0x76, 0x30, 0x4E, 0x34, 0x78, 0x66, 0x68, 0x69, 0x35, 0x4B, + 0x69, 0x59, 0x67, 0x64, 0x76, 0x4E, 0x4C, 0x4F, 0x33, 0x52, 0x42, 0x6D, 0x4E, 0x50, 0x76, 0x59, + 0x58, 0x50, 0x52, 0x46, 0x41, 0x76, 0x0A, 0x66, 0x61, 0x76, 0x66, 0x57, 0x75, 0x6C, 0x44, 0x31, + 0x64, 0x50, 0x36, 0x31, 0x69, 0x35, 0x62, 0x36, 0x59, 0x66, 0x56, 0x6C, 0x78, 0x62, 0x31, 0x61, + 0x57, 0x46, 0x37, 0x4C, 0x5A, 0x44, 0x32, 0x55, 0x6E, 0x63, 0x41, 0x6A, 0x37, 0x4E, 0x38, 0x78, + 0x38, 0x2B, 0x36, 0x58, 0x6B, 0x30, 0x6B, 0x63, 0x70, 0x58, 0x46, 0x38, 0x6C, 0x77, 0x58, 0x48, + 0x55, 0x57, 0x57, 0x55, 0x6D, 0x73, 0x2B, 0x0A, 0x4B, 0x56, 0x44, 0x34, 0x34, 0x39, 0x68, 0x6F, + 0x4D, 0x2B, 0x77, 0x4E, 0x4A, 0x49, 0x61, 0x4F, 0x52, 0x39, 0x4C, 0x46, 0x2B, 0x6B, 0x6F, 0x32, + 0x32, 0x37, 0x7A, 0x74, 0x37, 0x54, 0x41, 0x47, 0x64, 0x56, 0x35, 0x4A, 0x75, 0x7A, 0x71, 0x38, + 0x32, 0x2F, 0x6B, 0x75, 0x73, 0x6F, 0x65, 0x32, 0x69, 0x75, 0x57, 0x77, 0x54, 0x65, 0x42, 0x6C, + 0x53, 0x5A, 0x6E, 0x6B, 0x42, 0x38, 0x63, 0x64, 0x0A, 0x77, 0x4D, 0x30, 0x5A, 0x42, 0x58, 0x6D, + 0x34, 0x35, 0x48, 0x38, 0x6F, 0x79, 0x75, 0x36, 0x4A, 0x71, 0x59, 0x71, 0x45, 0x6D, 0x75, 0x4A, + 0x51, 0x64, 0x67, 0x79, 0x52, 0x2B, 0x63, 0x53, 0x53, 0x41, 0x7A, 0x2B, 0x4F, 0x32, 0x6D, 0x61, + 0x62, 0x68, 0x50, 0x5A, 0x65, 0x49, 0x76, 0x78, 0x65, 0x67, 0x6A, 0x6A, 0x61, 0x5A, 0x61, 0x46, + 0x4F, 0x71, 0x74, 0x73, 0x2B, 0x64, 0x33, 0x72, 0x39, 0x0A, 0x79, 0x71, 0x4A, 0x78, 0x67, 0x75, + 0x39, 0x43, 0x38, 0x39, 0x5A, 0x69, 0x33, 0x39, 0x57, 0x34, 0x38, 0x46, 0x66, 0x46, 0x63, 0x49, + 0x58, 0x4A, 0x4F, 0x6B, 0x39, 0x43, 0x4E, 0x46, 0x41, 0x2F, 0x69, 0x70, 0x54, 0x57, 0x6A, 0x74, + 0x74, 0x4E, 0x2F, 0x6B, 0x4F, 0x6B, 0x5A, 0x42, 0x70, 0x6F, 0x6A, 0x2F, 0x32, 0x6A, 0x4E, 0x45, + 0x62, 0x4F, 0x59, 0x7A, 0x7A, 0x6E, 0x4B, 0x77, 0x3D, 0x3D, 0x0A, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, + 0x45, 0x4E, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2D, + 0x2D, 0x2D, 0x2D, 0x2D, 0x0A +}; + +static const BYTE test_localhost_key[1704] = { + 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x42, 0x45, 0x47, 0x49, 0x4E, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, + 0x54, 0x45, 0x20, 0x4B, 0x45, 0x59, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A, 0x4D, 0x49, 0x49, 0x45, + 0x76, 0x51, 0x49, 0x42, 0x41, 0x44, 0x41, 0x4E, 0x42, 0x67, 0x6B, 0x71, 0x68, 0x6B, 0x69, 0x47, + 0x39, 0x77, 0x30, 0x42, 0x41, 0x51, 0x45, 0x46, 0x41, 0x41, 0x53, 0x43, 0x42, 0x4B, 0x63, 0x77, + 0x67, 0x67, 0x53, 0x6A, 0x41, 0x67, 0x45, 0x41, 0x41, 0x6F, 0x49, 0x42, 0x41, 0x51, 0x43, 0x33, + 0x65, 0x6E, 0x33, 0x68, 0x5A, 0x4F, 0x53, 0x33, 0x6B, 0x51, 0x2F, 0x55, 0x0A, 0x54, 0x30, 0x53, + 0x45, 0x6C, 0x30, 0x48, 0x6E, 0x50, 0x79, 0x64, 0x48, 0x75, 0x35, 0x39, 0x61, 0x69, 0x71, 0x64, + 0x73, 0x64, 0x53, 0x55, 0x74, 0x6E, 0x43, 0x41, 0x37, 0x46, 0x66, 0x74, 0x30, 0x4F, 0x36, 0x51, + 0x79, 0x68, 0x49, 0x71, 0x58, 0x7A, 0x30, 0x47, 0x32, 0x53, 0x76, 0x77, 0x4C, 0x54, 0x62, 0x79, + 0x68, 0x59, 0x54, 0x68, 0x31, 0x36, 0x78, 0x31, 0x72, 0x45, 0x48, 0x68, 0x31, 0x0A, 0x57, 0x47, + 0x5A, 0x6D, 0x36, 0x77, 0x64, 0x2B, 0x4B, 0x76, 0x38, 0x6B, 0x31, 0x6B, 0x2F, 0x36, 0x6F, 0x41, + 0x2F, 0x4F, 0x51, 0x76, 0x65, 0x61, 0x38, 0x6B, 0x63, 0x45, 0x64, 0x53, 0x72, 0x54, 0x64, 0x75, + 0x71, 0x4A, 0x33, 0x65, 0x66, 0x74, 0x48, 0x4A, 0x4A, 0x6E, 0x43, 0x4B, 0x30, 0x41, 0x62, 0x68, + 0x34, 0x39, 0x41, 0x47, 0x41, 0x50, 0x39, 0x79, 0x58, 0x77, 0x77, 0x59, 0x41, 0x6A, 0x0A, 0x51, + 0x49, 0x52, 0x6E, 0x38, 0x2B, 0x4F, 0x63, 0x63, 0x48, 0x74, 0x6F, 0x4E, 0x75, 0x75, 0x79, 0x52, + 0x63, 0x6B, 0x49, 0x50, 0x71, 0x75, 0x70, 0x78, 0x79, 0x31, 0x4A, 0x5A, 0x4B, 0x39, 0x64, 0x76, + 0x76, 0x62, 0x34, 0x79, 0x53, 0x6B, 0x49, 0x75, 0x7A, 0x62, 0x79, 0x50, 0x6F, 0x54, 0x41, 0x79, + 0x61, 0x55, 0x2B, 0x51, 0x72, 0x70, 0x34, 0x78, 0x67, 0x64, 0x4B, 0x46, 0x54, 0x70, 0x6B, 0x0A, + 0x50, 0x46, 0x34, 0x33, 0x6A, 0x32, 0x4D, 0x6D, 0x5A, 0x72, 0x46, 0x63, 0x42, 0x76, 0x79, 0x6A, + 0x69, 0x35, 0x6A, 0x4F, 0x37, 0x74, 0x66, 0x6F, 0x56, 0x61, 0x6B, 0x59, 0x47, 0x53, 0x2F, 0x4C, + 0x63, 0x78, 0x77, 0x47, 0x2B, 0x77, 0x51, 0x77, 0x63, 0x4F, 0x43, 0x54, 0x42, 0x45, 0x78, 0x2F, + 0x7A, 0x31, 0x53, 0x30, 0x37, 0x49, 0x2F, 0x6A, 0x62, 0x44, 0x79, 0x53, 0x4E, 0x68, 0x44, 0x35, + 0x0A, 0x63, 0x61, 0x63, 0x54, 0x75, 0x4E, 0x36, 0x50, 0x68, 0x33, 0x58, 0x30, 0x71, 0x70, 0x47, + 0x73, 0x37, 0x79, 0x50, 0x6B, 0x4E, 0x79, 0x69, 0x4A, 0x33, 0x57, 0x52, 0x69, 0x6C, 0x35, 0x75, + 0x57, 0x73, 0x4B, 0x65, 0x79, 0x63, 0x64, 0x71, 0x42, 0x4E, 0x72, 0x34, 0x75, 0x32, 0x62, 0x49, + 0x52, 0x6E, 0x63, 0x54, 0x51, 0x46, 0x72, 0x68, 0x73, 0x58, 0x39, 0x69, 0x77, 0x37, 0x35, 0x76, + 0x75, 0x0A, 0x53, 0x64, 0x35, 0x46, 0x39, 0x37, 0x56, 0x70, 0x41, 0x67, 0x4D, 0x42, 0x41, 0x41, + 0x45, 0x43, 0x67, 0x67, 0x45, 0x41, 0x42, 0x36, 0x6A, 0x6C, 0x65, 0x48, 0x4E, 0x74, 0x32, 0x50, + 0x77, 0x46, 0x58, 0x53, 0x65, 0x79, 0x42, 0x4A, 0x63, 0x4C, 0x2B, 0x55, 0x74, 0x35, 0x71, 0x46, + 0x54, 0x38, 0x34, 0x68, 0x72, 0x48, 0x77, 0x6F, 0x39, 0x68, 0x62, 0x66, 0x59, 0x47, 0x6F, 0x6E, + 0x44, 0x59, 0x0A, 0x66, 0x70, 0x47, 0x2B, 0x32, 0x52, 0x30, 0x50, 0x62, 0x43, 0x63, 0x4B, 0x35, + 0x30, 0x46, 0x61, 0x4A, 0x46, 0x36, 0x71, 0x63, 0x56, 0x4A, 0x4E, 0x75, 0x52, 0x36, 0x48, 0x71, + 0x2B, 0x43, 0x55, 0x4A, 0x74, 0x48, 0x35, 0x39, 0x48, 0x48, 0x37, 0x62, 0x68, 0x6A, 0x39, 0x62, + 0x64, 0x78, 0x45, 0x6D, 0x6F, 0x48, 0x30, 0x4A, 0x76, 0x68, 0x45, 0x76, 0x67, 0x4D, 0x2F, 0x55, + 0x38, 0x42, 0x51, 0x0A, 0x65, 0x57, 0x4F, 0x4E, 0x68, 0x78, 0x50, 0x73, 0x69, 0x73, 0x6D, 0x57, + 0x6B, 0x78, 0x61, 0x5A, 0x6F, 0x6C, 0x72, 0x32, 0x69, 0x44, 0x56, 0x72, 0x7A, 0x54, 0x37, 0x55, + 0x4A, 0x71, 0x6A, 0x74, 0x59, 0x49, 0x74, 0x67, 0x2B, 0x37, 0x59, 0x43, 0x32, 0x70, 0x55, 0x58, + 0x6B, 0x64, 0x49, 0x35, 0x4A, 0x4D, 0x67, 0x6C, 0x44, 0x47, 0x4D, 0x52, 0x5A, 0x35, 0x55, 0x5A, + 0x48, 0x75, 0x63, 0x7A, 0x0A, 0x41, 0x56, 0x2B, 0x71, 0x77, 0x77, 0x33, 0x65, 0x45, 0x52, 0x74, + 0x78, 0x44, 0x50, 0x61, 0x61, 0x61, 0x34, 0x54, 0x39, 0x50, 0x64, 0x33, 0x44, 0x31, 0x6D, 0x62, + 0x71, 0x58, 0x66, 0x75, 0x45, 0x68, 0x42, 0x6D, 0x33, 0x51, 0x6F, 0x2B, 0x75, 0x7A, 0x51, 0x32, + 0x36, 0x76, 0x73, 0x66, 0x48, 0x75, 0x56, 0x76, 0x61, 0x39, 0x38, 0x32, 0x4F, 0x6A, 0x41, 0x55, + 0x6A, 0x6E, 0x64, 0x30, 0x70, 0x0A, 0x77, 0x43, 0x53, 0x6E, 0x42, 0x49, 0x48, 0x67, 0x70, 0x73, + 0x30, 0x79, 0x61, 0x45, 0x50, 0x63, 0x37, 0x46, 0x78, 0x39, 0x71, 0x45, 0x63, 0x6D, 0x33, 0x70, + 0x7A, 0x41, 0x56, 0x31, 0x69, 0x72, 0x31, 0x4E, 0x4E, 0x63, 0x51, 0x47, 0x55, 0x45, 0x75, 0x45, + 0x6C, 0x4A, 0x78, 0x76, 0x2B, 0x69, 0x57, 0x34, 0x6D, 0x35, 0x70, 0x7A, 0x4C, 0x6A, 0x64, 0x53, + 0x63, 0x49, 0x30, 0x59, 0x45, 0x73, 0x0A, 0x4D, 0x61, 0x33, 0x78, 0x32, 0x79, 0x48, 0x74, 0x6E, + 0x77, 0x79, 0x65, 0x4C, 0x4D, 0x54, 0x4B, 0x6C, 0x72, 0x46, 0x4B, 0x70, 0x55, 0x4E, 0x4A, 0x62, + 0x78, 0x73, 0x35, 0x32, 0x62, 0x5A, 0x4B, 0x71, 0x49, 0x56, 0x33, 0x33, 0x4A, 0x53, 0x34, 0x41, + 0x51, 0x4B, 0x42, 0x67, 0x51, 0x44, 0x73, 0x4C, 0x54, 0x49, 0x68, 0x35, 0x59, 0x38, 0x4C, 0x2F, + 0x48, 0x33, 0x64, 0x74, 0x68, 0x63, 0x62, 0x0A, 0x53, 0x43, 0x45, 0x77, 0x32, 0x64, 0x42, 0x49, + 0x76, 0x49, 0x79, 0x54, 0x7A, 0x39, 0x53, 0x72, 0x62, 0x33, 0x58, 0x37, 0x37, 0x41, 0x77, 0x57, + 0x45, 0x4C, 0x53, 0x4D, 0x49, 0x57, 0x53, 0x50, 0x55, 0x43, 0x4B, 0x54, 0x49, 0x70, 0x6A, 0x4D, + 0x73, 0x6E, 0x7A, 0x6B, 0x46, 0x67, 0x32, 0x32, 0x59, 0x32, 0x53, 0x75, 0x47, 0x38, 0x4C, 0x72, + 0x50, 0x6D, 0x76, 0x73, 0x46, 0x4A, 0x34, 0x30, 0x0A, 0x32, 0x67, 0x35, 0x44, 0x55, 0x6C, 0x59, + 0x33, 0x59, 0x6D, 0x53, 0x4F, 0x46, 0x61, 0x45, 0x4A, 0x54, 0x70, 0x55, 0x47, 0x44, 0x4D, 0x79, + 0x65, 0x33, 0x74, 0x36, 0x4F, 0x30, 0x6C, 0x63, 0x51, 0x41, 0x66, 0x79, 0x6D, 0x58, 0x66, 0x41, + 0x38, 0x74, 0x50, 0x42, 0x48, 0x6A, 0x5A, 0x78, 0x56, 0x61, 0x38, 0x78, 0x78, 0x52, 0x5A, 0x6E, + 0x56, 0x43, 0x31, 0x41, 0x62, 0x75, 0x49, 0x49, 0x52, 0x0A, 0x6E, 0x77, 0x72, 0x4E, 0x46, 0x2B, + 0x42, 0x6F, 0x53, 0x4B, 0x55, 0x41, 0x73, 0x78, 0x2B, 0x46, 0x75, 0x35, 0x5A, 0x4A, 0x4B, 0x4F, + 0x66, 0x79, 0x4D, 0x51, 0x4B, 0x42, 0x67, 0x51, 0x44, 0x47, 0x34, 0x50, 0x52, 0x39, 0x2F, 0x58, + 0x58, 0x6B, 0x51, 0x54, 0x36, 0x6B, 0x7A, 0x4B, 0x64, 0x34, 0x50, 0x6C, 0x50, 0x4D, 0x63, 0x2B, + 0x4B, 0x51, 0x79, 0x4C, 0x45, 0x6C, 0x4B, 0x39, 0x71, 0x47, 0x0A, 0x41, 0x6D, 0x6E, 0x2F, 0x31, + 0x68, 0x64, 0x69, 0x57, 0x57, 0x4F, 0x52, 0x57, 0x46, 0x62, 0x32, 0x38, 0x30, 0x4D, 0x77, 0x76, + 0x77, 0x41, 0x64, 0x78, 0x72, 0x66, 0x65, 0x4C, 0x57, 0x4D, 0x57, 0x32, 0x66, 0x76, 0x4C, 0x59, + 0x4B, 0x66, 0x6C, 0x4F, 0x35, 0x50, 0x51, 0x44, 0x59, 0x67, 0x4B, 0x4A, 0x78, 0x35, 0x79, 0x50, + 0x37, 0x52, 0x64, 0x38, 0x2F, 0x64, 0x50, 0x79, 0x5A, 0x59, 0x36, 0x0A, 0x7A, 0x56, 0x37, 0x47, + 0x47, 0x6B, 0x51, 0x5A, 0x42, 0x4B, 0x36, 0x79, 0x74, 0x61, 0x66, 0x32, 0x35, 0x44, 0x50, 0x67, + 0x50, 0x72, 0x32, 0x77, 0x73, 0x59, 0x4D, 0x43, 0x6C, 0x53, 0x74, 0x6C, 0x56, 0x74, 0x72, 0x6D, + 0x4F, 0x78, 0x59, 0x55, 0x56, 0x77, 0x42, 0x59, 0x4F, 0x69, 0x36, 0x45, 0x62, 0x50, 0x69, 0x6B, + 0x78, 0x47, 0x48, 0x5A, 0x70, 0x59, 0x6F, 0x5A, 0x5A, 0x70, 0x68, 0x4A, 0x0A, 0x4E, 0x61, 0x38, + 0x4F, 0x4C, 0x31, 0x69, 0x77, 0x75, 0x51, 0x4B, 0x42, 0x67, 0x51, 0x44, 0x42, 0x55, 0x55, 0x31, + 0x54, 0x79, 0x5A, 0x2B, 0x4A, 0x5A, 0x43, 0x64, 0x79, 0x72, 0x33, 0x58, 0x43, 0x63, 0x77, 0x77, + 0x58, 0x2F, 0x48, 0x49, 0x73, 0x31, 0x34, 0x6B, 0x4B, 0x42, 0x48, 0x68, 0x44, 0x79, 0x33, 0x78, + 0x37, 0x74, 0x50, 0x38, 0x2F, 0x6F, 0x48, 0x54, 0x6F, 0x72, 0x76, 0x79, 0x74, 0x0A, 0x41, 0x68, + 0x38, 0x4B, 0x36, 0x4B, 0x72, 0x43, 0x41, 0x75, 0x65, 0x50, 0x6D, 0x79, 0x32, 0x6D, 0x4F, 0x54, + 0x31, 0x54, 0x39, 0x6F, 0x31, 0x61, 0x47, 0x55, 0x49, 0x6C, 0x66, 0x38, 0x72, 0x76, 0x33, 0x2F, + 0x30, 0x45, 0x78, 0x67, 0x53, 0x6B, 0x57, 0x50, 0x6D, 0x4F, 0x41, 0x38, 0x35, 0x49, 0x32, 0x2F, + 0x58, 0x48, 0x65, 0x66, 0x71, 0x54, 0x6F, 0x45, 0x48, 0x30, 0x44, 0x65, 0x41, 0x4E, 0x0A, 0x7A, + 0x6C, 0x4B, 0x4C, 0x71, 0x79, 0x44, 0x56, 0x30, 0x42, 0x56, 0x4E, 0x76, 0x48, 0x42, 0x57, 0x79, + 0x32, 0x49, 0x51, 0x35, 0x62, 0x50, 0x42, 0x57, 0x76, 0x30, 0x37, 0x63, 0x34, 0x2B, 0x6A, 0x39, + 0x4E, 0x62, 0x57, 0x67, 0x64, 0x44, 0x43, 0x43, 0x35, 0x52, 0x6B, 0x4F, 0x6A, 0x70, 0x33, 0x4D, + 0x4E, 0x45, 0x58, 0x47, 0x56, 0x43, 0x69, 0x51, 0x51, 0x4B, 0x42, 0x67, 0x43, 0x7A, 0x4D, 0x0A, + 0x77, 0x65, 0x61, 0x62, 0x73, 0x50, 0x48, 0x68, 0x44, 0x4B, 0x5A, 0x38, 0x2F, 0x34, 0x43, 0x6A, + 0x73, 0x61, 0x62, 0x4E, 0x75, 0x41, 0x7A, 0x62, 0x57, 0x4B, 0x52, 0x42, 0x38, 0x37, 0x44, 0x61, + 0x58, 0x46, 0x78, 0x6F, 0x4D, 0x73, 0x35, 0x52, 0x79, 0x6F, 0x38, 0x55, 0x4D, 0x6B, 0x72, 0x67, + 0x30, 0x35, 0x4C, 0x6F, 0x67, 0x37, 0x4D, 0x78, 0x62, 0x33, 0x76, 0x61, 0x42, 0x34, 0x63, 0x2F, + 0x0A, 0x52, 0x57, 0x77, 0x7A, 0x38, 0x72, 0x34, 0x39, 0x70, 0x48, 0x64, 0x71, 0x68, 0x4F, 0x6D, + 0x63, 0x6C, 0x45, 0x77, 0x79, 0x4D, 0x34, 0x51, 0x79, 0x6A, 0x39, 0x52, 0x6D, 0x57, 0x62, 0x51, + 0x58, 0x54, 0x54, 0x45, 0x63, 0x2B, 0x35, 0x67, 0x54, 0x4B, 0x50, 0x4E, 0x53, 0x33, 0x6D, 0x70, + 0x4D, 0x54, 0x36, 0x39, 0x46, 0x45, 0x74, 0x2F, 0x35, 0x72, 0x4D, 0x52, 0x70, 0x4B, 0x2B, 0x52, + 0x68, 0x0A, 0x49, 0x32, 0x42, 0x58, 0x6B, 0x51, 0x71, 0x31, 0x36, 0x6E, 0x72, 0x31, 0x61, 0x45, + 0x4D, 0x6D, 0x64, 0x51, 0x42, 0x51, 0x79, 0x4B, 0x59, 0x4A, 0x6C, 0x30, 0x6C, 0x50, 0x68, 0x69, + 0x42, 0x2F, 0x75, 0x6C, 0x5A, 0x63, 0x72, 0x67, 0x4C, 0x70, 0x41, 0x6F, 0x47, 0x41, 0x65, 0x30, + 0x65, 0x74, 0x50, 0x4A, 0x77, 0x6D, 0x51, 0x46, 0x6B, 0x6A, 0x4D, 0x70, 0x66, 0x4D, 0x44, 0x61, + 0x4E, 0x34, 0x0A, 0x70, 0x7A, 0x71, 0x45, 0x51, 0x72, 0x52, 0x35, 0x4B, 0x35, 0x4D, 0x6E, 0x54, + 0x48, 0x76, 0x47, 0x67, 0x2F, 0x70, 0x6A, 0x57, 0x6A, 0x43, 0x57, 0x58, 0x56, 0x48, 0x67, 0x35, + 0x76, 0x36, 0x46, 0x6F, 0x5A, 0x48, 0x35, 0x6E, 0x59, 0x2B, 0x56, 0x2F, 0x57, 0x75, 0x57, 0x38, + 0x38, 0x6A, 0x6C, 0x4B, 0x53, 0x50, 0x6C, 0x77, 0x6A, 0x50, 0x7A, 0x41, 0x67, 0x7A, 0x47, 0x33, + 0x45, 0x41, 0x55, 0x0A, 0x71, 0x57, 0x6B, 0x42, 0x67, 0x30, 0x71, 0x75, 0x50, 0x4D, 0x72, 0x54, + 0x6B, 0x73, 0x69, 0x6E, 0x58, 0x50, 0x2B, 0x58, 0x6B, 0x51, 0x65, 0x46, 0x66, 0x58, 0x61, 0x33, + 0x38, 0x6A, 0x72, 0x70, 0x62, 0x4B, 0x46, 0x4F, 0x72, 0x7A, 0x49, 0x6F, 0x6A, 0x69, 0x65, 0x6C, + 0x4B, 0x55, 0x4D, 0x50, 0x4D, 0x78, 0x2F, 0x78, 0x70, 0x53, 0x6A, 0x63, 0x55, 0x42, 0x68, 0x62, + 0x4E, 0x34, 0x45, 0x54, 0x0A, 0x4F, 0x30, 0x66, 0x63, 0x57, 0x47, 0x6F, 0x61, 0x56, 0x50, 0x72, + 0x63, 0x6E, 0x38, 0x62, 0x58, 0x4D, 0x54, 0x45, 0x4E, 0x53, 0x31, 0x41, 0x3D, 0x0A, 0x2D, 0x2D, + 0x2D, 0x2D, 0x2D, 0x45, 0x4E, 0x44, 0x20, 0x50, 0x52, 0x49, 0x56, 0x41, 0x54, 0x45, 0x20, 0x4B, + 0x45, 0x59, 0x2D, 0x2D, 0x2D, 0x2D, 0x2D, 0x0A +}; + +static const BYTE test_DummyMessage[64] = { + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD +}; + +static const BYTE test_LastDummyMessage[64] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static int schannel_send(PSecurityFunctionTable table, HANDLE hPipe, PCtxtHandle phContext, + BYTE* buffer, UINT32 length) +{ + BYTE* ioBuffer; + UINT32 ioBufferLength; + BYTE* pMessageBuffer; + SecBuffer Buffers[4] = { 0 }; + SecBufferDesc Message; + SECURITY_STATUS status; + DWORD NumberOfBytesWritten; + SecPkgContext_StreamSizes StreamSizes = { 0 }; + + status = table->QueryContextAttributes(phContext, SECPKG_ATTR_STREAM_SIZES, &StreamSizes); + ioBufferLength = StreamSizes.cbHeader + StreamSizes.cbMaximumMessage + StreamSizes.cbTrailer; + ioBuffer = (BYTE*)calloc(1, ioBufferLength); + if (!ioBuffer) + return -1; + pMessageBuffer = ioBuffer + StreamSizes.cbHeader; + CopyMemory(pMessageBuffer, buffer, length); + Buffers[0].pvBuffer = ioBuffer; + Buffers[0].cbBuffer = StreamSizes.cbHeader; + Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; + Buffers[1].pvBuffer = pMessageBuffer; + Buffers[1].cbBuffer = length; + Buffers[1].BufferType = SECBUFFER_DATA; + Buffers[2].pvBuffer = pMessageBuffer + length; + Buffers[2].cbBuffer = StreamSizes.cbTrailer; + Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + Buffers[3].pvBuffer = NULL; + Buffers[3].cbBuffer = 0; + Buffers[3].BufferType = SECBUFFER_EMPTY; + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + ioBufferLength = + Message.pBuffers[0].cbBuffer + Message.pBuffers[1].cbBuffer + Message.pBuffers[2].cbBuffer; + status = table->EncryptMessage(phContext, 0, &Message, 0); + printf("EncryptMessage status: 0x%08" PRIX32 "\n", status); + printf("EncryptMessage output: cBuffers: %" PRIu32 " [0]: %" PRIu32 " / %" PRIu32 + " [1]: %" PRIu32 " / %" PRIu32 " [2]: %" PRIu32 " / %" PRIu32 " [3]: %" PRIu32 + " / %" PRIu32 "\n", + Message.cBuffers, Message.pBuffers[0].cbBuffer, Message.pBuffers[0].BufferType, + Message.pBuffers[1].cbBuffer, Message.pBuffers[1].BufferType, + Message.pBuffers[2].cbBuffer, Message.pBuffers[2].BufferType, + Message.pBuffers[3].cbBuffer, Message.pBuffers[3].BufferType); + + if (status != SEC_E_OK) + return -1; + + printf("Client > Server (%" PRIu32 ")\n", ioBufferLength); + winpr_HexDump("sspi.test", WLOG_DEBUG, ioBuffer, ioBufferLength); + + if (!WriteFile(hPipe, ioBuffer, ioBufferLength, &NumberOfBytesWritten, NULL)) + { + printf("schannel_send: failed to write to pipe\n"); + return -1; + } + + return 0; +} + +static int schannel_recv(PSecurityFunctionTable table, HANDLE hPipe, PCtxtHandle phContext) +{ + BYTE* ioBuffer; + UINT32 ioBufferLength; + // BYTE* pMessageBuffer; + SecBuffer Buffers[4] = { 0 }; + SecBufferDesc Message; + SECURITY_STATUS status; + DWORD NumberOfBytesRead; + SecPkgContext_StreamSizes StreamSizes = { 0 }; + + status = table->QueryContextAttributes(phContext, SECPKG_ATTR_STREAM_SIZES, &StreamSizes); + ioBufferLength = StreamSizes.cbHeader + StreamSizes.cbMaximumMessage + StreamSizes.cbTrailer; + ioBuffer = (BYTE*)calloc(1, ioBufferLength); + if (!ioBuffer) + return -1; + + if (!ReadFile(hPipe, ioBuffer, ioBufferLength, &NumberOfBytesRead, NULL)) + { + printf("schannel_recv: failed to read from pipe\n"); + return -1; + } + + Buffers[0].pvBuffer = ioBuffer; + Buffers[0].cbBuffer = NumberOfBytesRead; + Buffers[0].BufferType = SECBUFFER_DATA; + Buffers[1].pvBuffer = NULL; + Buffers[1].cbBuffer = 0; + Buffers[1].BufferType = SECBUFFER_EMPTY; + Buffers[2].pvBuffer = NULL; + Buffers[2].cbBuffer = 0; + Buffers[2].BufferType = SECBUFFER_EMPTY; + Buffers[3].pvBuffer = NULL; + Buffers[3].cbBuffer = 0; + Buffers[3].BufferType = SECBUFFER_EMPTY; + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + status = table->DecryptMessage(phContext, &Message, 0, NULL); + printf("DecryptMessage status: 0x%08" PRIX32 "\n", status); + printf("DecryptMessage output: cBuffers: %" PRIu32 " [0]: %" PRIu32 " / %" PRIu32 + " [1]: %" PRIu32 " / %" PRIu32 " [2]: %" PRIu32 " / %" PRIu32 " [3]: %" PRIu32 + " / %" PRIu32 "\n", + Message.cBuffers, Message.pBuffers[0].cbBuffer, Message.pBuffers[0].BufferType, + Message.pBuffers[1].cbBuffer, Message.pBuffers[1].BufferType, + Message.pBuffers[2].cbBuffer, Message.pBuffers[2].BufferType, + Message.pBuffers[3].cbBuffer, Message.pBuffers[3].BufferType); + + if (status != SEC_E_OK) + return -1; + + printf("Decrypted Message (%" PRIu32 ")\n", Message.pBuffers[1].cbBuffer); + winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)Message.pBuffers[1].pvBuffer, + Message.pBuffers[1].cbBuffer); + + if (memcmp(Message.pBuffers[1].pvBuffer, test_LastDummyMessage, + sizeof(test_LastDummyMessage)) == 0) + return -1; + + return 0; +} + +static DWORD WINAPI schannel_test_server_thread(LPVOID arg) +{ + BOOL extraData; + BYTE* lpTokenIn; + BYTE* lpTokenOut; + TimeStamp expiry; + UINT32 cbMaxToken; + UINT32 fContextReq; + ULONG fContextAttr; + SCHANNEL_CRED cred = { 0 }; + CtxtHandle context; + CredHandle credentials; + DWORD cchNameString; + LPTSTR pszNameString; + HCERTSTORE hCertStore; + PCCERT_CONTEXT pCertContext; + PSecBuffer pSecBuffer; + SecBuffer SecBuffer_in[2] = { 0 }; + SecBuffer SecBuffer_out[2] = { 0 }; + SecBufferDesc SecBufferDesc_in; + SecBufferDesc SecBufferDesc_out; + DWORD NumberOfBytesRead; + SECURITY_STATUS status; + PSecPkgInfo pPackageInfo; + PSecurityFunctionTable table; + DWORD NumberOfBytesWritten; + printf("Starting Server\n"); + SecInvalidateHandle(&context); + SecInvalidateHandle(&credentials); + table = InitSecurityInterface(); + status = QuerySecurityPackageInfo(SCHANNEL_NAME, &pPackageInfo); + + if (status != SEC_E_OK) + { + printf("QuerySecurityPackageInfo failure: 0x%08" PRIX32 "\n", status); + return 0; + } + + cbMaxToken = pPackageInfo->cbMaxToken; + hCertStore = CertOpenSystemStore(0, _T("MY")); + + if (!hCertStore) + { + printf("Error opening system store\n"); + // return NULL; + } + +#ifdef CERT_FIND_HAS_PRIVATE_KEY + pCertContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0, + CERT_FIND_HAS_PRIVATE_KEY, NULL, NULL); +#else + pCertContext = + CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL); +#endif + + if (!pCertContext) + { + printf("Error finding certificate in store\n"); + // return NULL; + } + + cchNameString = + CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0); + pszNameString = (LPTSTR)malloc(cchNameString * sizeof(TCHAR)); + if (!pszNameString) + { + printf("Memory allocation failed\n"); + return 0; + } + cchNameString = CertGetNameString(pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, + pszNameString, cchNameString); + _tprintf(_T("Certificate Name: %s\n"), pszNameString); + cred.dwVersion = SCHANNEL_CRED_VERSION; + cred.cCreds = 1; + cred.paCred = &pCertContext; + cred.cSupportedAlgs = 0; + cred.palgSupportedAlgs = NULL; + cred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER; + cred.dwFlags = SCH_CRED_NO_SYSTEM_MAPPER; + status = table->AcquireCredentialsHandle(NULL, SCHANNEL_NAME, SECPKG_CRED_INBOUND, NULL, &cred, + NULL, NULL, &credentials, NULL); + + if (status != SEC_E_OK) + { + printf("AcquireCredentialsHandle failure: 0x%08" PRIX32 "\n", status); + return 0; + } + + extraData = FALSE; + g_ServerWait = TRUE; + if (!(lpTokenIn = (BYTE*)malloc(cbMaxToken))) + { + printf("Memory allocation failed\n"); + return 0; + } + if (!(lpTokenOut = (BYTE*)malloc(cbMaxToken))) + { + printf("Memory allocation failed\n"); + free(lpTokenIn); + return 0; + } + fContextReq = ASC_REQ_STREAM | ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | + ASC_REQ_CONFIDENTIALITY | ASC_REQ_EXTENDED_ERROR; + + do + { + if (!extraData) + { + if (g_ServerWait) + { + if (!ReadFile(g_ServerReadPipe, lpTokenIn, cbMaxToken, &NumberOfBytesRead, NULL)) + { + printf("Failed to read from server pipe\n"); + return NULL; + } + } + else + { + NumberOfBytesRead = 0; + } + } + + extraData = FALSE; + g_ServerWait = TRUE; + SecBuffer_in[0].BufferType = SECBUFFER_TOKEN; + SecBuffer_in[0].pvBuffer = lpTokenIn; + SecBuffer_in[0].cbBuffer = NumberOfBytesRead; + SecBuffer_in[1].BufferType = SECBUFFER_EMPTY; + SecBuffer_in[1].pvBuffer = NULL; + SecBuffer_in[1].cbBuffer = 0; + SecBufferDesc_in.ulVersion = SECBUFFER_VERSION; + SecBufferDesc_in.cBuffers = 2; + SecBufferDesc_in.pBuffers = SecBuffer_in; + SecBuffer_out[0].BufferType = SECBUFFER_TOKEN; + SecBuffer_out[0].pvBuffer = lpTokenOut; + SecBuffer_out[0].cbBuffer = cbMaxToken; + SecBufferDesc_out.ulVersion = SECBUFFER_VERSION; + SecBufferDesc_out.cBuffers = 1; + SecBufferDesc_out.pBuffers = SecBuffer_out; + status = table->AcceptSecurityContext( + &credentials, SecIsValidHandle(&context) ? &context : NULL, &SecBufferDesc_in, + fContextReq, 0, &context, &SecBufferDesc_out, &fContextAttr, &expiry); + + if ((status != SEC_E_OK) && (status != SEC_I_CONTINUE_NEEDED) && + (status != SEC_E_INCOMPLETE_MESSAGE)) + { + printf("AcceptSecurityContext unexpected status: 0x%08" PRIX32 "\n", status); + return NULL; + } + + NumberOfBytesWritten = 0; + + if (status == SEC_E_OK) + printf("AcceptSecurityContext status: SEC_E_OK\n"); + else if (status == SEC_I_CONTINUE_NEEDED) + printf("AcceptSecurityContext status: SEC_I_CONTINUE_NEEDED\n"); + else if (status == SEC_E_INCOMPLETE_MESSAGE) + printf("AcceptSecurityContext status: SEC_E_INCOMPLETE_MESSAGE\n"); + + printf("Server cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32 "\n", + SecBufferDesc_out.cBuffers, SecBufferDesc_out.pBuffers[0].cbBuffer, + SecBufferDesc_out.pBuffers[0].BufferType); + printf("Server Input cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32 + " pBuffers[1]: %" PRIu32 " type: %" PRIu32 "\n", + SecBufferDesc_in.cBuffers, SecBufferDesc_in.pBuffers[0].cbBuffer, + SecBufferDesc_in.pBuffers[0].BufferType, SecBufferDesc_in.pBuffers[1].cbBuffer, + SecBufferDesc_in.pBuffers[1].BufferType); + + if (SecBufferDesc_in.pBuffers[1].BufferType == SECBUFFER_EXTRA) + { + printf("AcceptSecurityContext SECBUFFER_EXTRA\n"); + pSecBuffer = &SecBufferDesc_in.pBuffers[1]; + CopyMemory(lpTokenIn, &lpTokenIn[NumberOfBytesRead - pSecBuffer->cbBuffer], + pSecBuffer->cbBuffer); + NumberOfBytesRead = pSecBuffer->cbBuffer; + continue; + } + + if (status != SEC_E_INCOMPLETE_MESSAGE) + { + pSecBuffer = &SecBufferDesc_out.pBuffers[0]; + + if (pSecBuffer->cbBuffer > 0) + { + printf("Server > Client (%" PRIu32 ")\n", pSecBuffer->cbBuffer); + winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, + pSecBuffer->cbBuffer); + + if (!WriteFile(g_ClientWritePipe, pSecBuffer->pvBuffer, pSecBuffer->cbBuffer, + &NumberOfBytesWritten, NULL)) + { + printf("failed to write to client pipe\n"); + return NULL; + } + } + } + + if (status == SEC_E_OK) + { + printf("Server Handshake Complete\n"); + break; + } + } while (1); + + do + { + if (schannel_recv(table, g_ServerReadPipe, &context) < 0) + break; + } while (1); + + return 0; +} + +static int dump_test_certificate_files(void) +{ + FILE* fp; + char* fullpath = NULL; + int ret = -1; + + /* + * Output Certificate File + */ + fullpath = GetCombinedPath("/tmp", "localhost.crt"); + if (!fullpath) + return -1; + + fp = winpr_fopen(fullpath, "w+"); + if (fp) + { + if (fwrite((void*)test_localhost_crt, sizeof(test_localhost_crt), 1, fp) != 1) + goto out_fail; + fclose(fp); + fp = NULL; + } + free(fullpath); + + /* + * Output Private Key File + */ + fullpath = GetCombinedPath("/tmp", "localhost.key"); + if (!fullpath) + return -1; + fp = winpr_fopen(fullpath, "w+"); + if (fp && fwrite((void*)test_localhost_key, sizeof(test_localhost_key), 1, fp) != 1) + goto out_fail; + + ret = 1; +out_fail: + free(fullpath); + if (fp) + fclose(fp); + return ret; +} + +int TestSchannel(int argc, char* argv[]) +{ + int count; + ALG_ID algId; + HANDLE thread; + BYTE* lpTokenIn; + BYTE* lpTokenOut; + TimeStamp expiry; + UINT32 cbMaxToken; + SCHANNEL_CRED cred = { 0 }; + UINT32 fContextReq; + ULONG fContextAttr; + CtxtHandle context; + CredHandle credentials; + SECURITY_STATUS status; + PSecPkgInfo pPackageInfo; + PSecBuffer pSecBuffer; + PSecurityFunctionTable table; + DWORD NumberOfBytesRead; + DWORD NumberOfBytesWritten; + SecPkgCred_SupportedAlgs SupportedAlgs = { 0 }; + SecPkgCred_CipherStrengths CipherStrengths = { 0 }; + SecPkgCred_SupportedProtocols SupportedProtocols = { 0 }; + return 0; /* disable by default - causes crash */ + sspi_GlobalInit(); + dump_test_certificate_files(); + SecInvalidateHandle(&context); + SecInvalidateHandle(&credentials); + + if (!CreatePipe(&g_ClientReadPipe, &g_ClientWritePipe, NULL, 0)) + { + printf("Failed to create client pipe\n"); + return -1; + } + + if (!CreatePipe(&g_ServerReadPipe, &g_ServerWritePipe, NULL, 0)) + { + printf("Failed to create server pipe\n"); + return -1; + } + + if (!(thread = CreateThread(NULL, 0, schannel_test_server_thread, NULL, 0, NULL))) + { + printf("Failed to create server thread\n"); + return -1; + } + + table = InitSecurityInterface(); + status = QuerySecurityPackageInfo(SCHANNEL_NAME, &pPackageInfo); + + if (status != SEC_E_OK) + { + printf("QuerySecurityPackageInfo failure: 0x%08" PRIX32 "\n", status); + return -1; + } + + cbMaxToken = pPackageInfo->cbMaxToken; + cred.dwVersion = SCHANNEL_CRED_VERSION; + cred.cCreds = 0; + cred.paCred = NULL; + cred.cSupportedAlgs = 0; + cred.palgSupportedAlgs = NULL; + cred.grbitEnabledProtocols = SP_PROT_SSL3TLS1_CLIENTS; + cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS; + cred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION; + cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK; + status = table->AcquireCredentialsHandle(NULL, SCHANNEL_NAME, SECPKG_CRED_OUTBOUND, NULL, &cred, + NULL, NULL, &credentials, NULL); + + if (status != SEC_E_OK) + { + printf("AcquireCredentialsHandle failure: 0x%08" PRIX32 "\n", status); + return -1; + } + + status = + table->QueryCredentialsAttributes(&credentials, SECPKG_ATTR_SUPPORTED_ALGS, &SupportedAlgs); + + if (status != SEC_E_OK) + { + printf("QueryCredentialsAttributes SECPKG_ATTR_SUPPORTED_ALGS failure: 0x%08" PRIX32 "\n", + status); + return -1; + } + + /** + * SupportedAlgs: 15 + * 0x660E 0x6610 0x6801 0x6603 0x6601 0x8003 0x8004 + * 0x800C 0x800D 0x800E 0x2400 0xAA02 0xAE06 0x2200 0x2203 + */ + printf("SupportedAlgs: %" PRIu32 "\n", SupportedAlgs.cSupportedAlgs); + + for (DWORD index = 0; index < SupportedAlgs.cSupportedAlgs; index++) + { + algId = SupportedAlgs.palgSupportedAlgs[index]; + printf("\t0x%08" PRIX32 " CLASS: %" PRIu32 " TYPE: %" PRIu32 " SID: %" PRIu32 "\n", algId, + ((GET_ALG_CLASS(algId)) >> 13), ((GET_ALG_TYPE(algId)) >> 9), GET_ALG_SID(algId)); + } + + printf("\n"); + status = table->QueryCredentialsAttributes(&credentials, SECPKG_ATTR_CIPHER_STRENGTHS, + &CipherStrengths); + + if (status != SEC_E_OK) + { + printf("QueryCredentialsAttributes SECPKG_ATTR_CIPHER_STRENGTHS failure: 0x%08" PRIX32 "\n", + status); + return -1; + } + + /* CipherStrengths: Minimum: 40 Maximum: 256 */ + printf("CipherStrengths: Minimum: %" PRIu32 " Maximum: %" PRIu32 "\n", + CipherStrengths.dwMinimumCipherStrength, CipherStrengths.dwMaximumCipherStrength); + status = table->QueryCredentialsAttributes(&credentials, SECPKG_ATTR_SUPPORTED_PROTOCOLS, + &SupportedProtocols); + + if (status != SEC_E_OK) + { + printf("QueryCredentialsAttributes SECPKG_ATTR_SUPPORTED_PROTOCOLS failure: 0x%08" PRIX32 + "\n", + status); + return -1; + } + + /* SupportedProtocols: 0x208A0 */ + printf("SupportedProtocols: 0x%08" PRIX32 "\n", SupportedProtocols.grbitProtocol); + fContextReq = ISC_REQ_STREAM | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | + ISC_REQ_MANUAL_CRED_VALIDATION | ISC_REQ_INTEGRITY; + if (!(lpTokenIn = (BYTE*)malloc(cbMaxToken))) + { + printf("Memory allocation failed\n"); + return -1; + } + if (!(lpTokenOut = (BYTE*)malloc(cbMaxToken))) + { + printf("Memory allocation failed\n"); + return -1; + } + g_ClientWait = FALSE; + + do + { + SecBuffer SecBuffer_in[2] = { 0 }; + SecBuffer SecBuffer_out[1] = { 0 }; + SecBufferDesc SecBufferDesc_in = { 0 }; + SecBufferDesc SecBufferDesc_out = { 0 }; + if (g_ClientWait) + { + if (!ReadFile(g_ClientReadPipe, lpTokenIn, cbMaxToken, &NumberOfBytesRead, NULL)) + { + printf("failed to read from server pipe\n"); + return -1; + } + } + else + { + NumberOfBytesRead = 0; + } + + g_ClientWait = TRUE; + printf("NumberOfBytesRead: %" PRIu32 "\n", NumberOfBytesRead); + SecBuffer_in[0].BufferType = SECBUFFER_TOKEN; + SecBuffer_in[0].pvBuffer = lpTokenIn; + SecBuffer_in[0].cbBuffer = NumberOfBytesRead; + SecBuffer_in[1].pvBuffer = NULL; + SecBuffer_in[1].cbBuffer = 0; + SecBuffer_in[1].BufferType = SECBUFFER_EMPTY; + SecBufferDesc_in.ulVersion = SECBUFFER_VERSION; + SecBufferDesc_in.cBuffers = 2; + SecBufferDesc_in.pBuffers = SecBuffer_in; + SecBuffer_out[0].BufferType = SECBUFFER_TOKEN; + SecBuffer_out[0].pvBuffer = lpTokenOut; + SecBuffer_out[0].cbBuffer = cbMaxToken; + SecBufferDesc_out.ulVersion = SECBUFFER_VERSION; + SecBufferDesc_out.cBuffers = 1; + SecBufferDesc_out.pBuffers = SecBuffer_out; + status = table->InitializeSecurityContext( + &credentials, SecIsValidHandle(&context) ? &context : NULL, _T("localhost"), + fContextReq, 0, 0, &SecBufferDesc_in, 0, &context, &SecBufferDesc_out, &fContextAttr, + &expiry); + + if ((status != SEC_E_OK) && (status != SEC_I_CONTINUE_NEEDED) && + (status != SEC_E_INCOMPLETE_MESSAGE)) + { + printf("InitializeSecurityContext unexpected status: 0x%08" PRIX32 "\n", status); + return -1; + } + + NumberOfBytesWritten = 0; + + if (status == SEC_E_OK) + printf("InitializeSecurityContext status: SEC_E_OK\n"); + else if (status == SEC_I_CONTINUE_NEEDED) + printf("InitializeSecurityContext status: SEC_I_CONTINUE_NEEDED\n"); + else if (status == SEC_E_INCOMPLETE_MESSAGE) + printf("InitializeSecurityContext status: SEC_E_INCOMPLETE_MESSAGE\n"); + + printf("Client Output cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32 "\n", + SecBufferDesc_out.cBuffers, SecBufferDesc_out.pBuffers[0].cbBuffer, + SecBufferDesc_out.pBuffers[0].BufferType); + printf("Client Input cBuffers: %" PRIu32 " pBuffers[0]: %" PRIu32 " type: %" PRIu32 + " pBuffers[1]: %" PRIu32 " type: %" PRIu32 "\n", + SecBufferDesc_in.cBuffers, SecBufferDesc_in.pBuffers[0].cbBuffer, + SecBufferDesc_in.pBuffers[0].BufferType, SecBufferDesc_in.pBuffers[1].cbBuffer, + SecBufferDesc_in.pBuffers[1].BufferType); + + if (status != SEC_E_INCOMPLETE_MESSAGE) + { + pSecBuffer = &SecBufferDesc_out.pBuffers[0]; + + if (pSecBuffer->cbBuffer > 0) + { + printf("Client > Server (%" PRIu32 ")\n", pSecBuffer->cbBuffer); + winpr_HexDump("sspi.test", WLOG_DEBUG, (BYTE*)pSecBuffer->pvBuffer, + pSecBuffer->cbBuffer); + + if (!WriteFile(g_ServerWritePipe, pSecBuffer->pvBuffer, pSecBuffer->cbBuffer, + &NumberOfBytesWritten, NULL)) + { + printf("failed to write to server pipe\n"); + return -1; + } + } + } + + if (status == SEC_E_OK) + { + printf("Client Handshake Complete\n"); + break; + } + } while (1); + + count = 0; + + do + { + if (schannel_send(table, g_ServerWritePipe, &context, test_DummyMessage, + sizeof(test_DummyMessage)) < 0) + break; + + for (DWORD index = 0; index < sizeof(test_DummyMessage); index++) + { + BYTE b, ln, hn; + b = test_DummyMessage[index]; + ln = (b & 0x0F); + hn = ((b & 0xF0) >> 4); + ln = (ln + 1) % 0xF; + hn = (ln + 1) % 0xF; + b = (ln | (hn << 4)); + test_DummyMessage[index] = b; + } + + Sleep(100); + count++; + } while (count < 3); + + schannel_send(table, g_ServerWritePipe, &context, test_LastDummyMessage, + sizeof(test_LastDummyMessage)); + WaitForSingleObject(thread, INFINITE); + sspi_GlobalFinish(); + return 0; +} diff --git a/winpr/libwinpr/sspicli/CMakeLists.txt b/winpr/libwinpr/sspicli/CMakeLists.txt new file mode 100644 index 0000000..f50585e --- /dev/null +++ b/winpr/libwinpr/sspicli/CMakeLists.txt @@ -0,0 +1,18 @@ +# WinPR: Windows Portable Runtime +# libwinpr-sspicli cmake build script +# +# Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +winpr_module_add(sspicli.c) diff --git a/winpr/libwinpr/sspicli/ModuleOptions.cmake b/winpr/libwinpr/sspicli/ModuleOptions.cmake new file mode 100644 index 0000000..3a356c7 --- /dev/null +++ b/winpr/libwinpr/sspicli/ModuleOptions.cmake @@ -0,0 +1,9 @@ + +set(MINWIN_LAYER "0") +set(MINWIN_GROUP "none") +set(MINWIN_MAJOR_VERSION "0") +set(MINWIN_MINOR_VERSION "0") +set(MINWIN_SHORT_NAME "sspicli") +set(MINWIN_LONG_NAME "Authentication Functions") +set(MODULE_LIBRARY_NAME "${MINWIN_SHORT_NAME}") + diff --git a/winpr/libwinpr/sspicli/sspicli.c b/winpr/libwinpr/sspicli/sspicli.c new file mode 100644 index 0000000..f3e922c --- /dev/null +++ b/winpr/libwinpr/sspicli/sspicli.c @@ -0,0 +1,275 @@ +/** + * WinPR: Windows Portable Runtime + * Security Support Provider Interface + * + * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/config.h> + +#include <winpr/assert.h> +#include <winpr/sspicli.h> + +/** + * sspicli.dll: + * + * EnumerateSecurityPackagesA + * EnumerateSecurityPackagesW + * GetUserNameExW + * ImportSecurityContextA + * LogonUser + * LogonUserEx + * LogonUserExExW + * SspiCompareAuthIdentities + * SspiCopyAuthIdentity + * SspiDecryptAuthIdentity + * SspiEncodeAuthIdentityAsStrings + * SspiEncodeStringsAsAuthIdentity + * SspiEncryptAuthIdentity + * SspiExcludePackage + * SspiFreeAuthIdentity + * SspiGetTargetHostName + * SspiIsAuthIdentityEncrypted + * SspiLocalFree + * SspiMarshalAuthIdentity + * SspiPrepareForCredRead + * SspiPrepareForCredWrite + * SspiUnmarshalAuthIdentity + * SspiValidateAuthIdentity + * SspiZeroAuthIdentity + */ + +#ifndef _WIN32 + +#include <winpr/crt.h> + +#ifdef WINPR_HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if defined(WINPR_HAVE_GETPWUID_R) +#include <sys/types.h> +#endif + +#include <pthread.h> + +#include <pwd.h> +#include <grp.h> + +#include "../handle/handle.h" + +#include "../security/security.h" + +static BOOL LogonUserCloseHandle(HANDLE handle); + +static BOOL LogonUserIsHandled(HANDLE handle) +{ + return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_ACCESS_TOKEN, FALSE); +} + +static int LogonUserGetFd(HANDLE handle) +{ + WINPR_ACCESS_TOKEN* pLogonUser = (WINPR_ACCESS_TOKEN*)handle; + + if (!LogonUserIsHandled(handle)) + return -1; + + /* TODO: File fd not supported */ + (void)pLogonUser; + return -1; +} + +BOOL LogonUserCloseHandle(HANDLE handle) +{ + WINPR_ACCESS_TOKEN* token = (WINPR_ACCESS_TOKEN*)handle; + + if (!handle || !LogonUserIsHandled(handle)) + return FALSE; + + free(token->Username); + free(token->Domain); + free(token); + return TRUE; +} + +static HANDLE_OPS ops = { LogonUserIsHandled, + LogonUserCloseHandle, + LogonUserGetFd, + NULL, /* CleanupHandle */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL }; + +BOOL LogonUserA(LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, + DWORD dwLogonProvider, PHANDLE phToken) +{ + struct passwd* pw = NULL; + WINPR_ACCESS_TOKEN* token = NULL; + + if (!lpszUsername) + return FALSE; + + token = (WINPR_ACCESS_TOKEN*)calloc(1, sizeof(WINPR_ACCESS_TOKEN)); + + if (!token) + return FALSE; + + WINPR_HANDLE_SET_TYPE_AND_MODE(token, HANDLE_TYPE_ACCESS_TOKEN, WINPR_FD_READ); + token->common.ops = &ops; + token->Username = _strdup(lpszUsername); + + if (!token->Username) + { + free(token); + return FALSE; + } + + if (lpszDomain) + { + token->Domain = _strdup(lpszDomain); + + if (!token->Domain) + { + free(token->Username); + free(token); + return FALSE; + } + } + + pw = getpwnam(lpszUsername); + + if (pw) + { + token->UserId = (DWORD)pw->pw_uid; + token->GroupId = (DWORD)pw->pw_gid; + } + + *((ULONG_PTR*)phToken) = (ULONG_PTR)token; + return TRUE; +} + +BOOL LogonUserW(LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword, DWORD dwLogonType, + DWORD dwLogonProvider, PHANDLE phToken) +{ + return TRUE; +} + +BOOL LogonUserExA(LPCSTR lpszUsername, LPCSTR lpszDomain, LPCSTR lpszPassword, DWORD dwLogonType, + DWORD dwLogonProvider, PHANDLE phToken, PSID* ppLogonSid, PVOID* ppProfileBuffer, + LPDWORD pdwProfileLength, PQUOTA_LIMITS pQuotaLimits) +{ + return TRUE; +} + +BOOL LogonUserExW(LPCWSTR lpszUsername, LPCWSTR lpszDomain, LPCWSTR lpszPassword, DWORD dwLogonType, + DWORD dwLogonProvider, PHANDLE phToken, PSID* ppLogonSid, PVOID* ppProfileBuffer, + LPDWORD pdwProfileLength, PQUOTA_LIMITS pQuotaLimits) +{ + return TRUE; +} + +BOOL GetUserNameExA(EXTENDED_NAME_FORMAT NameFormat, LPSTR lpNameBuffer, PULONG nSize) +{ + WINPR_ASSERT(lpNameBuffer); + WINPR_ASSERT(nSize); + + switch (NameFormat) + { + case NameSamCompatible: +#if defined(WINPR_HAVE_GETPWUID_R) + { + int rc = 0; + struct passwd pwd = { 0 }; + struct passwd* result = NULL; + uid_t uid = getuid(); + + rc = getpwuid_r(uid, &pwd, lpNameBuffer, *nSize, &result); + if (rc != 0) + return FALSE; + if (result == NULL) + return FALSE; + } +#elif defined(WINPR_HAVE_GETLOGIN_R) + if (getlogin_r(lpNameBuffer, *nSize) != 0) + return FALSE; +#else + { + const char* name = getlogin(); + if (!name) + return FALSE; + strncpy(lpNameBuffer, name, strnlen(name, *nSize)); + } +#endif + *nSize = strnlen(lpNameBuffer, *nSize); + return TRUE; + + case NameFullyQualifiedDN: + case NameDisplay: + case NameUniqueId: + case NameCanonical: + case NameUserPrincipal: + case NameCanonicalEx: + case NameServicePrincipal: + case NameDnsDomain: + break; + + default: + break; + } + + return FALSE; +} + +BOOL GetUserNameExW(EXTENDED_NAME_FORMAT NameFormat, LPWSTR lpNameBuffer, PULONG nSize) +{ + BOOL rc = FALSE; + char* name = NULL; + + WINPR_ASSERT(nSize); + WINPR_ASSERT(lpNameBuffer); + + name = calloc(1, *nSize + 1); + if (!name) + goto fail; + + if (!GetUserNameExA(NameFormat, name, nSize)) + goto fail; + + const SSIZE_T res = ConvertUtf8ToWChar(name, lpNameBuffer, *nSize); + if (res < 0) + goto fail; + + *nSize = res + 1; + rc = TRUE; +fail: + free(name); + return rc; +} + +#endif |