From 86554f5a7f81da1efa2849fa6961ca71ad3b8e90 Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Wed, 2 Jul 2014 20:24:49 -0400 Subject: [PATCH 1/6] Use correct value for anonymous flags The KDC Option and Ticket Flag for the anonymous extension were changed from 14 to 16 due to a conflict with S4U2Proxy in version 11 of the anonymous draft (now RFC6112). Fix the definitions --- lib/asn1/krb5.asn1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/asn1/krb5.asn1 b/lib/asn1/krb5.asn1 index c51cbbf6a..88c4f687a 100644 --- a/lib/asn1/krb5.asn1 +++ b/lib/asn1/krb5.asn1 @@ -338,8 +338,8 @@ TicketFlags ::= BIT STRING { hw-authent(11), transited-policy-checked(12), ok-as-delegate(13), - anonymous(14), - enc-pa-rep(15) + enc-pa-rep(15), + anonymous(16) } KDCOptions ::= BIT STRING { @@ -351,9 +351,9 @@ KDCOptions ::= BIT STRING { allow-postdate(5), postdated(6), renewable(8), - request-anonymous(14), + constrained-delegation(14), -- ms extension (aka cname-in-addl-tkt) canonicalize(15), - constrained-delegation(16), -- ms extension + request-anonymous(16), disable-transited-check(26), renewable-ok(27), enc-tkt-in-skey(28), From 5f2a93f5ca529bc38c465fe2d3eed22e8a4b9722 Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Wed, 2 Jul 2014 20:39:38 -0400 Subject: [PATCH 2/6] Recognize anonymous AS requests using bit 14 Check KDC Option bit 14 in addition to 16 when identifying anonymous AS-REQs. This provides compatibility with older heimdal releases. --- kdc/kerberos5.c | 21 ++++++++++++++------- kdc/pkinit.c | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index 6b6ea7d49..f30875903 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -432,7 +432,7 @@ pa_enc_chal_validate(kdc_request_t r, const PA_DATA *pa) heim_assert(r->armor_crypto != NULL, "ENC-CHAL called for non FAST"); - if (b->kdc_options.request_anonymous) { + if (_kdc_is_anon_request(b)) { ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; kdc_log(r->context, r->config, 0, "ENC-CHALL doesn't support anon"); return ret; @@ -555,7 +555,7 @@ pa_enc_ts_validate(kdc_request_t r, const PA_DATA *pa) Key *pa_key; char *str; - if (r->req.req_body.kdc_options.request_anonymous) { + if (_kdc_is_anon_request(&r->req.req_body)) { ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; _kdc_set_e_text(r, "ENC-TS doesn't support anon"); goto out; @@ -1656,12 +1656,12 @@ _kdc_as_rep(kdc_request_t r, */ if (_kdc_is_anonymous(context, r->client_princ)) { - if (!b->kdc_options.request_anonymous) { + if (!_kdc_is_anon_request(b)) { kdc_log(context, config, 0, "Anonymous ticket w/o anonymous flag"); ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; goto out; } - } else if (b->kdc_options.request_anonymous) { + } else if (_kdc_is_anon_request(b)) { kdc_log(context, config, 0, "Request for a anonymous ticket with non " "anonymous client name: %s", r->client_name); @@ -1810,7 +1810,7 @@ _kdc_as_rep(kdc_request_t r, * send requre preauth is its required or anon is requested, * anon is today only allowed via preauth mechanisms. */ - if (require_preauth_p(r) || b->kdc_options.request_anonymous) { + if (require_preauth_p(r) || _kdc_is_anon_request(b)) { ret = KRB5KDC_ERR_PREAUTH_REQUIRED; _kdc_set_e_text(r, "Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ"); goto out; @@ -1854,7 +1854,7 @@ _kdc_as_rep(kdc_request_t r, goto out; if(f.renew || f.validate || f.proxy || f.forwarded || f.enc_tkt_in_skey - || (f.request_anonymous && !config->allow_anonymous)) { + || (_kdc_is_anon_request(b) && !config->allow_anonymous)) { ret = KRB5KDC_ERR_BADOPTION; _kdc_set_e_text(r, "Bad KDC options"); goto out; @@ -1973,7 +1973,7 @@ _kdc_as_rep(kdc_request_t r, } } - if (f.request_anonymous) + if (_kdc_is_anon_request(b)) r->et.flags.anonymous = 1; if(b->addresses){ @@ -2320,3 +2320,10 @@ _kdc_tkt_add_if_relevant_ad(krb5_context context, return 0; } + +krb5_boolean +_kdc_is_anon_request(const KDC_REQ_BODY *b) +{ + return (b->kdc_options.request_anonymous || + (b->kdc_options.constrained_delegation && !b->additional_tickets)); +} diff --git a/kdc/pkinit.c b/kdc/pkinit.c index cd2067532..87621e371 100644 --- a/kdc/pkinit.c +++ b/kdc/pkinit.c @@ -573,7 +573,7 @@ _kdc_pk_rd_padata(krb5_context context, type = "PK-INIT-Win2k"; - if (req->req_body.kdc_options.request_anonymous) { + if (_kdc_is_anon_request(&req->req_body)) { ret = KRB5_KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED; krb5_set_error_message(context, ret, "Anon not supported in RSA mode"); @@ -719,7 +719,7 @@ _kdc_pk_rd_padata(krb5_context context, hx509_certs signer_certs; int flags = HX509_CMS_VS_ALLOW_DATA_OID_MISMATCH; /* BTMM */ - if (req->req_body.kdc_options.request_anonymous) + if (_kdc_is_anon_request(&req->req_body)) flags |= HX509_CMS_VS_ALLOW_ZERO_SIGNER; ret = hx509_cms_verify_signed(context->hx509ctx, @@ -804,7 +804,7 @@ _kdc_pk_rd_padata(krb5_context context, goto out; } - if (req->req_body.kdc_options.request_anonymous && + if (_kdc_is_anon_request(&req->req_body) && ap.clientPublicValue == NULL) { free_AuthPack(&ap); ret = KRB5_KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED; From bf7f31ee0959c20365b68b71540a66df005ba168 Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Wed, 2 Jul 2014 20:49:16 -0400 Subject: [PATCH 3/6] Include empty PKINIT-KX padata rfc6112 requires kdcs implementing anonymous PKINIT to include an empty PKINIT-KX padata in PREAUTH_REQUIRED messages. Including this improves compatibility with MIT kerberos. --- kdc/kerberos5.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index f30875903..757ca9af2 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -717,9 +717,14 @@ static const struct kdc_patypes pat[] = { KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", PA_ANNOUNCE, pa_pkinit_validate }, + { + KRB5_PADATA_PKINIT_KX, "Anonymous PK-INIT", PA_ANNOUNCE, + NULL + }, #else { KRB5_PADATA_PK_AS_REQ, "PK-INIT(ietf)", 0, NULL }, { KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", 0, NULL }, + { KRB5_PADATA_PKINIT_KX, "Anonymous PK-INIT", 0, NULL }, #endif { KRB5_PADATA_PA_PK_OCSP_RESPONSE , "OCSP", 0, NULL }, { From c2e2de7384c410c240cb54dc3637e2eb9e510ae6 Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Wed, 2 Jul 2014 21:00:18 -0400 Subject: [PATCH 4/6] When using PKINIT with DH, compute session key RFC6112 provides a method of computing a session key when the PKINIT DH is used, and mandates it for anonymous pkinit. The session key is computed using KRB-FX-CF2 from the reply key and a random key chosen by the kdc. The random key is provided to the client, which is supposed to verify that the session key was computed this way. --- kdc/pkinit.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/kdc/pkinit.c b/kdc/pkinit.c index 87621e371..66c27504a 100644 --- a/kdc/pkinit.c +++ b/kdc/pkinit.c @@ -1370,16 +1370,86 @@ _kdc_pk_mk_pa_reply(krb5_context context, if (rep.u.encKeyPack.length != size) krb5_abortx(context, "Internal ASN.1 encoder error"); - /* XXX KRB-FX-CF2 */ - ret = krb5_generate_random_keyblock(context, sessionetype, - sessionkey); - if (ret) { - free_PA_PK_AS_REP(&rep); - goto out; + /* generate the session key using the method from RFC6112 */ + { + krb5_keyblock kdc_contribution_key; + krb5_crypto reply_crypto; + krb5_crypto kdccont_crypto; + krb5_data p1 = { strlen("PKINIT"), "PKINIT"}; + krb5_data p2 = { strlen("KEYEXCHANGE"), "KEYEXCHANGE"}; + void *kckdata; + size_t kcklen; + EncryptedData kx; + void *kxdata; + size_t kxlen; + + ret = krb5_generate_random_keyblock(context, sessionetype, + &kdc_contribution_key); + if (ret) { + free_PA_PK_AS_REP(&rep); + goto out; + } + ret = krb5_crypto_init(context, &cp->reply_key, enctype, &reply_crypto); + if (ret) { + krb5_free_keyblock_contents(context, &kdc_contribution_key); + free_PA_PK_AS_REP(&rep); + goto out; + } + ret = krb5_crypto_init(context, &kdc_contribution_key, sessionetype, &kdccont_crypto); + if (ret) { + krb5_crypto_destroy(context, reply_crypto); + krb5_free_keyblock_contents(context, &kdc_contribution_key); + free_PA_PK_AS_REP(&rep); + goto out; + } + /* KRB-FX-CF2 */ + ret = krb5_crypto_fx_cf2(context, kdccont_crypto, reply_crypto, + &p1, &p2, sessionetype, sessionkey); + krb5_crypto_destroy(context, kdccont_crypto); + if (ret) { + krb5_crypto_destroy(context, reply_crypto); + krb5_free_keyblock_contents(context, &kdc_contribution_key); + free_PA_PK_AS_REP(&rep); + goto out; + } + ASN1_MALLOC_ENCODE(EncryptionKey, kckdata, kcklen, + &kdc_contribution_key, &size, ret); + krb5_free_keyblock_contents(context, &kdc_contribution_key); + if (ret) { + krb5_set_error_message(context, ret, "encoding of PKINIT-KX Key failed %d", ret); + krb5_crypto_destroy(context, reply_crypto); + free_PA_PK_AS_REP(&rep); + goto out; + } + if (kcklen != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + ret = krb5_encrypt_EncryptedData(context, reply_crypto, KRB5_KU_PA_PKINIT_KX, + kckdata, kcklen, 0, &kx); + krb5_crypto_destroy(context, reply_crypto); + free(kckdata); + if (ret) { + free_PA_PK_AS_REP(&rep); + goto out; + } + ASN1_MALLOC_ENCODE(EncryptedData, kxdata, kxlen, + &kx, &size, ret); + free_EncryptedData(&kx); + if (ret) { + krb5_set_error_message(context, ret, "encoding of PKINIT-KX failed %d", ret); + free_PA_PK_AS_REP(&rep); + goto out; + } + if (kxlen != size) + krb5_abortx(context, "Internal ASN.1 encoder error"); + /* Add PA-PKINIT-KX */ + ret = krb5_padata_add(context, md, KRB5_PADATA_PKINIT_KX, kxdata, kxlen); + if (ret) { + krb5_set_error_message(context, ret, + "Failed adding PKINIT-KX %d", ret); + free(buf); + goto out; + } } - - /* XXX Add PA-PKINIT-KX */ - } #define use_btmm_with_enckey 0 From f3789f8cc5b8f047a977754109966e0182e4b061 Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Sun, 6 Jul 2014 14:37:49 -0400 Subject: [PATCH 5/6] Document logic in _krb5_is_anon_request describe why we look at the different bits and fields --- kdc/kerberos5.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index 757ca9af2..ba0776961 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -2329,6 +2329,10 @@ _kdc_tkt_add_if_relevant_ad(krb5_context context, krb5_boolean _kdc_is_anon_request(const KDC_REQ_BODY *b) { + /* some versions of heimdal use bit 14 instead of 16 for + request_anonymous, as indicated in the anonymous draft prior to + version 11. Bit 14 is assigned to S4U2Proxy, but all S4U2Proxy + requests will have a second ticket; don't consider those anonymous */ return (b->kdc_options.request_anonymous || (b->kdc_options.constrained_delegation && !b->additional_tickets)); } From f07ee072883ffa4015abb671ea15d585539992b8 Mon Sep 17 00:00:00 2001 From: Chaskiel Grundman Date: Mon, 7 Jul 2014 12:35:43 -0400 Subject: [PATCH 6/6] Use anon realm for anonymous PKINIT When an AS request names the anonymous principal, use the anonymous realm in the response and ticket. --- kdc/kerberos5.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index ba0776961..f93a0108b 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -1872,7 +1872,11 @@ _kdc_as_rep(kdc_request_t r, rep.pvno = 5; rep.msg_type = krb_as_rep; - ret = copy_Realm(&r->client->entry.principal->realm, &rep.crealm); + if (_kdc_is_anonymous(context, r->client_princ)) { + Realm anon_realm=KRB5_ANON_REALM; + ret = copy_Realm(&anon_realm, &rep.crealm); + } else + ret = copy_Realm(&r->client->entry.principal->realm, &rep.crealm); if (ret) goto out; ret = _krb5_principal2principalname(&rep.cname, r->client->entry.principal);