diff --git a/doc/standardisation/draft-perez-krb-wg-gss-preauth-02.txt b/doc/standardisation/draft-perez-krb-wg-gss-preauth-02.txt new file mode 100644 index 000000000..dd8834931 --- /dev/null +++ b/doc/standardisation/draft-perez-krb-wg-gss-preauth-02.txt @@ -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] + diff --git a/kadmin/init.c b/kadmin/init.c index 47201424e..8b025e112 100644 --- a/kadmin/init.c +++ b/kadmin/init.c @@ -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, diff --git a/kdc/Makefile.am b/kdc/Makefile.am index 72ab590df..f4597857f 100644 --- a/kdc/Makefile.am +++ b/kdc/Makefile.am @@ -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) \ diff --git a/kdc/NTMakefile b/kdc/NTMakefile index cb20d26aa..8ae4b51da 100644 --- a/kdc/NTMakefile +++ b/kdc/NTMakefile @@ -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) diff --git a/kdc/default_config.c b/kdc/default_config.c index 496561d56..8de65ee35 100644 --- a/kdc/default_config.c +++ b/kdc/default_config.c @@ -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; diff --git a/kdc/digest-service.c b/kdc/digest-service.c index 6608d0a06..5bd7324ed 100644 --- a/kdc/digest-service.c +++ b/kdc/digest-service.c @@ -42,6 +42,7 @@ #include typedef struct pk_client_params pk_client_params; +typedef struct gss_client_params gss_client_params; struct DigestREQ; struct Kx509Request; diff --git a/kdc/fast.c b/kdc/fast.c index af232f88e..64b0f507a 100644 --- a/kdc/fast.c +++ b/kdc/fast.c @@ -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) { diff --git a/kdc/gss_preauth.c b/kdc/gss_preauth.c new file mode 100644 index 000000000..b23009da1 --- /dev/null +++ b/kdc/gss_preauth.c @@ -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 +#include +#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("") - 1, + "" +}; + +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; +} diff --git a/kdc/gss_preauth_authorizer_plugin.h b/kdc/gss_preauth_authorizer_plugin.h new file mode 100644 index 000000000..5394e654b --- /dev/null +++ b/kdc/gss_preauth_authorizer_plugin.h @@ -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 +#include + +/* + * @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 */ diff --git a/kdc/headers.h b/kdc/headers.h index 8f0d5c1bd..6f4d56ae9 100644 --- a/kdc/headers.h +++ b/kdc/headers.h @@ -98,6 +98,7 @@ #include #include #include +#include #ifndef NO_NTLM #include diff --git a/kdc/kdc.h b/kdc/kdc.h index 5953c23aa..694263cac 100644 --- a/kdc/kdc.h +++ b/kdc/kdc.h @@ -44,6 +44,7 @@ #include #include #include +#include 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; diff --git a/kdc/kdc_locl.h b/kdc/kdc_locl.h index d4427e36c..a2fdb06a6 100644 --- a/kdc/kdc_locl.h +++ b/kdc/kdc_locl.h @@ -41,6 +41,7 @@ #include "headers.h" typedef struct pk_client_params pk_client_params; +typedef struct gss_client_params gss_client_params; #include diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index 219e77547..f0cc2bdba 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -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, diff --git a/kdc/negotiate_token_validator.c b/kdc/negotiate_token_validator.c index df6ccdbde..ad5db1e3c 100644 --- a/kdc/negotiate_token_validator.c +++ b/kdc/negotiate_token_validator.c @@ -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; } diff --git a/kdc/windc.c b/kdc/windc.c index 05c59caf2..21e0839aa 100644 --- a/kdc/windc.c +++ b/kdc/windc.c @@ -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; } diff --git a/kuser/Makefile.am b/kuser/Makefile.am index 8ad4c3488..2ff2db3a1 100644 --- a/kuser/Makefile.am +++ b/kuser/Makefile.am @@ -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 \ diff --git a/kuser/kinit.1 b/kuser/kinit.1 index 1793530f8..b9c77c235 100644 --- a/kuser/kinit.1 +++ b/kuser/kinit.1 @@ -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. diff --git a/kuser/kinit.c b/kuser/kinit.c index a8062e2d6..b95fd91c5 100644 --- a/kuser/kinit.c +++ b/kuser/kinit.c @@ -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]); } diff --git a/kuser/kuser_locl.h b/kuser/kuser_locl.h index a0fcc9db6..b02e9ffa7 100644 --- a/kuser/kuser_locl.h +++ b/kuser/kuser_locl.h @@ -72,6 +72,11 @@ #include #include #include +#include + +#include +#include +#include "../lib/gssapi/preauth/pa-private.h" #if defined(HAVE_SYS_IOCTL_H) && SunOS != 40 #include diff --git a/lib/asn1/krb5.asn1 b/lib/asn1/krb5.asn1 index 6916e25f6..17f08247e 100644 --- a/lib/asn1/krb5.asn1 +++ b/lib/asn1/krb5.asn1 @@ -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 diff --git a/lib/gssapi/Makefile.am b/lib/gssapi/Makefile.am index cb4960840..33de29bd8 100644 --- a/lib/gssapi/Makefile.am +++ b/lib/gssapi/Makefile.am @@ -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 diff --git a/lib/gssapi/NTMakefile b/lib/gssapi/NTMakefile index fe103f272..d5493cb34 100644 --- a/lib/gssapi/NTMakefile +++ b/lib/gssapi/NTMakefile @@ -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 diff --git a/lib/gssapi/gssapi/gssapi.h b/lib/gssapi/gssapi/gssapi.h index 05d96c347..164c80343 100644 --- a/lib/gssapi/gssapi/gssapi.h +++ b/lib/gssapi/gssapi/gssapi.h @@ -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__)) diff --git a/lib/gssapi/gssapi/gssapi_preauth.h b/lib/gssapi/gssapi/gssapi_preauth.h new file mode 100644 index 000000000..6a0a07130 --- /dev/null +++ b/lib/gssapi/gssapi/gssapi_preauth.h @@ -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 +#include + +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_ */ diff --git a/lib/gssapi/libgssapi-exports.def b/lib/gssapi/libgssapi-exports.def index 10e487f13..3a14e30d3 100644 --- a/lib/gssapi/libgssapi-exports.def +++ b/lib/gssapi/libgssapi-exports.def @@ -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. diff --git a/lib/gssapi/mech/gss_mech_switch.c b/lib/gssapi/mech/gss_mech_switch.c index 19a0824ea..6ae64641a 100644 --- a/lib/gssapi/mech/gss_mech_switch.c +++ b/lib/gssapi/mech/gss_mech_switch.c @@ -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; +} diff --git a/lib/gssapi/preauth/README.md b/lib/gssapi/preauth/README.md new file mode 100644 index 000000000..500270746 --- /dev/null +++ b/lib/gssapi/preauth/README.md @@ -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. diff --git a/lib/gssapi/preauth/pa_client.c b/lib/gssapi/preauth/pa_client.c new file mode 100644 index 000000000..bd6f4f754 --- /dev/null +++ b/lib/gssapi/preauth/pa_client.c @@ -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 + +#include + +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); +} diff --git a/lib/gssapi/preauth/pa_common.c b/lib/gssapi/preauth/pa_common.c new file mode 100644 index 000000000..814d1f01b --- /dev/null +++ b/lib/gssapi/preauth/pa_common.c @@ -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 + +#include + +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); + } +} diff --git a/lib/gssapi/version-script.map b/lib/gssapi/version-script.map index 0a731c430..2ea9e47c5 100644 --- a/lib/gssapi/version-script.map +++ b/lib/gssapi/version-script.map @@ -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; diff --git a/lib/krb5/init_creds_pw.c b/lib/krb5/init_creds_pw.c index 8c3b7fa6b..dc1eaf54c 100644 --- a/lib/krb5/init_creds_pw.c +++ b/lib/krb5/init_creds_pw.c @@ -38,6 +38,21 @@ #include #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; +} diff --git a/lib/krb5/krb5.conf.5 b/lib/krb5/krb5.conf.5 index 73679b153..ba0a21e44 100644 --- a/lib/krb5/krb5.conf.5 +++ b/lib/krb5/krb5.conf.5 @@ -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 diff --git a/lib/krb5/krb5.h b/lib/krb5/krb5.h index 9f9d563e4..d22fc8cbf 100644 --- a/lib/krb5/krb5.h +++ b/lib/krb5/krb5.h @@ -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") diff --git a/lib/krb5/krb5_err.et b/lib/krb5/krb5_err.et index 3bb1d3845..13543a357 100644 --- a/lib/krb5/krb5_err.et +++ b/lib/krb5/krb5_err.et @@ -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" diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index 87c36e86d..600e2e000 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -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 #include @@ -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 #include "heim_threads.h" diff --git a/lib/krb5/libkrb5-exports.def.in b/lib/krb5/libkrb5-exports.def.in index f9b605eaa..a2944e331 100644 --- a/lib/krb5/libkrb5-exports.def.in +++ b/lib/krb5/libkrb5-exports.def.in @@ -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 diff --git a/lib/krb5/principal.c b/lib/krb5/principal.c index 144966f24..caf110dcc 100644 --- a/lib/krb5/principal.c +++ b/lib/krb5/principal.c @@ -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) { diff --git a/lib/krb5/version-script.map b/lib/krb5/version-script.map index 31de963f3..d6312df4f 100644 --- a/lib/krb5/version-script.map +++ b/lib/krb5/version-script.map @@ -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; diff --git a/tests/kdc/check-fast.in b/tests/kdc/check-fast.in index 4d3577c55..00792d8d9 100644 --- a/tests/kdc/check-fast.in +++ b/tests/kdc/check-fast.in @@ -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 # diff --git a/tests/kdc/krb5.conf.in b/tests/kdc/krb5.conf.in index 3e6e810df..19b4e3ef6 100644 --- a/tests/kdc/krb5.conf.in +++ b/tests/kdc/krb5.conf.in @@ -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