kdc: support for GSS-API pre-authentication

Add support for GSS-API pre-authentication to the KDC, using a simplified
variation of draft-perez-krb-wg-gss-preauth-02 that encodes GSS-API context
tokens directly in PADATA, and uses FX-COOKIE for state management.

More information on the protocol and implementation may be found in
lib/gssapi/preauth/README.md.
This commit is contained in:
Luke Howard
2021-07-29 12:56:10 +10:00
parent 15c82996a4
commit 49f3f5bd99
40 changed files with 3132 additions and 90 deletions

View File

@@ -192,7 +192,9 @@ PADATA-TYPE ::= INTEGER {
KRB5-PADATA-PKINIT-KX(147), -- krb-wg-anon
KRB5-PADATA-PKU2U-NAME(148), -- zhu-pku2u
KRB5-PADATA-REQ-ENC-PA-REP(149), --
KRB5-PADATA-SUPPORTED-ETYPES(165) -- MS-KILE
KRB5-PADATA-SUPPORTED-ETYPES(165), -- MS-KILE
KRB5-PADATA-GSS(655) -- krb-wg-gss-preauth
}
AUTHDATA-TYPE ::= INTEGER {
@@ -221,8 +223,9 @@ AUTHDATA-TYPE ::= INTEGER {
KRB5-AUTHDATA-BEARER-TOKEN-JWT(581), -- JWT token
KRB5-AUTHDATA-BEARER-TOKEN-SAML(582), -- SAML token
KRB5-AUTHDATA-BEARER-TOKEN-OIDC(583), -- OIDC token
KRB5-AUTHDATA-CSR-AUTHORIZED(584) -- Proxy has authorized client
KRB5-AUTHDATA-CSR-AUTHORIZED(584), -- Proxy has authorized client
-- to requested exts in CSR
KRB5-AUTHDATA-GSS-COMPOSITE-NAME(655) -- gss_export_name_composite
}
-- checksumtypes
@@ -925,7 +928,6 @@ KERB-ARMOR-SERVICE-REPLY ::= SEQUENCE {
armor-key [1] EncryptionKey
}
END
-- etags -r '/\([A-Za-z][-A-Za-z0-9]*\).*::=/\1/' k5.asn1

View File

@@ -13,6 +13,7 @@ AM_CPPFLAGS += \
-I$(srcdir)/krb5 \
-I$(srcdir)/spnego \
-I$(srcdir)/sanon \
-I$(srcdir)/preauth \
$(INCLUDE_libintl)
lib_LTLIBRARIES = libgssapi.la test_negoex_mech.la
@@ -250,12 +251,17 @@ sanonsrc = \
sanon/release_name.c \
sanon/sanon-private.h
preauthsrc = \
preauth/pa_client.c \
preauth/pa_common.c
dist_libgssapi_la_SOURCES = \
$(krb5src) \
$(mechsrc) \
$(ntlmsrc) \
$(spnegosrc) \
$(sanonsrc)
$(sanonsrc) \
$(preauthsrc)
nodist_libgssapi_la_SOURCES = \
gkrb5_err.c \
@@ -289,6 +295,7 @@ noinst_HEADERS = \
$(srcdir)/ntlm/ntlm-private.h \
$(srcdir)/spnego/spnego-private.h \
$(srcdir)/sanon/sanon-private.h \
$(srcdir)/preauth/pa-private.h \
$(srcdir)/krb5/gsskrb5-private.h
nobase_include_HEADERS = \
@@ -296,6 +303,7 @@ nobase_include_HEADERS = \
gssapi/gssapi_krb5.h \
gssapi/gssapi_ntlm.h \
gssapi/gssapi_oid.h \
gssapi/gssapi_preauth.h \
gssapi/gssapi_spnego.h
gssapidir = $(includedir)/gssapi
@@ -319,7 +327,8 @@ BUILTHEADERS = \
$(srcdir)/krb5/gsskrb5-private.h \
$(srcdir)/spnego/spnego-private.h \
$(srcdir)/sanon/sanon-private.h \
$(srcdir)/ntlm/ntlm-private.h
$(srcdir)/ntlm/ntlm-private.h \
$(srcdir)/preauth/pa-private.h
$(libgssapi_la_OBJECTS): $(BUILTHEADERS)
$(test_context_OBJECTS): $(BUILTHEADERS)
@@ -356,6 +365,9 @@ $(srcdir)/spnego/spnego-private.h:
$(srcdir)/sanon/sanon-private.h:
cd $(srcdir) && perl ../../cf/make-proto.pl -q -P comment -p sanon/sanon-private.h $(sanonsrc) || rm -f sanon/sanon-private.h
$(srcdir)/preauth/pa-private.h:
cd $(srcdir) && perl ../../cf/make-proto.pl -q -P comment -p preauth/pa-private.h $(preauthsrc) || rm -f preauth/pa-private.h
TESTS = test_oid test_names test_cfx
# test_sequence

View File

@@ -261,6 +261,10 @@ sanonsrc = \
sanon/release_cred.c \
sanon/release_name.c
preauthsrc = \
preauth/pa_client.c \
preauth/pa_common.c
$(OBJ)\ntlm\ntlm-private.h: $(ntlmsrc)
$(PERL) ../../cf/make-proto.pl -q -P remove -p $@ $(ntlmsrc)
@@ -273,6 +277,9 @@ $(OBJ)\spnego\spnego-private.h: $(spnegosrc)
$(OBJ)\sanon\sanon-private.h: $(sanonsrc)
$(PERL) ../../cf/make-proto.pl -q -P remove -p $@ $(sanonsrc)
$(OBJ)\preauth\pa-private.h: $(preauthsrc)
$(PERL) ../../cf/make-proto.pl -q -P remove -p $@ $(preauthsrc)
gssapi_files = $(OBJ)\gssapi\asn1_gssapi_asn1.x
spnego_files = $(OBJ)\spnego\asn1_spnego_asn1.x
@@ -317,6 +324,7 @@ INCFILES= \
$(OBJ)\ntlm\ntlm-private.h \
$(OBJ)\spnego\spnego-private.h \
$(OBJ)\sanon\sanon-private.h \
$(OBJ)\preauth\pa-private.h \
$(OBJ)\krb5\gsskrb5-private.h \
$(OBJ)\gkrb5_err.h \
$(OBJ)\negoex_err.h \
@@ -533,6 +541,8 @@ libgssapi_OBJs = \
$(OBJ)\sanon/process_context_token.obj \
$(OBJ)\sanon/release_cred.obj \
$(OBJ)\sanon/release_name.obj \
$(OBJ)\preauth/pa_client.obj \
$(OBJ)\preauth/pa_common.obj \
$(OBJ)\gkrb5_err.obj \
$(OBJ)\negoex_err.obj \
$(spnego_files:.x=.obj) \
@@ -570,6 +580,12 @@ GCOPTS=-I$(SRCDIR) -I$(OBJ) -Igssapi -DBUILD_GSSAPI_LIB
{sanon}.c{$(OBJ)\sanon}.obj::
$(C2OBJ_NP) -Fo$(OBJ)\sanon\ -Fd$(OBJ)\sanon\ -I$(OBJ)\sanon -I$(OBJ) -I$(OBJ)\krb5 -I$(OBJ)\gssapi -Ikrb5 -Imech -Igssapi $(GCOPTS) -DASN1_LIB
{$(OBJ)\preauth}.c{$(OBJ)\preauth}.obj::
$(C2OBJ_NP) -Fo$(OBJ)\preauth\ -Fd$(OBJ)\pa\ -I$(OBJ)\pa -I$(OBJ) -I$(OBJ)\krb5 -I$(OBJ)\gssapi -Ikrb5 -Imech -Igssapi $(GCOPTS)
{preauth}.c{$(OBJ)\preauth}.obj::
$(C2OBJ_NP) -Fo$(OBJ)\preauth\ -Fd$(OBJ)\pa\ -I$(OBJ)\pa -I$(OBJ) -I$(OBJ)\krb5 -I$(OBJ)\gssapi -Ikrb5 -Imech -Igssapi $(GCOPTS) -DASN1_LIB
{$(OBJ)\gssapi}.c{$(OBJ)\gssapi}.obj::
$(C2OBJ_NP) -Fo$(OBJ)\gssapi\ -Fd$(OBJ)\gssapi\ -I$(OBJ)\gssapi $(GCOPTS)
@@ -660,6 +676,9 @@ mkdirs-gss:
!if !exist($(OBJ)\gssapi)
$(MKDIR) $(OBJ)\gssapi
!endif
!if !exist($(OBJ)\preauth)
$(MKDIR) $(OBJ)\preauth
!endif
clean::
-$(RM) $(OBJ)\ntlm\*.*
@@ -668,6 +687,7 @@ clean::
-$(RM) $(OBJ)\mech\*.*
-$(RM) $(OBJ)\sanon\*.*
-$(RM) $(OBJ)\gssapi\*.*
-$(RM) $(OBJ)\preauth\*.*
all-tools:: $(BINDIR)\gsstool.exe

View File

@@ -1240,6 +1240,9 @@ GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
gss_destroy_cred(OM_uint32 *minor_status,
gss_cred_id_t *cred_handle);
GSSAPI_LIB_FUNCTION uintptr_t GSSAPI_CALLCONV
gss_get_instance(const char *libname);
GSSAPI_CPP_END
#if defined(__APPLE__) && (defined(__ppc__) || defined(__ppc64__) || defined(__i386__) || defined(__x86_64__))

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/* $Id$ */
#ifndef GSSAPI_PREAUTH_H_
#define GSSAPI_PREAUTH_H_
#include <gssapi.h>
#include <krb5.h>
GSSAPI_CPP_START
#if !defined(__GNUC__) && !defined(__attribute__)
#define __attribute__(x)
#endif
GSSAPI_LIB_FUNCTION krb5_error_code GSSAPI_LIB_CALL
krb5_gss_set_init_creds(krb5_context context,
krb5_init_creds_context ctx,
gss_const_cred_id_t gss_cred,
gss_const_OID gss_mech);
#endif /* GSSAPI_PREAUTH_H_ */

View File

@@ -40,6 +40,7 @@ EXPORTS
gss_export_name
gss_export_name_composite
gss_export_sec_context
gss_get_instance
gss_get_mic
gss_get_neg_mechs
gss_get_name_attribute
@@ -122,6 +123,14 @@ EXPORTS
gsskrb5_set_send_to_kdc
gsskrb5_set_time_offset
krb5_gss_register_acceptor_identity
krb5_gss_set_init_creds
_krb5_gss_data_to_buffer
_krb5_gss_buffer_to_data
_krb5_gss_map_error
_krb5_gss_pa_parse_name
_krb5_gss_pa_unparse_name
_krb5_gss_pa_derive_key
; _gsskrb5cfx_ are really internal symbols, but export
; then now to make testing easier.

View File

@@ -563,3 +563,16 @@ gss_oid_to_name(gss_const_OID oid)
return NULL;
}
GSSAPI_LIB_FUNCTION uintptr_t GSSAPI_CALLCONV
gss_get_instance(const char *libname)
{
static const char *instance = "libgssapi";
if (strcmp(libname, "gssapi") == 0)
return (uintptr_t)instance;
else if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
return 0;
}

