/** * FreeRDP: A Remote Desktop Protocol Client * Kerberos Auth Protocol * * Copyright 2015 ANSSI, Author Thomas Calderon * Copyright 2017 Dorian Ducournau * Copyright 2022 David Fort * Copyright 2022 Isaac Klein * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kerberos.h" #ifdef WITH_KRB5_MIT #include "krb5glue.h" #include #endif #ifdef WITH_KRB5_HEIMDAL #include "krb5glue.h" #include #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); free(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 (!name) return NULL; 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; char* 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 = encrypt_iov[2].data.data + encrypt_iov[2].data.length; encrypt_iov[4].data.data = encrypt_iov[3].data.data + encrypt_iov[3].data.length; encrypt_iov[0].data.data = 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] = (char)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 = iov[2].data.data + iov[2].data.length; iov[4].data.data = 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; }