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

@@ -0,0 +1,616 @@
Kerberos Working Group A. Perez-Mendez
Internet-Draft R. Marin-Lopez
Intended status: Experimental F. Pereniguez-Garcia
Expires: March 5, 2013 G. Lopez-Millan
University of Murcia
Sep 2012
GSS-API pre-authentication for Kerberos
draft-perez-krb-wg-gss-preauth-02
Abstract
This document describes a pre-authentication mechanism for Kerberos
based on the Generic Security Service Application Program Interface
(GSS-API), which allows a Key Distribution Center (KDC) to
authenticate clients by using a GSS mechanism.
Status of this Memo
This Internet-Draft is submitted in full conformance with the
provisions of BCP 78 and BCP 79.
Internet-Drafts are working documents of the Internet Engineering
Task Force (IETF). Note that other groups may also distribute
working documents as Internet-Drafts. The list of current Internet-
Drafts is at http://datatracker.ietf.org/drafts/current/.
Internet-Drafts are draft documents valid for a maximum of six months
and may be updated, replaced, or obsoleted by other documents at any
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on March 5, 2013.
Copyright Notice
Copyright (c) 2012 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
carefully, as they describe your rights and restrictions with respect
to this document. Code Components extracted from this document must
include Simplified BSD License text as described in Section 4.e of
the Trust Legal Provisions and are provided without warranty as
Perez-Mendez, et al. Expires March 5, 2013 [Page 1]
Internet-Draft GSS preauth Sep 2012
described in the Simplified BSD License.
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1. Requirements Language . . . . . . . . . . . . . . . . . . 4
2. Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3. Definition of the Kerberos GSS padata . . . . . . . . . . . . 4
4. GSS Pre-authentication Operation . . . . . . . . . . . . . . . 5
4.1. Generation of GSS preauth requests . . . . . . . . . . . . 5
4.2. Processing of GSS preauth requests . . . . . . . . . . . . 6
4.3. Generation of GSS preauth responses . . . . . . . . . . . 6
4.4. Processing of GSS preauth responses . . . . . . . . . . . 7
5. Data in the KDC_ERR_PREAUTH_REQUIRED . . . . . . . . . . . . . 7
6. Derivation of the reply key from the GSS context . . . . . . . 7
7. KDC state management . . . . . . . . . . . . . . . . . . . . . 8
8. Support for federated users . . . . . . . . . . . . . . . . . 8
9. GSS channel bindings . . . . . . . . . . . . . . . . . . . . . 9
10. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 9
11. Security Considerations . . . . . . . . . . . . . . . . . . . 9
12. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 9
13. Normative References . . . . . . . . . . . . . . . . . . . . . 9
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 10
Perez-Mendez, et al. Expires March 5, 2013 [Page 2]
Internet-Draft GSS preauth Sep 2012
1. Introduction
The GSS-API (Generic Security Service Application Programming
Interface) [RFC2743] provides a generic toolset of functions that
allow applications to establish security contexts in order to protect
their communications through security services such as
authentication, confidentiality and integrity protection. Thanks to
the GSS-API, applications remain independent from the specific
underlying mechanism used to establish the context and provide
security.
On the other hand, Kerberos [RFC4120] defines a process called pre-
authentication. This feature is intended to avoid the security risk
of providing tickets encrypted with the user's long-term key to
attackers, by requiring clients to proof their knowledge over these
credentials. The execution of a pre-authentication mechanism may
require the exchange of several KRB_AS_REQ/KRB_ERROR messages before
the KDC delivers the TGT requested by the client within a KRB_AS_REP.
These messages transport authentication information by means of pre-
authentication elements.
There exists a variety of pre-authentication mechanisms, like PKINIT
[RFC4556] and encrypted time-stamp [RFC4120]. Furthermore,
[I-D.ietf-krb-wg-preauth-framework] provides a generic framework for
Kerberos pre-authentication, which aims to describe the features that
a pre-authentication mechanism may provide (e.g. mutual
authentication, replace reply key, etc.). Additionally, in order to
simplify the definition of new pre-authentication mechanisms, it
defines a mechanism called FAST (Flexible Authentication Secure
Tunneling), which provides a generic and secure transport for pre-
authentication elements. More specifically, FAST establishes a
secure tunnel providing confidentiality and integrity protection
between the client and the KDC prior to the exchange of any specific
pre-authentication data. Within this tunnel, different pre-
authentication methods can be executed. This inner mechanism is
called a FAST factor. It is important to note that FAST factors
cannot usually be used outside the FAST pre-authentication method
since they assume the underlying security layer provided by FAST.
The aim of this draft is to define a new pre-authentication
mechanism, following the recommendations of
[I-D.ietf-krb-wg-preauth-framework], that relies on the GSS-API
security services to pre-authenticate clients. This pre-
authentication mechanism will allow the KDC to authenticate clients
making use of any current or future GSS mechanism, as long as they
satisfy the minimum security requirements described in this
specification. The Kerberos client will play the role of the GSS
initiator, while the Authentication Server (AS) in the KDC will play
Perez-Mendez, et al. Expires March 5, 2013 [Page 3]
Internet-Draft GSS preauth Sep 2012
the role of the GSS acceptor.
1.1. Requirements Language
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
document are to be interpreted as described in RFC 2119 [RFC2119].
2. Motivation
This work is mainly motivated by the necessity of a way to allow the
KDC to make use of the technologies defined in the ABFAB WG to
perform the access control of federated users. Specifically, the
ABFAB architecture requires relying parties to make use of the GSS-
EAP mechanism to perform authentication.
[I-D.perez-abfab-eap-gss-preauth] defines how GSS-EAP is transported
on top of the GSS pre-authentication mechanism defined in this
document.
3. Definition of the Kerberos GSS padata
To establish the security context, the GSS-API defines the exchange
of GSS tokens between the initiator and the acceptor. These tokens,
which contain mechanism-specific information, are completely opaque
to the application. However, how these tokens are transported
between the initiator and the responder depends on the specific
application. Since GSS-API is defined as independent of the
underlying communications service, its use does not require to
implement any specific security feature for the transport. For
instance, tokens could just be sent by means of plain UDP datagrams.
For this reason, security and ordered delivery of information must be
implemented by each specific GSS mechanism (if required).
Therefore, GSS tokens are the atomic piece of information from the
application point of view when using GSS-API, which require a proper
transport between the initiator (Kerberos client) and the acceptor
(AS). In particular, the proposed GSS-based pre-authentication
mechanism defines a new pre-authentication element (hereafter padata)
called PA-GSS, to transport a generic GSS token from the Kerberos
client to the AS and vice-versa. This padata also transport state
information required to maintain the KDC stateless (see section
Section 7.
PA-GSS To be defined (TBD)
A PA-GSS padata element contains the ASN.1 DER encoding of the PA-GSS
Perez-Mendez, et al. Expires March 5, 2013 [Page 4]
Internet-Draft GSS preauth Sep 2012
structure:
PA-GSS ::= SEQUENCE {
sec-ctx-token [0] OCTET STRING,
state [1] EncryptedData OPTIONAL -- contains PA-GSS-STATE
}
PA-GSS-STATE ::= SEQUENCE {
timestamp [0] KerberosTime,
exported-sec-ctx-token [1] OCTET STRING,
...
}
The sec-ctx-token element of the PA-GSS structure contains the
output_token token returned by either, the GSS_Init_sec_context and
the GSS_Accept_sec_context calls. The state element of the PA-GSS
structure is optional, and will be absent in the first AS_REQ message
from the client to the KDC and in the last AS_REP message from the
KDC to the client. It contains a PA-GSS-STATE structure encrypted
with the first krbtgt key and a key usage in the 512-1023 range (to
be defined TBD). The state element is generated by the KDC, while
the client just copy it from the previously received PA-GSS structure
(if present).
The PA-GSS-STATE contains a timestamp element, meant to detect
possible replay situations, and a exported-sec-ctx-token element,
representing the whole GSS security context state corresponding to
the current authentication process. This value is generated by the
KDC by calling to the GSS_Export_sec_context, when the
GSS_Accept_sec_context returns GSS_S_CONTINUE_NEEDED.
4. GSS Pre-authentication Operation
4.1. Generation of GSS preauth requests
The Kerberos client (initiator) starts by calling to the
GSS_Init_sec_context function. In the first call to this function,
the client provides GSS_C_NO_CTX as the value of the context_handle
and NULL as the input_token, given that no context has been initiated
yet. When using multi round-trip GSS mechanisms, in subsequent calls
to this routine the client will use both, the context_handle value
obtained after the first call, and the input_token received from the
KDC. The mutual_req_flag, replay_det_req_flag and sequence_req_flag
MUST be set, as the GSS token is meant to be tranported over
cleartext channels.
The GSS_Init_sec_context returns a context_handle, an output_token
Perez-Mendez, et al. Expires March 5, 2013 [Page 5]
Internet-Draft GSS preauth Sep 2012
and a status value. In this first call, the only acceptable status
value is GSS_S_CONTINUE_NEEDED, as the KDC is expected to provide a
token in order to continue with the context establishment process.
The Kerberos client creates a new PA-GSS padata, with the obtained
output_token as the sec-ctx-token element, and with the state element
absent. The PA-GSS padata is sent to the KDC through a KRB_AS_REQ
message.
4.2. Processing of GSS preauth requests
When the KDC (GSS acceptor) receives a KRB_AS_REQ message containing
a PA-GSS padata, but a state element (see Section 7) is not included,
the KDC assumes that this is the first message of a context
establishment, and thus GSS_C_NO_CTX is used as context_handle to
invoke the GSS_Accept_sec_context routine. Conversely, if a state
element is included, the KDC assumes that this message is part an
ongoing authentication and the value of the state element is
decrypted and used to recover the state of the authentication (see
Section 7). In both cases, after receiving the message, the KDC
calls to the GSS_Accept_sec_context function, using the adequate
context_handle value and using the received token in the PA-GSS
padata as input_token.
Once the execution of the GSS_Accept_sec_context function is
completed, the KDC obtains a context_handle, an output_token that
MUST be sent to the initiator in order to continue with the
authentication process, and a status value. If the obtained status
is GSS_S_COMPLETE, the client is considered authenticated. If the
status is GSS_S_CONTINUE_NEEDED, further information is required to
complete the process.
4.3. Generation of GSS preauth responses
Once the KDC has processed the input_token provided by the client (as
described in Section 4.2), two main different situations may occur
depending on the status value. If the client is successfully
authenticated (GSS_S_COMPLETE), the KDC will reply to the client with
a KRB_AS_REP message. This message will transport the final
output_token in a PA-GSS padata type. This PA-GSS padata will not
contain the state element. The reply key used to encrypt the enc-
part field of the KRB_AS_REP message is derived from the GSS security
context cryptographic material. Section 6 provides further details
regarding this derivation. At this moment, the KDC also verifies
that the cname provided in the AS_REQ matches the src_name obtained
through the final GSS_Accept_sec_ctx call (except when WELLKNOWN/
FEDERATED is used as cname Section 8).
On the contrary, if further data is required to complete the
Perez-Mendez, et al. Expires March 5, 2013 [Page 6]
Internet-Draft GSS preauth Sep 2012
establishment process (GSS_S_CONTINUE_NEEDED), the KDC will reply to
the client with a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error message
[I-D.ietf-krb-wg-preauth-framework]. In the e-data field of the
message, the KDC will include the PA-GSS padata, containing both, the
GSS token (sec-ctx-token) and the exported GSS security context
(state) (see Section 7).
4.4. Processing of GSS preauth responses
When the client receives a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error,
it extracts the token from the PA-GSS element and invokes the
GSS_Init_sec_context function, as described in section Section 4.1.
If present, the state element of the PA-GSS padata is treated as an
opaque element, and it is simply copied and included into the
generated PA-GSS element without further processing.
On the other hand, when the client receives a KRB_AS_REP, it knows
the context establishment has finalized successfully. The client
invokes the GSS_Init_sec_context function using the transported GSS
token. Note that, to be consistent, this call MUST return
GSS_S_COMPLETE and not generate any output_token, since the KDC does
not expect further data from the client.
If the context establishment is completed correctly, the client MUST
use the same key derivation process followed by the KDC (Section 4.3)
to obtain the reply key to decrypt the enc-part of the KRB_AS_REP.
5. Data in the KDC_ERR_PREAUTH_REQUIRED
When the KDC sends a KDC_ERR_PREAUTH_REQUIRED error to the client, it
includes a sequence of padata, each corresponding to an acceptable
pre-authentication method. Optionally, these padata elements contain
data valuable for the client to configure the selected mechanism.
The data to be included in the padata for this message is described
in this section.
TBD. (For example, list of the OIDs of the GSS mechanisms supported
by the KDC)
6. Derivation of the reply key from the GSS context
The GSS pre-authentication mechanism proposed in this draft provides
the "Replacing-reply-key" facility
[I-D.ietf-krb-wg-preauth-framework].
After a successful authentication, client and KDC may decide to
Perez-Mendez, et al. Expires March 5, 2013 [Page 7]
Internet-Draft GSS preauth Sep 2012
completely replace the reply key used to encrypt the KRB_AS_REP by a
new one that is cryptographically independent from the client's
password stored in client password on the Kerberos users database.
This additional keying material can be obtained by means of calls to
the GSS_Pseudo_random [RFC4401] function, using "KRB-GSS" as the
prf_in parameter.
7. KDC state management
The Kerberos standard [RFC4120] defines the KDC as a stateless
entity. This means that, if the GSS mechanism requires more than one
round-trip, the client MUST provide enough data to the KDC in the
following interactions to allow recovering the complete state of the
ongoing authentication. This is specially relevant when the client
switches from one KDC to different one (within the same realm) during
a pre-authentication process. This second KDC must be able to
continue with the process in a seamless way.
The GSS-API manages the so-called security contexts. They represent
the whole context of an authentication, including all the state and
relevant data of the ongoing security context. Thus, this
information MUST be serialized and sent to the client in order to
ensure that the KDC receiving it will be able to reconstruct the
associated state. In order to prevent attacks, this information must
be confidentiality and integrity protected using a key shared amongst
all the KDCs deployed in the realm, and must be sent along with a
timestamp to prevent replay attacks. How this information is encoded
is described in section Section 3.
To generate the serialized security context information, the
GSS_Export_sec_ctx() call is used. The main drawback of this
approach is that the current GSS-API specifications does not allow
the exportation of a security context which has not been completely
established. Nevertheless, some GSS mechanisms do allow the
exportation of partially established context (e.g.
[I-D.ietf-abfab-gss-eap]), and we expect that other GSS mechanisms
will do the same in the future.
8. Support for federated users
This draft supports the authentication of users belonging to a
different domain than the authenticating KDC. This is achieved by
letting the GSS-API to provide both, the client name and the reply
key to be used. That means that the requested username may not be
present in the KDC's database. To avoid the generation of an error
of type KDC_ERR_C_PRINCIPAL_UNKNOWN, when the Kerberos client knows
Perez-Mendez, et al. Expires March 5, 2013 [Page 8]
Internet-Draft GSS preauth Sep 2012
it is operating in a federated environment, it MUST set the value of
the cname field of the KRB_AS_REQ message to a new wellknown value,
WELLKNOWN/FEDERATED, following the model proposed in [RFC6111]. In
this way, the KDC will be completely authenticated by the GSS-API
calls, and thus no local verification of credentials should be done.
9. GSS channel bindings
In order to link the GSS authentication with the actual Kerberos
exchange transporting GSS tokens, the DER-encoded KDC-REQ-BODY from
the AS-REQ is used as channel bindings.
10. Acknowledgements
This work is supported by the project MULTIGIGABIT EUROPEAN ACADEMIC
NETWORK (FP7-INFRASTRUCTURES-2009-1). It is also funded by a Seneca
Foundation grant from the Human Resources Researching Training
Program 2007. Authors finally thank the Funding Program for Research
Groups of Excellence with code 04552/GERM/06 granted by the Fundacion
Seneca.
11. Security Considerations
Protection of Request/Responses with FAST, restriction on GSS
mechanism, etc. TBD.
12. IANA Considerations
This document has no actions for IANA.
13. Normative References
[I-D.ietf-abfab-gss-eap]
Hartman, S. and J. Howlett, "A GSS-API Mechanism for the
Extensible Authentication Protocol",
draft-ietf-abfab-gss-eap-09 (work in progress),
August 2012.
[I-D.ietf-krb-wg-preauth-framework]
Hartman, S. and L. Zhu, "A Generalized Framework for
Kerberos Pre-Authentication",
draft-ietf-krb-wg-preauth-framework-17 (work in progress),
June 2010.
Perez-Mendez, et al. Expires March 5, 2013 [Page 9]
Internet-Draft GSS preauth Sep 2012
[I-D.perez-abfab-eap-gss-preauth]
Perez-Mendez, A., Lopez, R., Pereniguez-Garcia, F., and G.
Lopez-Millan, "GSS-EAP pre-authentication for Kerberos",
draft-perez-abfab-eap-gss-preauth-01 (work in progress),
March 2012.
[RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
Requirement Levels", BCP 14, RFC 2119, March 1997.
[RFC2743] Linn, J., "Generic Security Service Application Program
Interface Version 2, Update 1", RFC 2743, January 2000.
[RFC4120] Neuman, C., Yu, T., Hartman, S., and K. Raeburn, "The
Kerberos Network Authentication Service (V5)", RFC 4120,
July 2005.
[RFC4401] Williams, N., "A Pseudo-Random Function (PRF) API
Extension for the Generic Security Service Application
Program Interface (GSS-API)", RFC 4401, February 2006.
[RFC4556] Zhu, L. and B. Tung, "Public Key Cryptography for Initial
Authentication in Kerberos (PKINIT)", RFC 4556, June 2006.
[RFC6111] Zhu, L., "Additional Kerberos Naming Constraints",
RFC 6111, April 2011.
Authors' Addresses
Alejandro Perez-Mendez (Ed.)
University of Murcia
Campus de Espinardo S/N, Faculty of Computer Science
Murcia, 30100
Spain
Phone: +34 868 88 46 44
Email: alex@um.es
Rafa Marin-Lopez
University of Murcia
Campus de Espinardo S/N, Faculty of Computer Science
Murcia, 30100
Spain
Phone: +34 868 88 85 01
Email: rafa@um.es
Perez-Mendez, et al. Expires March 5, 2013 [Page 10]
Internet-Draft GSS preauth Sep 2012
Fernando Pereniguez-Garcia
University of Murcia
Campus de Espinardo S/N, Faculty of Computer Science
Murcia, 30100
Spain
Phone: +34 868 88 78 82
Email: pereniguez@um.es
Gabriel Lopez-Millan
University of Murcia
Campus de Espinardo S/N, Faculty of Computer Science
Murcia, 30100
Spain
Phone: +34 868 88 85 04
Email: gabilm@um.es
Perez-Mendez, et al. Expires March 5, 2013 [Page 11]

View File

@@ -236,6 +236,12 @@ init(struct init_options *opt, int argc, char **argv)
KRB5_KDB_REQUIRES_PRE_AUTH, 0);
krb5_free_principal(context, princ);
/* Create `WELLKNOWN/FEDERATED' for GSS preauth */
krb5_make_principal(context, &princ, realm,
KRB5_WELLKNOWN_NAME, KRB5_FEDERATED_NAME, NULL);
create_random_entry(princ, 60*60, 60*60,
KRB5_KDB_REQUIRES_PRE_AUTH, 0);
krb5_free_principal(context, princ);
/* Create `WELLKNONW/org.h5l.fast-cookie@WELLKNOWN:ORG.H5L' for FAST cookie */
krb5_make_principal(context, &princ, KRB5_WELLKNOWN_ORG_H5L_REALM,

View File

@@ -117,6 +117,7 @@ libkdc_la_SOURCES = \
csr_authorizer.c \
process.c \
windc.c \
gss_preauth.c \
rx.h
KDC_PROTOS = $(srcdir)/kdc-protos.h $(srcdir)/kdc-private.h
@@ -185,6 +186,7 @@ libkdc_la_LIBADD = \
$(LIB_pkinit) \
$(top_builddir)/lib/hdb/libhdb.la \
$(top_builddir)/lib/krb5/libkrb5.la \
$(top_builddir)/lib/gssapi/libgssapi.la \
$(LIB_kdb) \
$(top_builddir)/lib/ntlm/libheimntlm.la \
$(LIB_hcrypto) \

View File

@@ -107,7 +107,8 @@ LIBKDC_OBJS=\
$(OBJ)\token_validator.obj \
$(OBJ)\csr_authorizer.obj \
$(OBJ)\process.obj \
$(OBJ)\windc.obj
$(OBJ)\windc.obj \
$(OBJ)\gss_preauth.obj
LIBKDC_LIBS=\
$(LIBHDB) \
@@ -146,6 +147,7 @@ libkdc_la_SOURCES = \
csr_authorizer.c \
process.c \
windc.c \
gss_preauth.c \
rx.h
$(OBJ)\kdc-protos.h: $(libkdc_la_SOURCES)

View File

@@ -74,6 +74,7 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
{
static heim_base_once_t load_kdc_plugins = HEIM_BASE_ONCE_INIT;
krb5_kdc_configuration *c;
krb5_error_code ret;
heim_base_once_f(&load_kdc_plugins, context, load_kdc_plugins_once);
@@ -329,6 +330,36 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
"synthetic_clients_max_renew",
NULL);
c->enable_gss_preauth =
krb5_config_get_bool_default(context, NULL,
c->enable_gss_preauth,
"kdc",
"enable_gss_preauth", NULL);
c->enable_gss_auth_data =
krb5_config_get_bool_default(context, NULL,
c->enable_gss_auth_data,
"kdc",
"enable_gss_auth_data", NULL);
ret = _kdc_gss_get_mechanism_config(context, "kdc",
"gss_mechanisms_allowed",
&c->gss_mechanisms_allowed);
if (ret) {
free(c);
return ret;
}
ret = _kdc_gss_get_mechanism_config(context, "kdc",
"gss_cross_realm_mechanisms_allowed",
&c->gss_cross_realm_mechanisms_allowed);
if (ret) {
OM_uint32 minor;
gss_release_oid_set(&minor, &c->gss_mechanisms_allowed);
free(c);
return ret;
}
*config = c;
return 0;

View File

@@ -42,6 +42,7 @@
#include <getarg.h>
typedef struct pk_client_params pk_client_params;
typedef struct gss_client_params gss_client_params;
struct DigestREQ;
struct Kx509Request;

View File

@@ -257,6 +257,20 @@ _kdc_fast_mk_error(astgs_request_t r,
krb5_data_zero(&e_data);
if (armor_crypto || (r && r->fast.fast_state.len)) {
if (r)
ret = fast_add_cookie(r, error_method);
else
ret = krb5_padata_add(context, error_method,
KRB5_PADATA_FX_COOKIE,
NULL, 0);
if (ret) {
kdc_log(r->context, r->config, 1, "failed to add fast cookie with: %d", ret);
free_METHOD_DATA(error_method);
return ret;
}
}
if (armor_crypto) {
PA_FX_FAST_REPLY fxfastrep;
KrbFastResponse fastrep;
@@ -292,18 +306,6 @@ _kdc_fast_mk_error(astgs_request_t r,
error_server = NULL;
e_text = NULL;
if (r)
ret = fast_add_cookie(r, error_method);
else
ret = krb5_padata_add(context, error_method,
KRB5_PADATA_FX_COOKIE,
NULL, 0);
if (ret) {
kdc_log(r->context, r->config, 1, "failed to add fast cookie with: %d", ret);
free_METHOD_DATA(error_method);
return ret;
}
ret = _kdc_fast_mk_response(context, armor_crypto,
error_method, NULL, NULL,
req_body->nonce, &e_data);
@@ -342,8 +344,8 @@ _kdc_fast_mk_error(astgs_request_t r,
return ret;
}
krb5_error_code
_kdc_fast_unwrap_request(astgs_request_t r)
static krb5_error_code
fast_unwrap_request(astgs_request_t r)
{
krb5_principal armor_server = NULL;
hdb_entry_ex *armor_user = NULL;
@@ -362,17 +364,6 @@ _kdc_fast_unwrap_request(astgs_request_t r)
const PA_DATA *pa;
int i = 0;
/*
* First look for FX_COOKIE and and process it
*/
pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FX_COOKIE);
if (pa) {
ret = fast_parse_cookie(r, pa);
if (ret)
goto out;
}
i = 0;
pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FX_FAST);
if (pa == NULL)
return 0;
@@ -562,6 +553,30 @@ _kdc_fast_unwrap_request(astgs_request_t r)
return ret;
}
krb5_error_code
_kdc_fast_unwrap_request(astgs_request_t r)
{
krb5_error_code ret;
const PA_DATA *pa;
int i = 0;
ret = fast_unwrap_request(r);
if (ret)
return ret;
/*
* Non-FAST mechanisms may use FX-COOKIE to manage state.
*/
pa = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FX_COOKIE);
if (pa) {
ret = fast_parse_cookie(r, pa);
if (ret)
return ret;
}
return 0;
}
void
_kdc_free_fast_state(KDCFastState *state)
{

804
kdc/gss_preauth.c Normal file
View File

@@ -0,0 +1,804 @@
/*
* Copyright (c) 2021, PADL Software Pty Ltd.
* All rights reserved.
*
* Portions Copyright (c) 2019 Kungliga Tekniska Högskolan
*
* 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 "kdc_locl.h"
#include <gssapi/gssapi.h>
#include <gssapi_mech.h>
#include "../lib/gssapi/preauth/pa-private.h"
#include "gss_preauth_authorizer_plugin.h"
struct gss_client_params {
gss_ctx_id_t context_handle;
gss_name_t initiator_name;
gss_OID mech_type;
gss_buffer_desc output_token;
OM_uint32 flags;
OM_uint32 lifetime;
};
static void
pa_gss_display_status(astgs_request_t r,
OM_uint32 major,
OM_uint32 minor,
gss_client_params *gcp,
const char *msg);
static void
pa_gss_display_name(gss_name_t name,
gss_buffer_t namebuf,
gss_const_buffer_t *namebuf_p);
/*
* Deserialize a GSS-API security context from the FAST cookie.
*/
static krb5_error_code
pa_gss_get_context_state(astgs_request_t r,
gss_client_params *gcp)
{
int idx = 0;
PA_DATA *fast_pa;
fast_pa = krb5_find_padata(r->fast.fast_state.val,
r->fast.fast_state.len,
KRB5_PADATA_GSS, &idx);
if (fast_pa) {
gss_buffer_desc sec_context_token;
OM_uint32 major, minor;
_krb5_gss_data_to_buffer(&fast_pa->padata_value, &sec_context_token);
major = gss_import_sec_context(&minor, &sec_context_token,
&gcp->context_handle);
if (GSS_ERROR(major))
pa_gss_display_status(r, major, minor, gcp,
"Failed to import GSS pre-authentication context");
return _krb5_gss_map_error(major, minor);
}
return 0;
}
/*
* Serialize a GSS-API security context into a FAST cookie.
*/
static krb5_error_code
pa_gss_set_context_state(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
PA_DATA *fast_pa;
int idx = 0;
OM_uint32 major, minor;
gss_buffer_desc sec_context_token = GSS_C_EMPTY_BUFFER;
major = gss_export_sec_context(&minor, &gcp->context_handle,
&sec_context_token);
if (GSS_ERROR(major)) {
pa_gss_display_status(r, major, minor, gcp,
"Failed to export GSS pre-authentication context");
return _krb5_gss_map_error(major, minor);
}
fast_pa = krb5_find_padata(r->fast.fast_state.val,
r->fast.fast_state.len,
KRB5_PADATA_GSS, &idx);
if (fast_pa) {
krb5_data_free(&fast_pa->padata_value);
_krb5_gss_buffer_to_data(&sec_context_token, &fast_pa->padata_value);
} else {
ret = krb5_padata_add(r->context,
&r->fast.fast_state,
KRB5_PADATA_GSS,
sec_context_token.value,
sec_context_token.length);
if (ret) {
gss_release_buffer(&minor, &sec_context_token);
return ret;
}
}
return 0;
}
static krb5_error_code
pa_gss_acquire_acceptor_cred(astgs_request_t r,
gss_client_params *gcp,
gss_cred_id_t *cred)
{
krb5_error_code ret;
OM_uint32 major, minor;
gss_name_t target_name = GSS_C_NO_NAME;
gss_buffer_desc display_name = GSS_C_EMPTY_BUFFER;
gss_const_buffer_t display_name_p;
*cred = GSS_C_NO_CREDENTIAL;
ret = _krb5_gss_pa_unparse_name(r->context, r->server_princ, &target_name);
if (ret)
return ret;
pa_gss_display_name(target_name, &display_name, &display_name_p);
kdc_log(r->context, r->config, 4,
"Acquiring GSS acceptor credential for %.*s",
(int)display_name_p->length, (char *)display_name_p->value);
major = gss_acquire_cred(&minor, target_name, GSS_C_INDEFINITE,
r->config->gss_mechanisms_allowed,
GSS_C_ACCEPT, cred, NULL, NULL);
ret = _krb5_gss_map_error(major, minor);
if (ret)
pa_gss_display_status(r, major, minor, gcp,
"Failed to acquire GSS acceptor credential");
gss_release_buffer(&minor, &display_name);
gss_release_name(&minor, &target_name);
return ret;
}
krb5_error_code
_kdc_gss_rd_padata(astgs_request_t r,
const PA_DATA *pa,
gss_client_params **pgcp,
int *open)
{
krb5_error_code ret;
size_t size;
OM_uint32 major, minor;
gss_client_params *gcp = NULL;
gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
struct gss_channel_bindings_struct cb;
memset(&cb, 0, sizeof(cb));
*pgcp = NULL;
if (!r->config->enable_gss_preauth) {
ret = KRB5KDC_ERR_POLICY;
goto out;
}
if (pa->padata_value.length == 0) {
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto out;
}
gcp = calloc(1, sizeof(*gcp));
if (gcp == NULL) {
ret = krb5_enomem(r->context);
goto out;
}
ret = pa_gss_get_context_state(r, gcp);
if (ret)
goto out;
ret = pa_gss_acquire_acceptor_cred(r, gcp, &cred);
if (ret)
goto out;
_krb5_gss_data_to_buffer(&pa->padata_value, &input_token);
ASN1_MALLOC_ENCODE(KDC_REQ_BODY, cb.application_data.value,
cb.application_data.length, &r->req.req_body,
&size, ret);
heim_assert(ret || size == cb.application_data.length,
"internal asn1 encoder error");
major = gss_accept_sec_context(&minor,
&gcp->context_handle,
cred,
&input_token,
&cb,
&gcp->initiator_name,
&gcp->mech_type,
&gcp->output_token,
&gcp->flags,
&gcp->lifetime,
NULL); /* delegated_cred_handle */
if (GSS_ERROR(major)) {
pa_gss_display_status(r, major, minor, gcp,
"Failed to accept GSS security context");
ret = _krb5_gss_map_error(major, minor);
goto out;
}
if ((gcp->flags & GSS_C_ANON_FLAG) && !_kdc_is_anon_request(&r->req)) {
kdc_log(r->context, r->config, 2,
"Anonymous GSS pre-authentication request w/o anonymous flag");
ret = KRB5KDC_ERR_BADOPTION;
goto out;
}
*open = (major == GSS_S_COMPLETE);
out:
gss_release_cred(&minor, &cred);
gss_release_buffer(&minor, &cb.application_data);
if (ret == 0)
*pgcp = gcp;
else
_kdc_gss_free_client_param(r, gcp);
return ret;
}
krb5_timestamp
_kdc_gss_endtime(astgs_request_t r,
gss_client_params *gcp)
{
krb5_timestamp endtime;
if (gcp->lifetime == GSS_C_INDEFINITE)
endtime = 0;
else
endtime = kdc_time + gcp->lifetime;
kdc_log(r->context, r->config, 10,
"GSS pre-authentication endtime is %ld", endtime);
return endtime;
}
struct pa_gss_plugin_ctx {
astgs_request_t r;
struct gss_client_params *gcp;
krb5_boolean authorized;
krb5_principal initiator_princ;
};
static krb5_error_code
pa_gss_authorize_cb(krb5_context context,
const void *plug,
void *plugctx,
void *userctx)
{
const krb5plugin_gss_preauth_authorizer_ftable *authorizer = plug;
struct pa_gss_plugin_ctx *pa_gss_plugin_ctx = userctx;
return authorizer->authorize(plugctx, context,
&pa_gss_plugin_ctx->r->req,
pa_gss_plugin_ctx->r->client_princ,
pa_gss_plugin_ctx->r->client,
pa_gss_plugin_ctx->gcp->initiator_name,
pa_gss_plugin_ctx->gcp->mech_type,
pa_gss_plugin_ctx->gcp->flags,
&pa_gss_plugin_ctx->authorized,
&pa_gss_plugin_ctx->initiator_princ);
}
static const char *plugin_deps[] = {
"kdc",
"hdb",
"gssapi",
"krb5",
NULL
};
static struct heim_plugin_data
gss_preauth_authorizer_data = {
"kdc",
KDC_GSS_PREAUTH_AUTHORIZER,
KDC_GSS_PREAUTH_AUTHORIZER_VERSION_0,
plugin_deps,
kdc_get_instance
};
static krb5_error_code
pa_gss_authorize_plugin(astgs_request_t r,
struct gss_client_params *gcp,
gss_const_buffer_t display_name,
krb5_boolean *authorized,
krb5_principal *initiator_princ)
{
krb5_error_code ret;
struct pa_gss_plugin_ctx ctx;
ctx.r = r;
ctx.gcp = gcp;
ctx.authorized = 0;
ctx.initiator_princ = NULL;
krb5_clear_error_message(r->context);
ret = _krb5_plugin_run_f(r->context, &gss_preauth_authorizer_data,
0, &ctx, pa_gss_authorize_cb);
if (ret != KRB5_PLUGIN_NO_HANDLE) {
const char *msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, r->config, 7,
"GSS authz plugin %sauthorize%s %s initiator %.*s: %s",
ctx.authorized ? "" : "did not " ,
ctx.authorized ? "d" : "",
gss_oid_to_name(gcp->mech_type),
(int)display_name->length, (char *)display_name->value,
msg);
krb5_free_error_message(r->context, msg);
}
*authorized = ctx.authorized;
*initiator_princ = ctx.initiator_princ;
return ret;
}
static krb5_error_code
pa_gss_authorize_default(astgs_request_t r,
struct gss_client_params *gcp,
gss_const_buffer_t display_name,
krb5_boolean *authorized,
krb5_principal *initiator_princ)
{
krb5_error_code ret;
krb5_principal principal;
krb5_const_realm realm = r->server->entry.principal->realm;
int flags = 0, cross_realm_allowed = 0, unauth_anon;
/*
* gss_cross_realm_mechanisms_allowed is a list of GSS-API mechanisms
* that are allowed to map directly to Kerberos principals in any
* realm. If the authenticating mechanism is not on the list, then
* the initiator will be mapped to an enterprise principal in the
* service realm. This is useful to stop synthetic principals in
* foreign realms being conflated with true cross-realm principals.
*/
if (r->config->gss_cross_realm_mechanisms_allowed) {
OM_uint32 minor;
gss_test_oid_set_member(&minor, gcp->mech_type,
r->config->gss_cross_realm_mechanisms_allowed,
&cross_realm_allowed);
}
kdc_log(r->context, r->config, 10,
"Initiator %.*s will be mapped to %s",
(int)display_name->length, (char *)display_name->value,
cross_realm_allowed ? "nt-principal" : "nt-enterprise-principal");
if (!cross_realm_allowed)
flags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE | KRB5_PRINCIPAL_PARSE_NO_REALM;
ret = _krb5_gss_pa_parse_name(r->context, gcp->initiator_name,
flags, &principal);
if (ret) {
const char *msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, r->config, 2,
"Failed to parse %s initiator name %.*s: %s",
gss_oid_to_name(gcp->mech_type),
(int)display_name->length, (char *)display_name->value, msg);
krb5_free_error_message(r->context, msg);
return ret;
}
/*
* GSS_C_ANON_FLAG indicates the client requested anonymous authentication
* (it is validated against the request-anonymous flag).
*
* _kdc_is_anonymous_pkinit() returns TRUE if the principal contains both
* the well known anonymous name and realm.
*/
unauth_anon = (gcp->flags & GSS_C_ANON_FLAG) &&
_kdc_is_anonymous_pkinit(r->context, principal);
/*
* Always use the anonymous entry created in our HDB, i.e. with the local
* realm, for authorizing anonymous requests. This matches PKINIT behavior
* as anonymous PKINIT requests include the KDC realm in the request.
*/
if (unauth_anon || (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE)) {
ret = krb5_principal_set_realm(r->context, principal, realm);
if (ret) {
krb5_free_principal(r->context, principal);
return ret;
}
}
if (unauth_anon) {
/*
* Special case to avoid changing _kdc_as_rep(). If the initiator is
* the unauthenticated anonymous principal, r->client_princ also needs
* to be set in order to force the AS-REP realm to be set to the well-
* known anonymous identity. This is because (unlike anonymous PKINIT)
* we only require the anonymous flag, not the anonymous name, in the
* client AS-REQ.
*/
krb5_principal anon_princ;
ret = krb5_copy_principal(r->context, principal, &anon_princ);
if (ret)
return ret;
krb5_free_principal(r->context, r->client_princ);
r->client_princ = anon_princ;
}
*authorized = TRUE;
*initiator_princ = principal;
return 0;
}
krb5_error_code
_kdc_gss_check_client(astgs_request_t r,
gss_client_params *gcp,
char **client_name)
{
krb5_error_code ret;
krb5_principal initiator_princ = NULL;
hdb_entry_ex *initiator = NULL;
krb5_boolean authorized = FALSE;
OM_uint32 minor;
gss_buffer_desc display_name = GSS_C_EMPTY_BUFFER;
gss_const_buffer_t display_name_p;
*client_name = NULL;
pa_gss_display_name(gcp->initiator_name, &display_name, &display_name_p);
/*
* If no plugins handled the authorization request, then all clients
* are authorized as the directly corresponding Kerberos principal.
*/
ret = pa_gss_authorize_plugin(r, gcp, display_name_p,
&authorized, &initiator_princ);
if (ret == KRB5_PLUGIN_NO_HANDLE)
ret = pa_gss_authorize_default(r, gcp, display_name_p,
&authorized, &initiator_princ);
if (ret == 0 && !authorized)
ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
if (ret)
goto out;
ret = krb5_unparse_name(r->context, initiator_princ, client_name);
if (ret)
goto out;
kdc_log(r->context, r->config, 4,
"Mapped GSS %s initiator %.*s to principal %s",
gss_oid_to_name(gcp->mech_type),
(int)display_name_p->length, (char *)display_name_p->value,
*client_name);
ret = _kdc_db_fetch(r->context,
r->config,
initiator_princ,
HDB_F_FOR_AS_REQ | HDB_F_GET_CLIENT |
HDB_F_CANON | HDB_F_SYNTHETIC_OK,
NULL,
&r->clientdb,
&initiator);
if (ret) {
const char *msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, r->config, 4, "UNKNOWN -- %s: %s",
*client_name, msg);
krb5_free_error_message(r->context, msg);
goto out;
}
/*
* If the AS-REQ client name was the well-known federated name, then
* replace the client name with the initiator name. Otherwise, the
* two principals must match, noting that GSS pre-authentication is
* for authentication, not general purpose impersonation.
*/
if (krb5_principal_is_federated(r->context, r->client->entry.principal)) {
initiator->entry.flags.force_canonicalize = 1;
_kdc_free_ent(r->context, r->client);
r->client = initiator;
initiator = NULL;
} else if (!krb5_principal_compare(r->context,
r->client->entry.principal,
initiator->entry.principal)) {
kdc_log(r->context, r->config, 2,
"GSS %s initiator %.*s does not match principal %s",
gss_oid_to_name(gcp->mech_type),
(int)display_name_p->length, (char *)display_name_p->value,
r->cname);
ret = KRB5_KDC_ERR_CLIENT_NAME_MISMATCH;
goto out;
}
out:
krb5_free_principal(r->context, initiator_princ);
if (initiator)
_kdc_free_ent(r->context, initiator);
gss_release_buffer(&minor, &display_name);
return ret;
}
krb5_error_code
_kdc_gss_mk_pa_reply(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
const KDC_REQ *req = &r->req;
OM_uint32 major, minor;
int open;
major = gss_inquire_context(&minor,
gcp->context_handle,
NULL, /* initiator_name */
NULL, /* target_name */
NULL, /* lifetime_req */
NULL, /* mech_type */
NULL, /* ctx_flags */
NULL, /* locally_initiated */
&open);
if (GSS_ERROR(major)) {
pa_gss_display_status(r, major, minor, gcp,
"Failed to inquire GSS context");
ret = _krb5_gss_map_error(major, minor);
goto out;
}
if (open) {
krb5_enctype enctype;
uint32_t kfe = 0;
krb5_keyblock *reply_key = NULL;
if (krb5_principal_is_krbtgt(r->context, r->server_princ))
kfe |= KFE_IS_TGS;
ret = _kdc_find_etype(r, kfe, req->req_body.etype.val,
req->req_body.etype.len, &enctype, NULL, NULL);
if (ret)
goto out;
ret = _krb5_gss_pa_derive_key(r->context, gcp->context_handle,
req->req_body.nonce,
enctype, &reply_key);
if (ret) {
kdc_log(r->context, r->config, 10,
"Failed to derive GSS reply key: %d", ret);
goto out;
}
krb5_free_keyblock_contents(r->context, &r->reply_key);
r->reply_key = *reply_key;
free(reply_key);
} else {
ret = pa_gss_set_context_state(r, gcp);
if (ret)
goto out;
}
ret = krb5_padata_add(r->context, &r->outpadata, KRB5_PADATA_GSS,
gcp->output_token.value, gcp->output_token.length);
if (ret)
goto out;
/* token is now owned by outpadata */
gcp->output_token.length = 0;
gcp->output_token.value = NULL;
if (!open)
ret = KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
out:
return ret;
}
krb5_error_code
_kdc_gss_mk_composite_name_ad(astgs_request_t r,
gss_client_params *gcp)
{
krb5_error_code ret;
krb5_data data;
OM_uint32 major, minor;
gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER;
if (!r->config->enable_gss_auth_data || (gcp->flags & GSS_C_ANON_FLAG))
return 0;
major = gss_export_name_composite(&minor, gcp->initiator_name, &namebuf);
if (major == GSS_S_COMPLETE) {
_krb5_gss_buffer_to_data(&namebuf, &data);
ret = _kdc_tkt_add_if_relevant_ad(r->context, &r->et,
KRB5_AUTHDATA_GSS_COMPOSITE_NAME,
&data);
} else if (major != GSS_S_UNAVAILABLE)
ret = _krb5_gss_map_error(major, minor);
else
ret = 0;
gss_release_buffer(&minor, &namebuf);
return ret;
}
void
_kdc_gss_free_client_param(astgs_request_t r,
gss_client_params *gcp)
{
OM_uint32 minor;
if (gcp == NULL)
return;
gss_delete_sec_context(&minor, &gcp->context_handle, GSS_C_NO_BUFFER);
gss_release_name(&minor, &gcp->initiator_name);
gss_release_buffer(&minor, &gcp->output_token);
memset(gcp, 0, sizeof(*gcp));
free(gcp);
}
krb5_error_code
_kdc_gss_get_mechanism_config(krb5_context context,
const char *section,
const char *key,
gss_OID_set *oidsp)
{
krb5_error_code ret;
char **mechs, **mechp;
gss_OID_set oids = GSS_C_NO_OID_SET;
OM_uint32 major, minor;
mechs = krb5_config_get_strings(context, NULL, section, key, NULL);
if (mechs == NULL)
return 0;
major = gss_create_empty_oid_set(&minor, &oids);
if (GSS_ERROR(major)) {
krb5_config_free_strings(mechs);
return _krb5_gss_map_error(major, minor);
}
for (mechp = mechs; *mechp; mechp++) {
gss_OID oid = gss_name_to_oid(*mechp);
if (oid == GSS_C_NO_OID)
continue;
major = gss_add_oid_set_member(&minor, oid, &oids);
if (GSS_ERROR(major))
break;
}
ret = _krb5_gss_map_error(major, minor);
if (ret == 0)
*oidsp = oids;
else
gss_release_oid_set(&minor, &oids);
krb5_config_free_strings(mechs);
return ret;
}
static void
pa_gss_display_status(astgs_request_t r,
OM_uint32 major,
OM_uint32 minor,
gss_client_params *gcp,
const char *msg)
{
krb5_error_code ret = _krb5_gss_map_error(major, minor);
gss_buffer_desc buf = GSS_C_EMPTY_BUFFER;
OM_uint32 dmaj, dmin;
OM_uint32 more = 0;
char *gmmsg = NULL;
char *gmsg = NULL;
char *s = NULL;
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_GSS_CODE, GSS_C_NO_OID,
&more, &buf);
if (GSS_ERROR(dmaj) ||
buf.length >= INT_MAX ||
asprintf(&s, "%s%s%.*s", gmsg ? gmsg : "", gmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmsg);
gmsg = NULL;
break;
}
gmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
if (gcp->mech_type != GSS_C_NO_OID) {
do {
gss_release_buffer(&dmin, &buf);
dmaj = gss_display_status(&dmin, major, GSS_C_MECH_CODE,
gcp->mech_type, &more, &buf);
if (GSS_ERROR(dmaj) ||
asprintf(&s, "%s%s%.*s", gmmsg ? gmmsg : "", gmmsg ? ": " : "",
(int)buf.length, (char *)buf.value) == -1 ||
s == NULL) {
free(gmmsg);
gmmsg = NULL;
break;
}
gmmsg = s;
s = NULL;
} while (!GSS_ERROR(dmaj) && more);
}
if (gmsg == NULL)
krb5_set_error_message(r->context, ENOMEM,
"Error displaying GSS-API status");
else
krb5_set_error_message(r->context, ret, "%s%s%s%s", gmsg,
gmmsg ? " (" : "", gmmsg ? gmmsg : "",
gmmsg ? ")" : "");
krb5_prepend_error_message(r->context, ret, "%s", msg);
kdc_log(r->context, r->config, 1,
"%s: %s%s%s%s",
msg, gmsg, gmmsg ? " (" : "", gmmsg ? gmmsg : "",
gmmsg ? ")" : "");
free(gmmsg);
free(gmsg);
}
static const gss_buffer_desc
gss_pa_unknown_display_name = {
sizeof("<unknown name>") - 1,
"<unknown name>"
};
static void
pa_gss_display_name(gss_name_t name,
gss_buffer_t namebuf,
gss_const_buffer_t *namebuf_p)
{
OM_uint32 major, minor;
major = gss_display_name(&minor, name, namebuf, NULL);
if (GSS_ERROR(major))
*namebuf_p = &gss_pa_unknown_display_name;
else
*namebuf_p = namebuf;
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2019 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.
*/
#ifndef HEIMDAL_KDC_GSS_PREAUTH_AUTHORIZER_PLUGIN_H
#define HEIMDAL_KDC_GSS_PREAUTH_AUTHORIZER_PLUGIN_H 1
#define KDC_GSS_PREAUTH_AUTHORIZER "kdc_gss_preauth_authorizer"
#define KDC_GSS_PREAUTH_AUTHORIZER_VERSION_0 0
#include <krb5.h>
#include <gssapi/gssapi.h>
/*
* @param init Plugin initialization function (see krb5-plugin(7))
* @param minor_version The plugin minor version number (0)
* @param fini Plugin finalization function
* @param authorize Plugin name authorization function
*
* -# plug_ctx, the context value output by the plugin's init function
* -# context, a krb5_context
* -# req, the AS-REQ request
* -# client_name, the requested client principal name
* -# client, the requested client HDB entry
* -# initiator_name, the authenticated GSS initiator name
* -# ret_flags, the flags returned by GSS_Init_sec_context()
* -# authorized, indicate whether the initiator was authorized
* -# mapped_name, the mapped principal name
*
* @ingroup krb5_support
*/
typedef struct krb5plugin_gss_preauth_authorizer_ftable_desc {
int minor_version;
krb5_error_code (KRB5_LIB_CALL *init)(krb5_context, void **);
void (KRB5_LIB_CALL *fini)(void *);
krb5_error_code (KRB5_LIB_CALL *authorize)(void *, /*plug_ctx*/
krb5_context, /*context*/
KDC_REQ *, /*req*/
krb5_const_principal,/*client_name*/
hdb_entry_ex *, /*client*/
gss_const_name_t, /*initiator_name*/
gss_const_OID, /*mech_type*/
OM_uint32, /*ret_flags*/
krb5_boolean *, /*authorized*/
krb5_principal *); /*mapped_name*/
} krb5plugin_gss_preauth_authorizer_ftable;
#endif /* HEIMDAL_KDC_GSS_PREAUTH_AUTHORIZER_PLUGIN_H */

View File

@@ -98,6 +98,7 @@
#include <hdb.h>
#include <hdb_err.h>
#include <der.h>
#include <gssapi/gssapi.h>
#ifndef NO_NTLM
#include <heimntlm.h>

View File

@@ -44,6 +44,7 @@
#include <hdb.h>
#include <krb5.h>
#include <kx509_asn1.h>
#include <gssapi/gssapi.h>
enum krb5_kdc_trpolicy {
TRPOLICY_ALWAYS_CHECK,
@@ -107,6 +108,11 @@ typedef struct krb5_kdc_configuration {
int enable_digest;
int digests_allowed;
int enable_gss_preauth;
int enable_gss_auth_data;
gss_OID_set gss_mechanisms_allowed;
gss_OID_set gss_cross_realm_mechanisms_allowed;
size_t max_datagram_reply_length;
int enable_kx509;

View File

@@ -41,6 +41,7 @@
#include "headers.h"
typedef struct pk_client_params pk_client_params;
typedef struct gss_client_params gss_client_params;
#include <kdc-private.h>

View File

@@ -508,6 +508,56 @@ pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
#endif /* PKINIT */
static krb5_error_code
pa_gss_validate(astgs_request_t r, const PA_DATA *pa)
{
gss_client_params *gcp = NULL;
char *client_name = NULL;
krb5_error_code ret;
int open;
ret = _kdc_gss_rd_padata(r, pa, &gcp, &open);
if (ret) {
_kdc_r_log(r, 4, "Failed to decode GSS PA-DATA -- %s",
r->cname);
goto out;
}
if (open) {
ret = _kdc_gss_check_client(r, gcp, &client_name);
if (ret) {
_kdc_set_e_text(r, "GSS-API client not allowed to "
"impersonate principal");
goto out;
}
r->pa_endtime = _kdc_gss_endtime(r, gcp);
_kdc_r_log(r, 4, "GSS pre-authentication succeeded -- %s using %s",
r->cname, client_name);
free(client_name);
ret = _kdc_gss_mk_composite_name_ad(r, gcp);
if (ret) {
_kdc_set_e_text(r, "Failed to build GSS authorization data");
goto out;
}
}
ret = _kdc_gss_mk_pa_reply(r, gcp);
if (ret &&
ret != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED) {
_kdc_set_e_text(r, "Failed to build GSS pre-authentication reply");
goto out;
}
out:
if (gcp)
_kdc_gss_free_client_param(r, gcp);
return ret;
}
/*
*
*/
@@ -919,7 +969,12 @@ static const struct kdc_patypes pat[] = {
{ KRB5_PADATA_REQ_ENC_PA_REP , "REQ-ENC-PA-REP", 0, NULL },
{ KRB5_PADATA_FX_FAST, "FX-FAST", PA_ANNOUNCE, NULL },
{ KRB5_PADATA_FX_ERROR, "FX-ERROR", 0, NULL },
{ KRB5_PADATA_FX_COOKIE, "FX-COOKIE", 0, NULL }
{ KRB5_PADATA_FX_COOKIE, "FX-COOKIE", 0, NULL },
{
KRB5_PADATA_GSS , "GSS",
PA_ANNOUNCE | PA_SYNTHETIC_OK,
pa_gss_validate
},
};
static void
@@ -1808,6 +1863,20 @@ _kdc_is_anonymous(krb5_context context, krb5_const_principal principal)
return krb5_principal_is_anonymous(context, principal, KRB5_ANON_MATCH_ANY);
}
/*
* Returns TRUE if principal is the unauthenticated anonymous identity,
* i.e. WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS. Unfortunately due to
* backwards compatibility logic in krb5_principal_is_anonymous() we
* have to use our own implementation.
*/
krb5_boolean
_kdc_is_anonymous_pkinit(krb5_context context, krb5_const_principal principal)
{
return _kdc_is_anonymous(context, principal) &&
strcmp(principal->realm, KRB5_ANON_REALM) == 0;
}
static int
require_preauth_p(astgs_request_t r)
{
@@ -2521,7 +2590,9 @@ out:
* In case of a non proxy error, build an error message.
*/
if (ret != 0 && ret != HDB_ERR_NOT_FOUND_HERE && r->reply->length == 0)
ret = _kdc_fast_mk_error(r, &error_method,
ret = _kdc_fast_mk_error(r,
(ret == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
? &r->outpadata : &error_method,
r->armor_crypto,
&req->req_body,
ret, r->e_text,

View File

@@ -304,7 +304,9 @@ negotiate_get_instance(const char *libname)
{
if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
/* XXX gss_get_instance() doesn't exist :( */
else if (strcmp(libname, "gssapi") == 0)
return gss_get_instance(libname);
return 0;
}

View File

@@ -227,6 +227,8 @@ kdc_get_instance(const char *libname)
return hdb_get_instance(libname);
else if (strcmp(libname, "krb5") == 0)
return krb5_get_instance(libname);
else if (strcmp(libname, "gssapi") == 0)
return gss_get_instance(libname);
return 0;
}

View File

@@ -28,6 +28,7 @@ noinst_PROGRAMS = kverify kdecode_ticket generate-requests
kinit_LDADD = \
$(afs_lib) \
$(top_builddir)/lib/krb5/libkrb5.la \
$(top_builddir)/lib/gssapi/libgssapi.la \
$(top_builddir)/lib/ntlm/libheimntlm.la \
$(LIB_hcrypto) \
$(top_builddir)/lib/asn1/libasn1.la \

View File

@@ -240,6 +240,15 @@ An example of an enterprise name is
and this option is usually used with canonicalize so that the
principal returned from the KDC will typically be the real principal
name.
.It Fl Fl gss-mech
Enable GSS-API pre-authentication using the specified mechanism OID. Unless
.Ar gss-name
is also set, then the specified principal name will be used as the GSS-API
initiator name. If the principal is specified as @REALM or left unspecified,
then the default GSS-API credential will be used.
.It Fl Fl gss-name
Attempt GSS-API pre-authentication using an initiator name distinct from the
Kerberos client principal,
.It Fl Fl afslog
Gets AFS tickets, converts them to version 4 format, and stores them
in the kernel.

View File

@@ -79,6 +79,8 @@ struct hx509_certs_data *ent_user_id = NULL;
char *pk_x509_anchors = NULL;
int pk_use_enckey = 0;
int pk_anon_fast_armor = 0;
char *gss_preauth_mech = NULL;
char *gss_preauth_name = NULL;
static int canonicalize_flag = 0;
static int enterprise_flag = 0;
static int ok_as_delegate_flag = 0;
@@ -188,6 +190,13 @@ static struct getargs args[] = {
{ "pk-anon-fast-armor", 0, arg_flag, &pk_anon_fast_armor,
NP_("use unauthenticated anonymous PKINIT as FAST armor", ""), NULL },
#endif
{ "gss-mech", 0, arg_string, &gss_preauth_mech,
NP_("use GSS mechanism for pre-authentication", ""), NULL },
{ "gss-name", 0, arg_string, &gss_preauth_name,
NP_("use distinct GSS identity for pre-authentication", ""), NULL },
#ifndef NO_NTLM
{ "ntlm-domain", 0, arg_string, &ntlm_domain,
NP_("NTLM domain", ""), "domain" },
@@ -570,6 +579,106 @@ out:
return ret;
}
static krb5_error_code
make_wellknown_name(krb5_context context,
krb5_const_realm realm,
const char *instance,
krb5_principal *principal)
{
krb5_error_code ret;
ret = krb5_make_principal(context, principal, realm,
KRB5_WELLKNOWN_NAME, instance, NULL);
if (ret == 0)
krb5_principal_set_type(context, *principal, KRB5_NT_WELLKNOWN);
return ret;
}
static krb5_error_code
acquire_gss_cred(krb5_context context,
krb5_const_principal client,
krb5_deltat life,
const char *passwd,
gss_cred_id_t *cred,
gss_OID *mech)
{
krb5_error_code ret;
OM_uint32 major, minor;
gss_name_t name = GSS_C_NO_NAME;
gss_key_value_element_desc cred_element;
gss_key_value_set_desc cred_store;
gss_OID_set_desc mechs;
*cred = GSS_C_NO_CREDENTIAL;
*mech = GSS_C_NO_OID;
if (gss_preauth_mech) {
*mech = gss_name_to_oid(gss_preauth_mech);
if (*mech == GSS_C_NO_OID)
return EINVAL;
}
if (gss_preauth_name) {
gss_buffer_desc buf;
buf.value = gss_preauth_name;
buf.length = strlen(gss_preauth_name);
major = gss_import_name(&minor, &buf, GSS_C_NT_USER_NAME, &name);
ret = _krb5_gss_map_error(major, minor);
} else if (!krb5_principal_is_federated(context, client)) {
ret = _krb5_gss_pa_unparse_name(context, client, &name);
} else {
/*
* WELLKNOWN/FEDERATED is used a placeholder where the user
* did not specify either a Kerberos credential or a GSS-API
* initiator name. It avoids the expense of acquiring a default
* credential purely to interrogate the credential name.
*/
name = GSS_C_NO_NAME;
ret = 0;
}
if (ret)
goto out;
cred_store.count = 1;
cred_store.elements = &cred_element;
if (passwd && passwd[0]) {
cred_element.key = "password";
cred_element.value = passwd;
} else if (keytab_str) {
cred_element.key = "client_keytab",
cred_element.value = keytab_str;
} else {
cred_store.count = 0;
}
if (*mech) {
mechs.count = 1;
mechs.elements = (gss_OID)*mech;
}
major = gss_acquire_cred_from(&minor,
name,
life ? life : GSS_C_INDEFINITE,
*mech ? &mechs : GSS_C_NO_OID_SET,
GSS_C_INITIATE,
&cred_store,
cred,
NULL,
NULL);
if (major != GSS_S_COMPLETE) {
ret = _krb5_gss_map_error(major, minor);
goto out;
}
out:
gss_release_name(&minor, &name);
return ret;
}
#ifndef NO_NTLM
static krb5_error_code
@@ -624,6 +733,9 @@ get_new_tickets(krb5_context context,
krb5_init_creds_context ctx = NULL;
krb5_get_init_creds_opt *opt = NULL;
krb5_prompter_fct prompter = krb5_prompter_posix;
gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
gss_OID gss_mech = GSS_C_NO_OID;
krb5_principal federated_name = NULL;
#ifndef NO_NTLM
struct ntlm_buf ntlmkey;
@@ -775,6 +887,29 @@ get_new_tickets(krb5_context context,
etype_str.num_strings);
}
if (gss_preauth_mech || gss_preauth_name) {
ret = acquire_gss_cred(context, principal, ticket_life,
passwd, &gss_cred, &gss_mech);
if (ret)
goto out;
/*
* The principal specified on the command line is used as the GSS-API
* initiator name, unless the --gss-name option was present, in which
* case the initiator name is specified independently.
*/
if (gss_preauth_name == NULL) {
krb5_const_realm realm = krb5_principal_get_realm(context, principal);
ret = make_wellknown_name(context, realm,
KRB5_FEDERATED_NAME, &federated_name);
if (ret)
goto out;
principal = federated_name;
}
}
ret = krb5_init_creds_init(context, principal, prompter, NULL, start_time, opt, &ctx);
if (ret) {
krb5_warn(context, ret, "krb5_init_creds_init");
@@ -816,7 +951,13 @@ get_new_tickets(krb5_context context,
}
}
if (use_keytab || keytab_str) {
if (gss_mech != GSS_C_NO_OID) {
ret = krb5_gss_set_init_creds(context, ctx, gss_cred, gss_mech);
if (ret) {
krb5_warn(context, ret, "krb5_gss_set_init_creds");
goto out;
}
} else if (use_keytab || keytab_str) {
ret = krb5_init_creds_set_keytab(context, ctx, kt);
if (ret) {
krb5_warn(context, ret, "krb5_init_creds_set_keytab");
@@ -979,6 +1120,11 @@ get_new_tickets(krb5_context context,
}
out:
{
OM_uint32 minor;
gss_release_cred(&minor, &gss_cred);
}
krb5_free_principal(context, federated_name);
krb5_get_init_creds_opt_free(context, opt);
if (ctx)
krb5_init_creds_free(context, ctx);
@@ -1489,17 +1635,20 @@ main(int argc, char **argv)
krb5_err(context, 1, ret, "krb5_pk_enterprise_certs");
pk_user_id = NULL;
} else if (argc && argv[0][0] == '@' &&
(gss_preauth_mech || anonymous_flag)) {
const char *instance;
} else if (anonymous_flag && argc && argv[0][0] == '@') {
/* If principal argument as @REALM, try anonymous PKINIT */
if (gss_preauth_mech) {
instance = KRB5_FEDERATED_NAME;
} else if (anonymous_flag) {
instance = KRB5_ANON_NAME;
anonymous_pkinit = TRUE;
}
ret = krb5_make_principal(context, &principal, &argv[0][1],
KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME,
NULL);
ret = make_wellknown_name(context, &argv[0][1], instance, &principal);
if (ret)
krb5_err(context, 1, ret, "krb5_make_principal");
krb5_principal_set_type(context, principal, KRB5_NT_WELLKNOWN);
anonymous_pkinit = TRUE;
krb5_err(context, 1, ret, "make_wellknown_name");
} else if (anonymous_flag && historical_anon_pkinit) {
char *realm = argc == 0 ? get_default_realm(context) :
argv[0][0] == '@' ? &argv[0][1] : argv[0];
@@ -1512,6 +1661,15 @@ main(int argc, char **argv)
anonymous_pkinit = TRUE;
} else if (use_keytab || keytab_str) {
get_princ_kt(context, &principal, argv[0]);
} else if (gss_preauth_mech && argc == 0 && gss_preauth_name == NULL) {
/*
* Use the federated name as a placeholder if we have neither a Kerberos
* nor a GSS-API client name, and we are performing GSS-API preauth.
*/
ret = make_wellknown_name(context, get_default_realm(context),
KRB5_FEDERATED_NAME, &principal);
if (ret)
krb5_err(context, 1, ret, "make_wellknown_name");
} else {
get_princ(context, &principal, cred_cache, argv[0]);
}

View File

@@ -72,6 +72,11 @@
#include <parse_time.h>
#include <err.h>
#include <krb5.h>
#include <heimbase.h>
#include <gssapi_mech.h>
#include <gssapi/gssapi_preauth.h>
#include "../lib/gssapi/preauth/pa-private.h"
#if defined(HAVE_SYS_IOCTL_H) && SunOS != 40
#include <sys/ioctl.h>

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;

View File

@@ -136,6 +136,29 @@ echo "Getting service ticket"
${kgetcred} ${server}@${R} || { exit 1; }
${kdestroy}
#
# Test GSS-API pre-authentication using SAnon. It will only succeed where there
# is FAST armor to authenticate the KDC, otherwise it will fail as SAnon does
# not provide mutual authentication (GSS_C_MUTUAL_FLAG).
#
for mech in sanon-x25519 spnego ; do
echo "Trying ${mech} pre-authentication with FAST armor"; > messages.log
${kinit} --fast-armor-cache=${acache} \
--anonymous --gss-mech=${mech} @$R 2>/dev/null || \
{ ec=1 ; eval "${testfailed}"; }
echo "Trying ${mech} pre-authentication with anonymous FAST armor"; > messages.log
${kinit} --pk-anon-fast-armor \
--anonymous --gss-mech=${mech} @$R 2>/dev/null || \
{ ec=1 ; eval "${testfailed}"; }
echo "Trying ${mech} pre-authentication with no FAST armor"; > messages.log
${kinit} \
--anonymous --gss-mech=${mech} @$R 2>/dev/null && \
{ ec=1 ; eval "${testfailed}"; }
done
#
# Use MIT client tools
#

View File

@@ -83,6 +83,10 @@
enable-http = true
synthetic_clients = true
enable_gss_preauth = true
gss_mechanisms_allowed = sanon-x25519
enable-pkinit = true
pkinit_identity = FILE:@srcdir@/../../lib/hx509/data/kdc.crt,@srcdir@/../../lib/hx509/data/kdc.key
pkinit_anchors = FILE:@srcdir@/../../lib/hx509/data/ca.crt