View File

@@ -0,0 +1,119 @@
# GSS-API Pre-authentication in Heimdal
GSS-API pre-authentication in Heimdal is based on
[draft-perez-krb-wg-gss-preauth](https://datatracker.ietf.org/doc/html/draft-perez-krb-wg-gss-preauth)
but with some simplifications to the protocol.
The following text assumes the reader is familiar with the draft.
## Protocol changes
- The pre-authentication type KRB5-PADATA-GSS is 655
- Pre-authentication data is the raw context token rather than being
wrapped in another ASN.1 type
- Acceptor GSS state is stored in FX-COOKIE rather than alongside the
context token
- Key derivation salt is the string "KRB-GSS\0" || nonce
## Client side
Because libkrb5 cannot have a recursive dependency on libgssapi, it instead
exports the function `_krb5_init_creds_init_gss()` which allows libgssapi to
register a set of function pointers for:
- Generating context tokens
- Finalizing a context (inquiring the initiator name and reply key)
- Releasing context and credential handles
This is a private API.
This architecture also means that the libkrb5 implementation could be used with
an alternative GSS-API implementation such as SSPI, without too much work. The
bulk of the pre-authentication logic remains in libkrb5, however, in
[`init_creds_pw.c`](../../krb5/init_creds_pw.c).
libgssapi itself exports `krb5_gss_set_init_creds()`, which is the public
interface for GSS-API pre-authentication.
`krb5_gss_set_init_creds()` enables GSS-API pre-authentication on an initial
credentials context, taking a GSS-API credential handle and mechanism. Both are
optional; defaults will be used if absent. These two parameters are exposed as
the `--gss-name` and `--gss-mech` options to `kinit` (see
[kinit(1)](../../../kuser/kinit.1) for further details). `kinit` supports
acquiring anonymous, keytab- and password-based GSS-API credentials using the
same arguments as regular Kerberos.
The selected GSS-API mechanism must support mutual authentication (ie.
authenticating the KDC) as it replaces the AS-REP reply key, However, if FAST
was used, and we know that the KDC was verified, then this requirement is
removed.
If the client does not know its initiator name, it can specify the last
arugment to `kinit` as `@REALM`, and the initiator name will be filled in when
the authentication is complete. (The realm is required to select a KDC.)
## KDC side
The KDC implements the acceptor side of the GSS-API authentication exchange.
The selected GSS-API mechanism must allow `gss_export_sec_context()` to be
called by the acceptor before the context is established, if it needs more than
a single round trip of token exchanges.
### Configuration
Configuration directives live in the [kdc] section of
[krb5.conf(5)](../../krb5/krb5.conf.5).
The `enable_gss_preauth` krb5.conf option must be set in order to enable
GSS-API pre-authentication in the KDC. When authenticating federated principals
which may not exist in the KDC, the `synthetic_clients` option should also be
set.
The `gss_mechanisms_allowed` option can be used to limit the set of GSS-API
mechanisms which are allowed to perform pre-authentication. Mechanisms are
specified as dot-separated OIDs or by a short name, such as `sanon-x25519`.
The `enable_gss_auth_data` option will include a composite GSS name in the
authorization data of returned tickets.
### Authorization
The default is that the initiator is permitted to authenticate to the Kerberos
principal that directly corresponds to it. The correspondence is governed as
follows: if the authenticating mechanism is in the list of mechanisms in the
`gss_cross_realm_mechanisms_allowed` configuration option, then the principal
is mapped identically: an initiator named `lukeh@AAA.PADL.COM` will be mapped
to the Kerberos principal `lukeh@AAA.PADL.COM`.
If the authenticating mechanism is not in this list, then the initiator will be
mapped to an enterprise principal in the service realm. For example,
`lukeh@AAA.PADL.COM` might be mapped to `lukeh\@AAA.PADL.COM@PADL.COM`
(enterprise principal name type);
This mapping has no effect for principals that exist in the HDB, because
enterprise principal names are always looked up by their first component (as if
they were an ordinary principal name). This logic is instead useful when
synthetic principals are enabled as we wish to avoid issuing tickets with a
client name in a foreign Kerberos realm, as that would conflate GSS-API
"realms" with Kerberos realms.
A custom authorization plugin installed in `$prefix/lib/plugin/kdc` will
replace this mapping and authorization logic. The plugin interface is defined in
[`gss_preauth_authorizer_plugin.h`](../../../kdc/gss_preauth_authorizer_plugin.h)).
### Anonymous authentication
A further note on the interaction of anonymous GSS-API authentication and
pre-authentication. Initiator contexts that set `GSS_C_ANON_FLAG` and a
`GSS_C_NT_ANONYMOUS` name are mapped to the unauthenticated anonymous Kerberos
principal, `WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS`. However, the local
`WELLKNOWN/ANONYMOUS` HDB entry is used to perform any authorization decisions
(as it would be for anonymous PKINIT). The AP-REP will contain the well-known
anonymous realm.
If `GSS_C_NT_ANONYMOUS` was set but a different name type was returned, then
the initiator is treated as authenticated anonymous, and the client realm will
be present in the AP-REP.
The `request-anonymous` AP-REQ flag must also be set for GSS-API anonymous
authentication to succeed.

