server-side kerberos (and some fixes)

This commit is contained in:
fifthdegree 2022-06-06 23:30:42 -04:00 committed by David Fort
parent 8d9f990124
commit eeece1a027
10 changed files with 207 additions and 52 deletions

View File

@ -680,6 +680,7 @@ typedef struct
#define FreeRDP_KerberosRenewableLifeTime (1348)
#define FreeRDP_KerberosCache (1349)
#define FreeRDP_KerberosArmor (1350)
#define FreeRDP_KerberosKeytab (1351)
#define FreeRDP_IgnoreCertificate (1408)
#define FreeRDP_CertificateName (1409)
#define FreeRDP_CertificateFile (1410)
@ -1186,7 +1187,8 @@ struct rdp_settings
ALIGN64 char* KerberosRenewableLifeTime; /* 1348 */
ALIGN64 char* KerberosCache; /* 1349 */
ALIGN64 char* KerberosArmor; /* 1350 */
UINT64 padding1408[1408 - 1351]; /* 1351 */
ALIGN64 char* KerberosKeytab; /* 1351 */
UINT64 padding1408[1408 - 1352]; /* 1352 */
/* Server Certificate */
ALIGN64 BOOL IgnoreCertificate; /* 1408 */

View File

@ -2454,6 +2454,9 @@ const char* freerdp_settings_get_string(const rdpSettings* settings, size_t id)
case FreeRDP_KerberosKdc:
return settings->KerberosKdc;
case FreeRDP_KerberosKeytab:
return settings->KerberosKeytab;
case FreeRDP_KerberosLifeTime:
return settings->KerberosLifeTime;
@ -2718,6 +2721,9 @@ char* freerdp_settings_get_string_writable(rdpSettings* settings, size_t id)
case FreeRDP_KerberosKdc:
return settings->KerberosKdc;
case FreeRDP_KerberosKeytab:
return settings->KerberosKeytab;
case FreeRDP_KerberosLifeTime:
return settings->KerberosLifeTime;
@ -2992,6 +2998,9 @@ BOOL freerdp_settings_set_string_(rdpSettings* settings, size_t id, const char*
case FreeRDP_KerberosKdc:
return update_string(&settings->KerberosKdc, cnv.cc, len, cleanup);
case FreeRDP_KerberosKeytab:
return update_string(&settings->KerberosKeytab, cnv.cc, len, cleanup);
case FreeRDP_KerberosLifeTime:
return update_string(&settings->KerberosLifeTime, cnv.cc, len, cleanup);

View File

@ -347,6 +347,7 @@ static const struct settings_str_entry settings_map[] = {
{ FreeRDP_KerberosArmor, 7, "FreeRDP_KerberosArmor" },
{ FreeRDP_KerberosCache, 7, "FreeRDP_KerberosCache" },
{ FreeRDP_KerberosKdc, 7, "FreeRDP_KerberosKdc" },
{ FreeRDP_KerberosKeytab, 7, "FreeRDP_KerberosKeytab" },
{ FreeRDP_KerberosLifeTime, 7, "FreeRDP_KerberosLifeTime" },
{ FreeRDP_KerberosRealm, 7, "FreeRDP_KerberosRealm" },
{ FreeRDP_KerberosRenewableLifeTime, 7, "FreeRDP_KerberosRenewableLifeTime" },

View File

@ -943,6 +943,16 @@ static BOOL nla_setup_kerberos(rdpNla* nla)
}
}
if (settings->KerberosKeytab)
{
kerbSettings->keytab = _strdup(settings->KerberosKeytab);
if (!kerbSettings->keytab)
{
WLog_ERR(TAG, "unable to copy keytab name");
return FALSE;
}
}
if (settings->KerberosArmor)
{
kerbSettings->armorCache = _strdup(settings->KerberosArmor);
@ -1352,13 +1362,16 @@ static int nla_server_init(rdpNla* nla)
if (!nla_sspi_module_init(nla))
return -1;
if (!nla_setup_kerberos(nla))
return -1;
nla->status = nla_update_package_name(nla);
if (nla->status != SEC_E_OK)
return -1;
nla->status =
nla->table->AcquireCredentialsHandle(NULL, NLA_PKG_NAME, SECPKG_CRED_INBOUND, NULL, NULL,
nla->table->AcquireCredentialsHandle(NULL, NLA_PKG_NAME, SECPKG_CRED_INBOUND, NULL, nla->identity,
NULL, NULL, &nla->credentials, &nla->expiration);
if (nla->status != SEC_E_OK)

View File

@ -356,6 +356,7 @@ static const size_t string_list_indices[] = {
FreeRDP_KerberosArmor,
FreeRDP_KerberosCache,
FreeRDP_KerberosKdc,
FreeRDP_KerberosKeytab,
FreeRDP_KerberosLifeTime,
FreeRDP_KerberosRealm,
FreeRDP_KerberosRenewableLifeTime,

View File

@ -71,6 +71,8 @@ int main(int argc, char** argv)
"nla extended protocol security" },
{ "sam-file", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
"NTLM SAM file for NLA authentication" },
{ "keytab", COMMAND_LINE_VALUE_REQUIRED, "<file>", NULL, NULL, -1, NULL,
"Kerberos keytab file for NLA authentication" },
{ "gfx-progressive", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,
"Allow GFX progressive codec" },
{ "gfx-rfx", COMMAND_LINE_VALUE_BOOL, NULL, BoolValueTrue, NULL, -1, NULL,

View File

@ -392,6 +392,10 @@ int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** a
if (!freerdp_settings_set_bool(settings, FreeRDP_GfxAVC444, arg->Value ? TRUE : FALSE))
return COMMAND_LINE_ERROR;
}
CommandLineSwitchCase(arg, "keytab")
{
freerdp_settings_set_string(settings, FreeRDP_KerberosKeytab, arg->Value);
}
CommandLineSwitchDefault(arg)
{
}

View File

@ -601,6 +601,7 @@ typedef struct
typedef struct
{
char* keytab;
char* cache;
char* armorCache;
char* pkinitX509Anchors;

View File

@ -169,21 +169,23 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
krb5_error_code rv;
krb5_context ctx = NULL;
krb5_ccache ccache = NULL;
krb5_keytab keytab = NULL;
krb5_get_init_creds_opt* gic_opt = NULL;
krb5_deltat start_time = 0;
krb5_principal principal = NULL;
krb5_principal cache_principal = NULL;
krb5_creds creds;
BOOL is_unicode = FALSE;
char* domain = NULL;
char* username = NULL;
char* password = NULL;
char* ccache_name = NULL;
char keytab_name[PATH_MAX];
sspi_gss_OID_set_desc desired_mechs = { 1, SSPI_GSS_C_SPNEGO_KRB5 };
sspi_gss_key_value_element_desc ccache_setting = { "ccache", ccache_name };
sspi_gss_key_value_set_desc cred_store = { 1, &ccache_setting };
sspi_gss_key_value_element_desc cred_store_opts[2];
sspi_gss_key_value_set_desc cred_store = { 2, cred_store_opts };
sspi_gss_cred_id_t gss_creds = NULL;
OM_uint32 major, minor;
char* fallback_cc_name = "MEMORY:";
int cred_usage;
switch (fCredentialUse)
@ -218,19 +220,14 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
username = (char*)identity->User;
password = (char*)identity->Password;
}
if (!pszPrincipal)
pszPrincipal = username;
}
if ((rv = krb5_init_context(&ctx)))
goto cleanup;
/* If user provided a cache use it whether or not it's initialized with the right principal */
if (krb_settings && krb_settings->cache)
{
if ((rv = krb5_cc_set_default_name(ctx, krb_settings->cache)))
goto cleanup;
fallback_cc_name = krb_settings->cache;
}
if (domain)
{
CharUpperA(domain);
@ -239,36 +236,67 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
goto cleanup;
}
if (username)
if (pszPrincipal)
{
/* Find realm component if included and convert to uppercase */
char* p = username;
for (; *p != '@' && *p != 0; p++)
;
char *p = strchr(pszPrincipal, '@');
CharUpperA(p);
if ((rv = krb5_parse_name(ctx, username, &principal)))
if ((rv = krb5_parse_name(ctx, pszPrincipal, &principal)))
goto cleanup;
}
else
if (krb_settings && krb_settings->cache)
{
if ((rv = krb5_cc_resolve(ctx, krb_settings->cache, &ccache)))
goto cleanup;
/* Make sure the cache is initialized with the right principal */
if (principal)
{
if ((rv = krb5_cc_get_principal(ctx, ccache, &cache_principal)))
goto cleanup;
if (!krb5_principal_compare(ctx, principal, cache_principal))
if ((rv = krb5_cc_initialize(ctx, ccache, principal)))
goto cleanup;
}
}
else 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 ((rv = krb5_cc_resolve(ctx, "MEMORY:", &ccache)))
goto cleanup;
if ((rv = krb5_cc_initialize(ctx, ccache, principal)))
goto cleanup;
}
}
else if (fCredentialUse & SECPKG_CRED_OUTBOUND)
{
/* Use the default cache with it's default principal */
if ((rv = krb5_cc_default(ctx, &ccache)))
goto cleanup;
if ((rv = krb5_cc_get_principal(ctx, ccache, &principal)))
goto cleanup;
}
/* If the default (or user provided) cache is already initialized with the right principal use
it otherwise initialize a new cache in memory (or the user provided one) with our principal
*/
if (krb5_cc_cache_match(ctx, principal, &ccache) == KRB5_CC_NOTFOUND)
else
{
if ((rv = krb5_cc_resolve(ctx, fallback_cc_name, &ccache)))
goto cleanup;
if ((rv = krb5_cc_initialize(ctx, ccache, principal)))
if ((rv = krb5_cc_resolve(ctx, "MEMORY:", &ccache)))
goto cleanup;
}
if (krb_settings && krb_settings->keytab)
{
if ((rv = krb5_kt_resolve(ctx, krb_settings->keytab, &keytab)))
goto cleanup;
}
else
{
if ((rv = krb5_kt_default(ctx, &keytab)))
goto cleanup;
}
if ((rv = krb5_get_init_creds_opt_alloc(ctx, &gic_opt)))
goto cleanup;
@ -301,15 +329,22 @@ static SECURITY_STATUS SEC_ENTRY kerberos_AcquireCredentialsHandleA(
#endif
krb5_cc_get_full_name(ctx, ccache, &ccache_name);
ccache_setting.value = ccache_name;
krb5_kt_get_name(ctx, keytab, keytab_name, PATH_MAX);
cred_store_opts[0].key = "ccache";
cred_store_opts[0].value = ccache_name;
cred_store_opts[1].key = "keytab";
cred_store_opts[1].value = keytab_name;
/* Check if there are initial creds already in the cache there's no need to request new ones */
major = sspi_gss_acquire_cred_from(&minor, SSPI_GSS_C_NO_NAME, SSPI_GSS_C_INDEFINITE,
&desired_mechs, SSPI_GSS_C_INITIATE, &cred_store, &gss_creds,
&desired_mechs, cred_usage, &cred_store, &gss_creds,
NULL, NULL);
if (major != SSPI_GSS_S_NO_CRED)
goto cleanup;
gss_log_status_messages(major, minor);
if ((rv = krb5_get_init_creds_password(ctx, &creds, principal, password, krb5_prompter,
password, start_time, NULL, gic_opt)))
goto cleanup;
@ -343,7 +378,7 @@ cleanup:
if (password)
free(password);
}
if (ccache_setting.value)
if (ccache_name)
krb5_free_string(ctx, ccache_name);
if (principal)
krb5_free_principal(ctx, principal);
@ -556,6 +591,82 @@ static SECURITY_STATUS SEC_ENTRY kerberos_InitializeSecurityContextW(
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_GSSAPI
KRB_CONTEXT *context;
sspi_gss_cred_id_t creds;
sspi_gss_ctx_id_t gss_ctx = SSPI_GSS_C_NO_CONTEXT;
PSecBuffer input_buffer;
PSecBuffer output_buffer = NULL;
sspi_gss_buffer_desc input_token;
sspi_gss_buffer_desc output_token;
UINT32 major, minor;
UINT32 time_rec;
context = sspi_SecureHandleGetLowerPointer(phContext);
creds = 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 (context)
gss_ctx = context->gss_ctx;
input_token.length = input_buffer->cbBuffer;
input_token.value = input_buffer->pvBuffer;
major = sspi_gss_accept_sec_context(
&minor, &gss_ctx, creds, &input_token, SSPI_GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL, &output_token, pfContextAttr, &time_rec, NULL);
if (SSPI_GSS_ERROR(major))
{
gss_log_status_messages(major, minor);
return SEC_E_INTERNAL_ERROR;
}
if (output_token.length > 0)
{
if (output_buffer && output_buffer->cbBuffer >= output_token.length)
{
output_buffer->cbBuffer = output_token.length;
CopyMemory(output_buffer->pvBuffer, output_token.value, output_token.length);
sspi_gss_release_buffer(&minor, &output_token);
}
else
{
sspi_gss_release_buffer(&minor, &output_token);
return SEC_E_INVALID_TOKEN;
}
}
if (!context)
{
context = kerberos_ContextNew();
if (!context)
return SEC_E_INSUFFICIENT_MEMORY;
}
context->gss_ctx = gss_ctx;
sspi_SecureHandleSetUpperPointer(phNewContext, KERBEROS_SSP_NAME);
sspi_SecureHandleSetLowerPointer(phNewContext, context);
if (major & SSPI_GSS_S_CONTINUE_NEEDED)
return SEC_I_CONTINUE_NEEDED;
return SEC_E_OK;
#else
return SEC_E_UNSUPPORTED_FUNCTION;
#endif /* WITH_GSSAPI */
}
static SECURITY_STATUS SEC_ENTRY kerberos_DeleteSecurityContext(PCtxtHandle phContext)
{
KRB_CONTEXT* context;
@ -852,7 +963,7 @@ const SecurityFunctionTableA KERBEROS_SecurityFunctionTableA = {
kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
NULL, /* Reserved2 */
kerberos_InitializeSecurityContextA, /* InitializeSecurityContext */
NULL, /* AcceptSecurityContext */
kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
NULL, /* CompleteAuthToken */
kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
NULL, /* ApplyControlToken */
@ -883,7 +994,7 @@ const SecurityFunctionTableW KERBEROS_SecurityFunctionTableW = {
kerberos_FreeCredentialsHandle, /* FreeCredentialsHandle */
NULL, /* Reserved2 */
kerberos_InitializeSecurityContextW, /* InitializeSecurityContext */
NULL, /* AcceptSecurityContext */
kerberos_AcceptSecurityContext, /* AcceptSecurityContext */
NULL, /* CompleteAuthToken */
kerberos_DeleteSecurityContext, /* DeleteSecurityContext */
NULL, /* ApplyControlToken */

View File

@ -55,8 +55,8 @@ struct Mech_st
{
const sspi_gss_OID_desc* oid;
const SecPkg* pkg;
const UINT init_flags;
const UINT accept_flags;
const UINT flags;
const BOOL preferred;
};
typedef struct
@ -101,8 +101,8 @@ static const SecPkg SecPkgTable[] = {
};
static const Mech MechTable[] = {
{ &kerberos_OID, &SecPkgTable[0], 0, 0 },
{ &ntlm_OID, &SecPkgTable[1], 0, 0 },
{ &kerberos_OID, &SecPkgTable[0], 0, TRUE },
{ &ntlm_OID, &SecPkgTable[1], 0, FALSE },
};
static const int MECH_COUNT = sizeof(MechTable) / sizeof(Mech);
@ -671,7 +671,7 @@ static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextW(
CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
status = MechTable[i].pkg->table_w->InitializeSecurityContextW(
&creds[i].cred, NULL, pszTargetName, fContextReq | creds[i].mech->init_flags,
&creds[i].cred, NULL, pszTargetName, fContextReq | creds[i].mech->flags,
Reserved1, TargetDataRep, NULL, Reserved2, &init_context.sub_context, &mech_output,
pfContextAttr, ptsExpiry);
@ -802,7 +802,7 @@ static SECURITY_STATUS SEC_ENTRY negotiate_InitializeSecurityContextW(
CopyMemory(&output_token.mechToken, output_buffer, sizeof(SecBuffer));
status = context->mech->pkg->table_w->InitializeSecurityContextW(
sub_cred, sub_context, pszTargetName, fContextReq | context->mech->init_flags,
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);
@ -894,6 +894,7 @@ static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
BYTE *p, tag;
size_t bytes_remain, len;
sspi_gss_OID_desc oid = { 0 };
const Mech* first_mech = NULL;
if (!phCredential || !SecIsValidHandle(phCredential))
return SEC_E_NO_CREDENTIALS;
@ -971,6 +972,8 @@ static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
oid.length = len;
oid.elements = p;
p += len;
bytes_remain -= len;
init_context.mech = negotiate_GetMechByOID(oid);
@ -1009,7 +1012,9 @@ static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
return status;
init_context.mic = TRUE;
first_mech = init_context.mech;
init_context.mech = NULL;
output_token.mechToken.cbBuffer = 0;
}
while (!init_context.mech && bytes_remain > 0)
@ -1021,8 +1026,15 @@ static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
oid.length = len;
oid.elements = p;
p += len;
bytes_remain -= len;
init_context.mech = negotiate_GetMechByOID(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;
}
@ -1047,22 +1059,21 @@ static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
CopyMemory(init_context.mechTypes.pvBuffer, input_token.mechTypes.pvBuffer,
input_token.mechTypes.cbBuffer);
/* Check if the chosen mechanism is our most preferred; otherwise request mic */
for (int i = 0; i < MECH_COUNT; i++)
if (!context->mech->preferred)
{
if (context->mech != creds[i].mech)
{
output_token.negState = REQUEST_MIC;
context->mic = TRUE;
}
else
{
output_token.negState = ACCEPT_INCOMPLETE;
}
break;
output_token.negState = REQUEST_MIC;
context->mic = TRUE;
}
else
{
output_token.negState = ACCEPT_INCOMPLETE;
}
context->state = NEGOTIATE_STATE_NEGORESP;
if (status == SEC_E_OK)
context->state = NEGOTIATE_STATE_FINAL;
else
context->state = NEGOTIATE_STATE_NEGORESP;
output_token.supportedMech.length = oid.length;
output_token.supportedMech.elements = oid.elements;
WLog_DBG(TAG, "Accepted mechanism: %s", negotiate_mech_name(&output_token.supportedMech));
@ -1094,7 +1105,7 @@ static SECURITY_STATUS SEC_ENTRY negotiate_AcceptSecurityContext(
status = context->mech->pkg->table->AcceptSecurityContext(
sub_cred, &context->sub_context, &mech_input,
fContextReq | context->mech->accept_flags, TargetDataRep, &context->sub_context,
fContextReq | context->mech->flags, TargetDataRep, &context->sub_context,
pOutput, pfContextAttr, ptsTimeStamp);
if (IsSecurityStatusError(status))