View File

@@ -0,0 +1,251 @@
/*
* Copyright (c) 2021, PADL Software Pty Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of PADL Software nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "krb5_locl.h"
#include "mech_locl.h"
#include <gssapi/gssapi_preauth.h>
#include <preauth/pa-private.h>
static krb5_error_code
pa_gss_acquire_initiator_cred(krb5_context context,
krb5_gss_init_ctx gssic,
const krb5_creds *kcred,
gss_cred_id_t *cred)
{
krb5_error_code ret;
OM_uint32 major, minor;
gss_const_OID mech;
gss_OID_set_desc mechs;
gss_name_t initiator_name = GSS_C_NO_NAME;
OM_uint32 time_req;
krb5_timestamp now;
*cred = GSS_C_NO_CREDENTIAL;
mech = _krb5_init_creds_get_gss_mechanism(context, gssic);
mechs.count = 1;
mechs.elements = (gss_OID)mech;
ret = _krb5_gss_pa_unparse_name(context, kcred->client, &initiator_name);
if (ret)
return ret;
krb5_timeofday(context, &now);
if (kcred->times.endtime && kcred->times.endtime > now)
time_req = kcred->times.endtime - now;
else
time_req = GSS_C_INDEFINITE;
major = gss_acquire_cred(&minor, initiator_name, time_req, &mechs,
GSS_C_INITIATE, cred, NULL, NULL);
ret = _krb5_gss_map_error(major, minor);
gss_release_name(&major, &initiator_name);
return ret;
}
static krb5_error_code KRB5_LIB_CALL
pa_gss_step(krb5_context context,
krb5_gss_init_ctx gssic,
const krb5_creds *kcred,
KDCOptions flags,
krb5_data *enc_as_req,
krb5_data *in,
krb5_data *out)
{
krb5_error_code ret;
OM_uint32 major, minor;
gss_cred_id_t cred;
gss_ctx_id_t ctx;
gss_name_t target_name = GSS_C_NO_NAME;
OM_uint32 req_flags = GSS_C_MUTUAL_FLAG;
OM_uint32 ret_flags;
struct gss_channel_bindings_struct cb = { 0 };
gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER;
krb5_data_zero(out);
if (flags.request_anonymous)
req_flags |= GSS_C_ANON_FLAG;
cred = (gss_cred_id_t)_krb5_init_creds_get_gss_cred(context, gssic);
if (cred == GSS_C_NO_CREDENTIAL) {
ret = pa_gss_acquire_initiator_cred(context, gssic, kcred, &cred);
if (ret)
goto out;
_krb5_init_creds_set_gss_cred(context, gssic, cred);
}
ctx = (gss_ctx_id_t)_krb5_init_creds_get_gss_context(context, gssic);
ret = _krb5_gss_pa_unparse_name(context, kcred->server, &target_name);
if (ret)
goto out;
_krb5_gss_data_to_buffer(enc_as_req, &cb.application_data);
_krb5_gss_data_to_buffer(in, &input_token);
major = gss_init_sec_context(&minor,
cred,
&ctx,
target_name,
(gss_OID)_krb5_init_creds_get_gss_mechanism(context, gssic),
req_flags,
GSS_C_INDEFINITE,
&cb,
&input_token,
NULL,
&output_token,
&ret_flags,
NULL);
_krb5_init_creds_set_gss_context(context, gssic, ctx);
_krb5_gss_buffer_to_data(&output_token, out);
_mg_buffer_zero(&output_token);
if (major == GSS_S_COMPLETE) {
if ((ret_flags & GSS_C_MUTUAL_FLAG) == 0)
ret = KRB5_MUTUAL_FAILED;
else if ((ret_flags & req_flags) != req_flags)
ret = KRB5KDC_ERR_BADOPTION;
else
ret = 0;
} else
ret = _krb5_gss_map_error(major, minor);
out:
gss_release_name(&minor, &target_name);
gss_release_buffer(&minor, &output_token);
return ret;
}
static krb5_error_code KRB5_LIB_CALL
pa_gss_finish(krb5_context context,
krb5_gss_init_ctx gssic,
const krb5_creds *kcred,
krb5int32 nonce,
krb5_enctype enctype,
krb5_principal *client_p,
krb5_keyblock **reply_key_p)
{
krb5_error_code ret;
krb5_principal client = NULL;
krb5_keyblock *reply_key = NULL;
OM_uint32 major, minor;
gss_name_t initiator_name = GSS_C_NO_NAME;
gss_ctx_id_t ctx = (gss_ctx_id_t)_krb5_init_creds_get_gss_context(context, gssic);
*client_p = NULL;
*reply_key_p = NULL;
major = gss_inquire_context(&minor,
ctx,
&initiator_name,
NULL, /* target_name */
NULL, /* lifetime_req */
NULL, /* mech_type */
NULL, /* ctx_flags */
NULL, /* locally_initiated */
NULL); /* open */
if (GSS_ERROR(major))
return _krb5_gss_map_error(major, minor);
ret = _krb5_gss_pa_parse_name(context, initiator_name, 0, &client);
if (ret)
goto out;
ret = _krb5_gss_pa_derive_key(context, ctx, nonce, enctype, &reply_key);
if (ret)
goto out;
*client_p = client;
client = NULL;
*reply_key_p = reply_key;
reply_key = NULL;
out:
krb5_free_principal(context, client);
if (reply_key)
krb5_free_keyblock(context, reply_key);
gss_release_name(&minor, &initiator_name);
return ret;
}
static void KRB5_LIB_CALL
pa_gss_delete_sec_context(krb5_context context,
krb5_gss_init_ctx gssic,
gss_ctx_id_t ctx)
{
OM_uint32 minor;
gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER);
}
static void KRB5_LIB_CALL
pa_gss_release_cred(krb5_context context,
krb5_gss_init_ctx gssic,
gss_cred_id_t cred)
{
OM_uint32 minor;
gss_release_cred(&minor, &cred);
}
GSSAPI_LIB_FUNCTION krb5_error_code GSSAPI_LIB_CALL
krb5_gss_set_init_creds(krb5_context context,
krb5_init_creds_context ctx,
gss_const_cred_id_t gss_cred,
gss_const_OID gss_mech)
{
return _krb5_init_creds_init_gss(context,ctx,
pa_gss_step,
pa_gss_finish,
pa_gss_release_cred,
pa_gss_delete_sec_context,
gss_cred,
gss_mech,
0);
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2021, PADL Software Pty Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of PADL Software nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "krb5_locl.h"
#include "mech_locl.h"
#include <gssapi/gssapi_preauth.h>
#include <preauth/pa-private.h>
krb5_error_code
_krb5_gss_map_error(OM_uint32 major, OM_uint32 minor)
{
krb5_error_code ret;
if (minor != 0)
return (krb5_error_code)minor;
switch (major) {
case GSS_S_COMPLETE:
ret = 0;
break;
case GSS_S_CONTINUE_NEEDED:
ret = KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
break;
case GSS_S_BAD_NAME:
case GSS_S_BAD_NAMETYPE:
ret = KRB5_PRINC_NOMATCH;
break;
case GSS_S_BAD_MIC:
ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
break;
case GSS_S_FAILURE:
default:
ret = KRB5KDC_ERR_PREAUTH_FAILED;
break;
}
return ret;
}
krb5_error_code
_krb5_gss_pa_derive_key(krb5_context context,
gss_ctx_id_t ctx,
krb5int32 nonce,
krb5_enctype enctype,
krb5_keyblock **keyblock)
{
krb5_error_code ret;
u_char saltdata[12] = "KRB-GSS";
krb5_keyblock kdkey;
size_t keysize;
OM_uint32 major, minor;
gss_buffer_desc salt, dkey = GSS_C_EMPTY_BUFFER;
*keyblock = NULL;
ret = krb5_enctype_keysize(context, enctype, &keysize);
if (ret)
return ret;
_gss_mg_encode_le_uint32(nonce, &saltdata[8]);
salt.value = saltdata;
salt.length = sizeof(saltdata);
major = gss_pseudo_random(&minor, ctx, GSS_C_PRF_KEY_FULL,
&salt, keysize, &dkey);
if (GSS_ERROR(major))
return KRB5_PREAUTH_NO_KEY;
kdkey.keytype = enctype;
kdkey.keyvalue.data = dkey.value;
kdkey.keyvalue.length = dkey.length;
ret = krb5_copy_keyblock(context, &kdkey, keyblock);
_gss_secure_release_buffer(&minor, &dkey);
return ret;
}
krb5_error_code
_krb5_gss_pa_unparse_name(krb5_context context,
krb5_const_principal principal,
gss_name_t *namep)
{
krb5_error_code ret;
char *name = NULL;
OM_uint32 major, minor;
gss_buffer_desc name_buf;
gss_OID name_type;
*namep = GSS_C_NO_NAME;
if (principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
if (principal->name.name_string.len != 1)
return EINVAL;
name = principal->name.name_string.val[0];
} else {
ret = krb5_unparse_name(context, principal, &name);
if (ret)
return ret;
}
name_buf.length = strlen(name);
name_buf.value = name;
if (principal->name.name_type == KRB5_NT_PRINCIPAL ||
principal->name.name_type == KRB5_NT_ENTERPRISE_PRINCIPAL)
name_type = GSS_C_NT_USER_NAME;
else
name_type = GSS_KRB5_NT_PRINCIPAL_NAME;
major = gss_import_name(&minor, &name_buf, name_type, namep);
if (name != principal->name.name_string.val[0])
krb5_xfree(name);
return _krb5_gss_map_error(major, minor);
}
krb5_error_code
_krb5_gss_pa_parse_name(krb5_context context,
gss_const_name_t name,
int flags,
krb5_principal *principal)
{
krb5_error_code ret;
char *displayed_name0;
OM_uint32 major, minor;
gss_OID name_type = GSS_C_NO_OID;
gss_buffer_desc displayed_name = GSS_C_EMPTY_BUFFER;
major = gss_display_name(&minor, name, &displayed_name, &name_type);
if (GSS_ERROR(major))
return _krb5_gss_map_error(major, minor);
if (gss_oid_equal(name_type, GSS_C_NT_ANONYMOUS)) {
ret = krb5_make_principal(context, principal, KRB5_ANON_REALM,
KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME, NULL);
if (ret == 0)
(*principal)->name.name_type = KRB5_NT_WELLKNOWN;
} else {
displayed_name0 = malloc(displayed_name.length + 1);
if (displayed_name0 == NULL)
return krb5_enomem(context);
memcpy(displayed_name0, displayed_name.value, displayed_name.length);
displayed_name0[displayed_name.length] = '\0';
ret = krb5_parse_name_flags(context, displayed_name0, flags, principal);
gss_release_buffer(&minor, &displayed_name);
free(displayed_name0);
}
gss_release_buffer(&minor, &displayed_name);
return ret;
}
void
_krb5_gss_data_to_buffer(const krb5_data *data, gss_buffer_t buffer)
{
if (data) {
buffer->length = data->length;
buffer->value = data->data;
} else {
_mg_buffer_zero(buffer);
}
}
void
_krb5_gss_buffer_to_data(gss_const_buffer_t buffer, krb5_data *data)
{
if (buffer) {
data->length = buffer->length;
data->data = buffer->value;
} else {
krb5_data_zero(data);
}
}

View File

@@ -43,6 +43,7 @@ HEIMDAL_GSS_2.0 {
gss_export_name;
gss_export_name_composite;
gss_export_sec_context;
gss_get_instance;
gss_get_mic;
gss_get_neg_mechs;
gss_get_name_attribute;
@@ -116,6 +117,7 @@ HEIMDAL_GSS_2.0 {
gsskrb5_set_send_to_kdc;
gsskrb5_set_time_offset;
krb5_gss_register_acceptor_identity;
krb5_gss_set_init_creds;
gss_display_mech_attr;
gss_inquire_attrs_for_mech;
gss_indicate_mechs_by_attrs;
@@ -133,6 +135,13 @@ HEIMDAL_GSS_2.0 {
_gsskrb5cfx_wrap_length_cfx;
_gssapi_wrap_size_cfx;
_krb5_gss_data_to_buffer;
_krb5_gss_buffer_to_data;
_krb5_gss_map_error;
_krb5_gss_pa_parse_name;
_krb5_gss_pa_unparse_name;
_krb5_gss_pa_derive_key;
__gss_krb5_copy_ccache_x_oid_desc;
__gss_krb5_get_tkt_flags_x_oid_desc;
__gss_krb5_extract_authz_data_from_sec_context_x_oid_desc;

View File

@@ -38,6 +38,21 @@
#include <heim-ipc.h>
#endif /* WIN32 */
struct krb5_gss_init_ctx_data {
unsigned int flags;
#define GSSIC_FLAG_RELEASE_CRED (1 << 0)
#define GSSIC_FLAG_CONTEXT_OPEN (1 << 1)
krb5_gssic_step step;
krb5_gssic_finish finish;
krb5_gssic_release_cred release_cred;
krb5_gssic_delete_sec_context delete_sec_context;
const struct gss_OID_desc_struct *mech;
struct gss_cred_id_t_desc_struct *cred;
struct gss_ctx_id_t_desc_struct *ctx;
};
typedef struct krb5_get_init_creds_ctx {
KDCOptions flags;
krb5_creds cred;
@@ -62,6 +77,7 @@ typedef struct krb5_get_init_creds_ctx {
krb5_get_init_creds_tristate req_pac;
krb5_pk_init_ctx pk_init_ctx;
krb5_gss_init_ctx gss_init_ctx;
int ic_flags;
struct {
@@ -106,6 +122,7 @@ typedef struct krb5_get_init_creds_ctx {
krb5_keyblock *strengthen_key;
krb5_get_init_creds_opt *anon_pkinit_opt;
krb5_init_creds_context anon_pkinit_ctx;
krb5_data cookie;
} fast_state;
} krb5_get_init_creds_ctx;
@@ -155,6 +172,15 @@ default_s2k_func(krb5_context context, krb5_enctype type,
return ret;
}
static void
free_gss_init_ctx(krb5_context context, krb5_gss_init_ctx gssic)
{
if (gssic->flags & GSSIC_FLAG_RELEASE_CRED)
gssic->release_cred(context, gssic, gssic->cred);
gssic->delete_sec_context(context, gssic, gssic->ctx);
free(gssic);
}
static void
free_init_creds_ctx(krb5_context context, krb5_init_creds_context ctx)
{
@@ -172,6 +198,8 @@ free_init_creds_ctx(krb5_context context, krb5_init_creds_context ctx)
memset_s(ctx->password, len, 0, len);
free(ctx->password);
}
if (ctx->gss_init_ctx)
free_gss_init_ctx(context, ctx->gss_init_ctx);
/*
* FAST state (we don't close the armor_ccache because we might have
* to destroy it, and how would we know? also, the caller should
@@ -183,6 +211,7 @@ free_init_creds_ctx(krb5_context context, krb5_init_creds_context ctx)
krb5_crypto_destroy(context, ctx->fast_state.armor_crypto);
if (ctx->fast_state.strengthen_key)
krb5_free_keyblock(context, ctx->fast_state.strengthen_key);
krb5_data_free(&ctx->fast_state.cookie);
krb5_free_keyblock_contents(context, &ctx->fast_state.armor_key);
if (ctx->fast_state.flags & KRB5_FAST_ANON_PKINIT_ARMOR)
krb5_cc_destroy(context, ctx->fast_state.armor_ccache);
@@ -1169,6 +1198,105 @@ pa_data_to_md_pkinit(krb5_context context,
#endif
}
static krb5_error_code
gss_pa_step(krb5_context context,
const krb5_creds *creds,
const krb5_get_init_creds_ctx *ctx,
const METHOD_DATA *md,
krb5_data *output_token)
{
krb5_error_code ret;
size_t len = 0;
krb5_gss_init_ctx gssic = ctx->gss_init_ctx;
krb5_data req_body;
PA_DATA *pa;
krb5_data *input_token;
krb5_data_zero(&req_body);
krb5_data_zero(output_token);
pa = find_pa_data(md, KRB5_PADATA_GSS);
input_token = pa ? &pa->padata_value : NULL;
if (input_token == NULL || input_token->length == 0) {
if (gssic->ctx == NULL)
ret = 0; /* initial context token */
else {
krb5_set_error_message(context, KRB5_PREAUTH_BAD_TYPE,
"Missing GSS preauthentication data from KDC");
ret = KRB5_PREAUTH_BAD_TYPE;
}
if (ret)
goto out;
}
if (input_token && input_token->length &&
(gssic->flags & GSSIC_FLAG_CONTEXT_OPEN)) {
krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP,
"Already completed GSS authentication, looping");
ret = KRB5_GET_IN_TKT_LOOP;
goto out;
}
ASN1_MALLOC_ENCODE(KDC_REQ_BODY, req_body.data, req_body.length,
&ctx->as_req.req_body, &len, ret);
if (ret)
goto out;
heim_assert(req_body.length == len, "ASN.1 internal error");
ret = gssic->step(context, gssic, creds, ctx->flags, &req_body,
input_token, output_token);
/*
* If FAST authenticated the KDC (which will be the case unless anonymous
* PKINIT was used without KDC certificate validation) then we can relax
* the mutual authentication requirement.
*/
if (ret == KRB5_MUTUAL_FAILED &&
(ctx->fast_state.flags & KRB5_FAST_EXPECTED) &&
(ctx->fast_state.flags & KRB5_FAST_KDC_VERIFIED))
ret = 0;
out:
krb5_data_free(&req_body);
return ret;
}
static krb5_error_code
pa_data_to_md_gss(krb5_context context,
const AS_REQ *a,
const krb5_creds *creds,
krb5_get_init_creds_ctx *ctx,
METHOD_DATA *in_md,
METHOD_DATA *out_md)
{
krb5_error_code ret;
krb5_data output_token;
krb5_gss_init_ctx gssic = ctx->gss_init_ctx;
heim_assert(gssic != NULL, "invalid context passed to pa_data_to_md_gss");
ret = gss_pa_step(context, creds, ctx, in_md, &output_token);
if (ret == 0)
gssic->flags |= GSSIC_FLAG_CONTEXT_OPEN;
else if (ret == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
ret = 0;
if (output_token.length) {
ret = krb5_padata_add(context, out_md, KRB5_PADATA_GSS,
output_token.data, output_token.length);
if (ret) {
krb5_data_free(&output_token);
return ret;
}
krb5_data_zero(&output_token);
} else
krb5_data_free(&output_token);
return ret;
}
static krb5_error_code
pa_data_add_pac_request(krb5_context context,
krb5_get_init_creds_ctx *ctx,
@@ -1261,6 +1389,12 @@ process_pa_data_to_md(krb5_context context,
else
ctx->used_pa_types |= USED_PKINIT;
} else if (ctx->gss_init_ctx) {
_krb5_debug(context, 5, "krb5_get_init_creds: preparing GSS credentials");
ret = pa_data_to_md_gss(context, a, creds, ctx, in_md, *out_md);
if (ret)
return ret;
} else if (in_md->len != 0) {
struct pa_info_data *paid, *ppaid;
unsigned flag;
@@ -1317,6 +1451,61 @@ process_pa_data_to_md(krb5_context context,
return 0;
}
static krb5_error_code
gss_pa_data_to_key(krb5_context context,
krb5_get_init_creds_ctx *ctx,
krb5_creds *creds,
krb5_enctype etype,
METHOD_DATA *md,
krb5_keyblock **key)
{
krb5_error_code ret;
krb5_principal cname = NULL;
krb5_gss_init_ctx gssic = ctx->gss_init_ctx;
/*
* Finish the GSS authentication and extract the reply key. If the GSS mechanism
* required an odd number of legs, the context may already be open, but we still
* needed to wait for a reply from the KDC.
*/
if ((gssic->flags & GSSIC_FLAG_CONTEXT_OPEN) == 0) {
krb5_data output_token;
ret = gss_pa_step(context, creds, ctx, md, &output_token);
if (ret == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED ||
output_token.length) {
krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED,
"KDC sent AS-REP before GSS preauthentication completed");
ret = KRB5_PREAUTH_FAILED;
}
krb5_data_free(&output_token);
if (ret)
goto out;
gssic->flags |= GSSIC_FLAG_CONTEXT_OPEN;
}
ret = gssic->finish(context, gssic, creds, ctx->nonce, etype, &cname, key);
if (ret)
goto out;
if (krb5_principal_is_federated(context, creds->client)) {
/* replace the wellknown federated name with the initiator name */
krb5_free_principal(context, creds->client);
creds->client = cname;
cname = NULL;
/* allow the KDC to canonicalize the name */
if (ctx->flags.canonicalize)
ctx->ic_flags |= KRB5_INIT_CREDS_NO_C_CANON_CHECK;
}
out:
krb5_free_principal(context, cname);
return ret;
}
static krb5_error_code
process_pa_data_to_key(krb5_context context,
krb5_get_init_creds_ctx *ctx,
@@ -1353,18 +1542,9 @@ process_pa_data_to_key(krb5_context context,
pa = NULL;
if (rep->padata) {
int idx = 0;
pa = krb5_find_padata(rep->padata->val,
rep->padata->len,
KRB5_PADATA_PK_AS_REP,
&idx);
if (pa == NULL) {
idx = 0;
pa = krb5_find_padata(rep->padata->val,
rep->padata->len,
KRB5_PADATA_PK_AS_REP_19,
&idx);
}
pa = find_pa_data(rep->padata, KRB5_PADATA_PK_AS_REP);
if (pa == NULL)
pa = find_pa_data(rep->padata, KRB5_PADATA_PK_AS_REP_19);
}
if (pa && ctx->pk_init_ctx) {
#ifdef PKINIT
@@ -1383,6 +1563,9 @@ process_pa_data_to_key(krb5_context context,
ret = EINVAL;
krb5_set_error_message(context, ret, N_("no support for PKINIT compiled in", ""));
#endif
} else if (ctx->gss_init_ctx) {
_krb5_debug(context, 5, "krb5_get_init_creds: using GSS for reply key");
ret = gss_pa_data_to_key(context, ctx, creds, etype, rep->padata, key);
} else if (ctx->keyseed) {
_krb5_debug(context, 5, "krb5_get_init_creds: using keyproc");
ret = pa_data_to_key_plain(context, creds->client, ctx,
@@ -1765,15 +1948,12 @@ fast_unwrap_as_rep(krb5_context context, int32_t nonce,
KrbFastResponse fastrep;
krb5_error_code ret;
PA_DATA *pa = NULL;
int idx = 0;
if (state->armor_crypto == NULL || rep->padata == NULL)
return check_fast(context, state);
/* find PA_FX_FAST_REPLY */
pa = krb5_find_padata(rep->padata->val, rep->padata->len,
KRB5_PADATA_FX_FAST, &idx);
pa = find_pa_data(rep->padata, KRB5_PADATA_FX_FAST);
if (pa == NULL)
return check_fast(context, state);
@@ -1863,17 +2043,105 @@ fast_unwrap_as_rep(krb5_context context, int32_t nonce,
out:
free_PA_FX_FAST_REPLY(&fxfastrep);
free_KrbFastResponse(&fastrep);
return ret;
}
static krb5_error_code
fast_unwrap_error(krb5_context context, struct fast_state *state, KRB_ERROR *error)
fast_unwrap_error(krb5_context context,
int32_t nonce,
struct fast_state *state,
KRB_ERROR *error,
METHOD_DATA *error_method)
{
if (state->armor_crypto == NULL)
return check_fast(context, state);
METHOD_DATA md;
PA_FX_FAST_REPLY fxfastrep;
KrbFastResponse fastrep;
krb5_error_code ret;
PA_DATA *pa;
KRB_ERROR fast_error;
return 0;
memset(&md, 0, sizeof(md));
memset(&fxfastrep, 0, sizeof(fxfastrep));
memset(&fastrep, 0, sizeof(fastrep));
memset(&fast_error, 0, sizeof(fast_error));
if (error->e_data) {
ret = decode_METHOD_DATA(error->e_data->data,
error->e_data->length, &md, NULL);
if (ret) {
krb5_set_error_message(context, ret,
N_("Failed to decode METHOD-DATA", ""));
return ret;
}
}
if (state->armor_crypto == NULL ||
(pa = find_pa_data(&md, KRB5_PADATA_FX_FAST)) == NULL) {
free_METHOD_DATA(error_method);
*error_method = md;
return check_fast(context, state);
}
ret = decode_PA_FX_FAST_REPLY(pa->padata_value.data,
pa->padata_value.length,
&fxfastrep,
NULL);
if (ret)
goto out;
if (fxfastrep.element == choice_PA_FX_FAST_REPLY_armored_data) {
krb5_data data;
ret = krb5_decrypt_EncryptedData(context,
state->armor_crypto,
KRB5_KU_FAST_REP,
&fxfastrep.u.armored_data.enc_fast_rep,
&data);
if (ret)
goto out;
ret = decode_KrbFastResponse(data.data, data.length, &fastrep, NULL);
krb5_data_free(&data);
if (ret)
goto out;
} else {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto out;
}
/* RFC 6113 5.4.3: strengthen key must be absent in error reply */
if (fastrep.strengthen_key || nonce != fastrep.nonce) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto out;
}
pa = find_pa_data(&fastrep.padata, KRB5_PADATA_FX_ERROR);
if (pa == NULL) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto out;
}
ret = krb5_rd_error(context, &pa->padata_value, &fast_error);
if (ret)
goto out;
free_KRB_ERROR(error);
*error = fast_error;
memset(&fast_error, 0, sizeof(fast_error));
free_METHOD_DATA(error_method);
*error_method = fastrep.padata;
memset(&fastrep.padata, 0, sizeof(fastrep.padata));
out:
free_METHOD_DATA(&md);
free_PA_FX_FAST_REPLY(&fxfastrep);
free_KrbFastResponse(&fastrep);
free_KRB_ERROR(&fast_error);
return ret;
}
krb5_error_code
@@ -2058,7 +2326,9 @@ make_fast_ap_fxarmor(krb5_context context,
}
static krb5_error_code
fast_wrap_req(krb5_context context, struct fast_state *state, KDC_REQ *req)
fast_wrap_req(krb5_context context,
struct fast_state *state,
KDC_REQ *req)
{
KrbFastArmor *fxarmor = NULL;
PA_FX_FAST_REQUEST fxreq;
@@ -2067,6 +2337,16 @@ fast_wrap_req(krb5_context context, struct fast_state *state, KDC_REQ *req)
krb5_data data;
size_t size;
/* FX-COOKIE can be used without FAST armor */
if (state->cookie.data) {
ret = krb5_padata_add(context, req->padata, KRB5_PADATA_FX_COOKIE,
state->cookie.data, state->cookie.length);
if (ret)
return ret;
krb5_data_zero(&state->cookie);
}
if (state->flags & KRB5_FAST_DISABLED) {
_krb5_debug(context, 10, "fast disabled, not doing any fast wrapping");
return 0;
@@ -2196,6 +2476,43 @@ fast_wrap_req(krb5_context context, struct fast_state *state, KDC_REQ *req)
return ret;
}
static krb5_error_code
fast_validate_conversation(METHOD_DATA *md, struct fast_state *state)
{
PA_DATA *pa;
/*
* RFC 6113 5.4.3: PA-FX-COOKIE MUST be included if the KDC
* expects at least one more message from the client.
*/
pa = find_pa_data(md, KRB5_PADATA_FX_COOKIE);
if (pa == NULL)
return KRB5_PREAUTH_FAILED;
krb5_data_free(&state->cookie);
state->cookie = pa->padata_value;
krb5_data_zero(&pa->padata_value);
return 0;
}
static size_t
available_padata_count(METHOD_DATA *md)
{
size_t i, count = 0;
for (i = 0; i < md->len; i++) {
PA_DATA *pa = &md->val[i];
if (pa->padata_type == KRB5_PADATA_FX_COOKIE ||
pa->padata_type == KRB5_PADATA_FX_ERROR)
continue;
count++;
}
return count;
}
static krb5_error_code
init_creds_step(krb5_context context,
@@ -2219,7 +2536,7 @@ init_creds_step(krb5_context context,
return ret;
}
#define MAX_PA_COUNTER 10
#define MAX_PA_COUNTER 15
if (ctx->pa_counter > MAX_PA_COUNTER) {
krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP,
N_("Looping %d times while getting "
@@ -2296,13 +2613,8 @@ init_creds_step(krb5_context context,
NULL);
if (ret == 0 && ctx->pk_init_ctx) {
PA_DATA *pa_pkinit_kx;
int idx = 0;
pa_pkinit_kx =
krb5_find_padata(rep.kdc_rep.padata->val,
rep.kdc_rep.padata->len,
KRB5_PADATA_PKINIT_KX,
&idx);
pa_pkinit_kx = find_pa_data(rep.kdc_rep.padata, KRB5_PADATA_PKINIT_KX);
ret = _krb5_pk_kx_confirm(context, ctx->pk_init_ctx,
ctx->fast_state.reply_key,
@@ -2344,7 +2656,8 @@ init_creds_step(krb5_context context,
/*
* Unwrap KRB-ERROR
*/
ret = fast_unwrap_error(context, &ctx->fast_state, &ctx->error);
ret = fast_unwrap_error(context, ctx->nonce, &ctx->fast_state,
&ctx->error, &ctx->md);
if (ret)
goto out;
@@ -2361,24 +2674,17 @@ init_creds_step(krb5_context context,
* more try.
*/
if (ret == KRB5KDC_ERR_PREAUTH_REQUIRED) {
free_METHOD_DATA(&ctx->md);
memset_s(&ctx->md, sizeof(ctx->md), 0, sizeof(ctx->md));
if (ctx->error.e_data) {
ret = decode_METHOD_DATA(ctx->error.e_data->data,
ctx->error.e_data->length,
&ctx->md,
NULL);
if (ret)
krb5_set_error_message(context, ret,
N_("Failed to decode METHOD-DATA", ""));
} else {
if (ret == KRB5KDC_ERR_PREAUTH_REQUIRED ||
ret == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED) {
if (available_padata_count(&ctx->md) == 0) {
krb5_set_error_message(context, ret,
N_("Preauth required but no preauth "
"options send by KDC", ""));
}
if (ret == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
ret = fast_validate_conversation(&ctx->md, &ctx->fast_state);
else
ret = 0;
} else if (ret == KRB5KRB_AP_ERR_SKEW && context->kdc_sec_offset == 0) {
/*
* Try adapt to timeskrew when we are using pre-auth, and
@@ -2819,7 +3125,7 @@ krb5_init_creds_get(krb5_context context, krb5_init_creds_context ctx)
if (ret)
goto out;
if ((flags & 1) == 0)
if ((flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) == 0)
break;
ret = krb5_sendto_context (context, stctx, &out,
@@ -3056,3 +3362,88 @@ krb5_get_init_creds_keytab(krb5_context context,
return ret;
}
KRB5_LIB_FUNCTION void KRB5_LIB_CALL
_krb5_init_creds_set_gss_mechanism(krb5_context context,
krb5_gss_init_ctx gssic,
const struct gss_OID_desc_struct *gss_mech)
{
gssic->mech = gss_mech; /* OIDs are interned, so no copy required */
}
KRB5_LIB_FUNCTION const struct gss_OID_desc_struct * KRB5_LIB_CALL
_krb5_init_creds_get_gss_mechanism(krb5_context context,
krb5_gss_init_ctx gssic)
{
return gssic->mech;
}
KRB5_LIB_FUNCTION void KRB5_LIB_CALL
_krb5_init_creds_set_gss_cred(krb5_context context,
krb5_gss_init_ctx gssic,
struct gss_cred_id_t_desc_struct *gss_cred)
{
if (gssic->cred != gss_cred &&
(gssic->flags & GSSIC_FLAG_RELEASE_CRED))
gssic->release_cred(context, gssic, gssic->cred);
gssic->cred = gss_cred;
gssic->flags |= GSSIC_FLAG_RELEASE_CRED;
}
KRB5_LIB_FUNCTION const struct gss_cred_id_t_desc_struct * KRB5_LIB_CALL
_krb5_init_creds_get_gss_cred(krb5_context context,
krb5_gss_init_ctx gssic)
{
return gssic->cred;
}
KRB5_LIB_FUNCTION void KRB5_LIB_CALL
_krb5_init_creds_set_gss_context(krb5_context context,
krb5_gss_init_ctx gssic,
struct gss_ctx_id_t_desc_struct *gss_ctx)
{
if (gssic->ctx != gss_ctx)
gssic->delete_sec_context(context, gssic, gssic->ctx);
gssic->ctx = gss_ctx;
}
KRB5_LIB_FUNCTION const struct gss_ctx_id_t_desc_struct * KRB5_LIB_CALL
_krb5_init_creds_get_gss_context(krb5_context context,
krb5_gss_init_ctx gssic)
{
return gssic->ctx;
}
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_init_creds_init_gss(krb5_context context,
krb5_init_creds_context ctx,
krb5_gssic_step step,
krb5_gssic_finish finish,
krb5_gssic_release_cred release_cred,
krb5_gssic_delete_sec_context delete_sec_context,
const struct gss_cred_id_t_desc_struct *gss_cred,
const struct gss_OID_desc_struct *gss_mech,
unsigned int flags)
{
krb5_gss_init_ctx gssic = ctx->gss_init_ctx;
gssic = calloc(1, sizeof(*gssic));
if (gssic == NULL)
return krb5_enomem(context);
if (ctx->gss_init_ctx)
free_gss_init_ctx(context, ctx->gss_init_ctx);
ctx->gss_init_ctx = gssic;
gssic->cred = (struct gss_cred_id_t_desc_struct *)gss_cred;
gssic->mech = gss_mech;
gssic->flags = flags;
gssic->step = step;
gssic->finish = finish;
gssic->release_cred = release_cred;
gssic->delete_sec_context = delete_sec_context;
return 0;
}

View File

@@ -901,6 +901,32 @@ can be given as a number followed by a unit, such as
.Dq 2d
for
.Dq two days .
.It Li enable_gss_preauth = Va boolean
Enables pre-authentication using a GSS-API mechanism supported by the client and KDC.
The GSS-API initiator and AS request client names must match, unless the
.Li
WELLKNOWN/FEDERATED
name was used in the AS request, in which case the AS reply will contain the
GSS-API initiator name. Authorization and mapping behavior may be customized
by plugins. If synthetic clients are enabled, then the GSS-API initiator need
not exist in the local database. GSS-API pre-authentication is disabled by
default.
.It Li enable_gss_auth_data = Va boolean
When using GSS-API pre-authentication, includes a Kerberos authorization data
element containing naming attributes associated with the GSS-API initiator. This
is disabled by default as it may significantly increase the size of returned
tickets.
.It Li gss_mechanisms_allowed = Va mechs ...
A list of GSS-API mechanisms that may be used for GSS-API pre-authentication.
.It Li gss_cross_realm_mechanisms_allowed = Va mechs ...
A list of GSS-API mechanisms that, when using the default authorization
mechanism, will be permitted to map Kerberos principals in foreign realms. The
list is empty by default. Initiator names from mechanisms not on this list will
be mapped to an enterprise principal in the AS-REQ realm. This option is
intended to avoid conflating GSS-API pre-authentication and Kerberos
cross-realm authentication. The behavior is provided by the default
authorization mechanism and will be overridden by an authorization plugin.
Mechanisms may be identified by dot-separated OID or a short name.
.It Li historical_anon_realm = Va boolean
Enables pre-7.0 non-RFC-comformant KDC behavior.
With this option set to

View File

@@ -715,10 +715,11 @@ typedef EncAPRepPart krb5_ap_rep_enc_part;
#define KRB5_WELLKNOWN_NAME ("WELLKNOWN")
#define KRB5_ANON_NAME ("ANONYMOUS")
#define KRB5_ANON_REALM ("WELLKNOWN:ANONYMOUS")
#define KRB5_FEDERATED_NAME ("FEDERATED")
#define KRB5_FEDERATED_REALM ("WELLKNOWN:FEDERATED")
#define KRB5_WELLKNOWN_ORG_H5L_REALM ("WELLKNOWN:ORG.H5L")
#define KRB5_DIGEST_NAME ("digest")
#define KRB5_PKU2U_REALM_NAME ("WELLKNOWN:PKU2U")
#define KRB5_LKDC_REALM_NAME ("WELLKNOWN:COM.APPLE.LKDC")

View File

@@ -101,12 +101,13 @@ error_code DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED, "Digest in signedData not accepte
error_code PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED, "Public key encryption not supported"
## these are never used
#index 80
#index 85
#prefix KRB5_IAKERB
#error_code ERR_KDC_NOT_FOUND, "IAKERB proxy could not find a KDC"
#error_code ERR_KDC_NO_RESPONSE, "IAKERB proxy never reeived a response from a KDC"
# 82-93 are reserved
index 91
error_code MORE_PREAUTH_DATA_REQUIRED, "More pre-authentication data required"
index 94
error_code INVALID_HASH_ALG, "Invalid OTP digest algorithm"

View File

@@ -140,6 +140,13 @@ struct krb5_dh_moduli;
/* v4 glue */
struct _krb5_krb_auth_data;
struct krb5_gss_init_ctx_data;
typedef struct krb5_gss_init_ctx_data *krb5_gss_init_ctx;
struct gss_ctx_id_t_desc_struct;
struct gss_cred_id_t_desc_struct;
struct gss_OID_desc_struct;
#include <der.h>
#include <krb5.h>
@@ -152,6 +159,34 @@ struct _krb5_krb_auth_data;
#include "crypto.h"
typedef krb5_error_code (KRB5_LIB_CALL *krb5_gssic_step)(
krb5_context,
krb5_gss_init_ctx,
const krb5_creds *,
KDCOptions options,
krb5_data *,
krb5_data *,
krb5_data *);
typedef krb5_error_code (KRB5_LIB_CALL *krb5_gssic_finish)(
krb5_context,
krb5_gss_init_ctx,
const krb5_creds *,
krb5int32,
krb5_enctype,
krb5_principal *,
krb5_keyblock **);
typedef void (KRB5_LIB_CALL *krb5_gssic_release_cred)(
krb5_context,
krb5_gss_init_ctx,
struct gss_cred_id_t_desc_struct *);
typedef void (KRB5_LIB_CALL *krb5_gssic_delete_sec_context)(
krb5_context,
krb5_gss_init_ctx,
struct gss_ctx_id_t_desc_struct *);
#include <krb5-private.h>
#include "heim_threads.h"

View File

@@ -525,6 +525,7 @@ EXPORTS
krb5_principal_get_realm
krb5_principal_get_type
krb5_principal_is_anonymous
krb5_principal_is_federated
krb5_principal_is_krbtgt
krb5_principal_is_root_krbtgt
krb5_principal_match
@@ -792,6 +793,15 @@ EXPORTS
_krb5_SP800_108_HMAC_KDF
_krb5_get_ad
; Shared with GSSAPI preauth wrapper
_krb5_init_creds_set_gss_mechanism
_krb5_init_creds_get_gss_mechanism
_krb5_init_creds_set_gss_cred
_krb5_init_creds_get_gss_cred
_krb5_init_creds_set_gss_context
_krb5_init_creds_get_gss_context
_krb5_init_creds_init_gss
; Shared with libkadm5
_krb5_load_plugins
_krb5_unload_plugins

View File

@@ -1312,6 +1312,28 @@ krb5_principal_is_anonymous(krb5_context context,
return strcmp(p->realm, KRB5_ANON_REALM) != 0;
}
/**
* Returns true iff name is WELLKNOWN/FEDERATED
*
* @ingroup krb5_principal
*/
KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL
krb5_principal_is_federated(krb5_context context,
krb5_const_principal p)
{
if (p->name.name_type != KRB5_NT_WELLKNOWN &&
p->name.name_type != KRB5_NT_UNKNOWN)
return FALSE;
if (p->name.name_string.len != 2 ||
strcmp(p->name.name_string.val[0], KRB5_WELLKNOWN_NAME) != 0 ||
strcmp(p->name.name_string.val[1], KRB5_FEDERATED_NAME) != 0)
return FALSE;
return TRUE;
}
static int
tolower_ascii(int c)
{

View File

@@ -522,6 +522,7 @@ HEIMDAL_KRB5_2.0 {
krb5_principal_set_realm;
krb5_principal_set_type;
krb5_principal_is_anonymous;
krb5_principal_is_federated;
krb5_principal_is_krbtgt;
krb5_principal_is_root_krbtgt;
krb5_print_address;
@@ -784,6 +785,15 @@ HEIMDAL_KRB5_2.0 {
_krb5_SP800_108_HMAC_KDF;
_krb5_get_ad;
# Shared with GSSAPI preauth wrapper
_krb5_init_creds_set_gss_mechanism;
_krb5_init_creds_get_gss_mechanism;
_krb5_init_creds_set_gss_cred;
_krb5_init_creds_get_gss_cred;
_krb5_init_creds_set_gss_context;
_krb5_init_creds_get_gss_context;
_krb5_init_creds_init_gss;
# Shared with libkadm5
_krb5_load_plugins;
_krb5_unload_plugins;