From 5edc6de93196b4f07da6695a4b271a067000c84d Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Tue, 31 Jan 2017 17:02:34 -0500 Subject: [PATCH] Add PKINIT client support for freshness token Send an empty PA_AS_FRESHNESS padata item in unauthenticated AS requests to indicate support for RFC 8070. If the KDC includes a PA_AS_FRESHNESS value in its method data, echo it back in the new freshnessToken field of pkAuthenticator ticket: 8648 (cherry picked from commit 085785362e01467cb25c79a90dcebfba9ea019d8) --- doc/user/user_commands/kinit.rst | 3 +++ src/include/k5-int-pkinit.h | 1 + src/include/krb5/krb5.hin | 1 + src/lib/krb5/asn.1/asn1_k_encode.c | 5 ++++- src/lib/krb5/krb/get_in_tkt.c | 12 ++++++++---- src/lib/krb5/krb/init_creds_ctx.h | 2 +- src/plugins/preauth/pkinit/pkinit.h | 3 +++ src/plugins/preauth/pkinit/pkinit_clnt.c | 19 ++++++++++++++++++- src/plugins/preauth/pkinit/pkinit_lib.c | 3 +++ src/plugins/preauth/pkinit/pkinit_trace.h | 2 ++ src/tests/asn.1/ktest.c | 4 ++++ src/tests/asn.1/pkinit_encode.out | 2 +- src/tests/asn.1/pkinit_trval.out | 1 + 13 files changed, 50 insertions(+), 8 deletions(-) diff --git a/doc/user/user_commands/kinit.rst b/doc/user/user_commands/kinit.rst index 3f9d5340f..1f696920f 100644 --- a/doc/user/user_commands/kinit.rst +++ b/doc/user/user_commands/kinit.rst @@ -197,6 +197,9 @@ OPTIONS specify use of RSA, rather than the default Diffie-Hellman protocol + **disable_freshness**\ [**=yes**] + disable sending freshness tokens (for testing purposes only) + ENVIRONMENT ----------- diff --git a/src/include/k5-int-pkinit.h b/src/include/k5-int-pkinit.h index 7b2f595cb..4622a629e 100644 --- a/src/include/k5-int-pkinit.h +++ b/src/include/k5-int-pkinit.h @@ -42,6 +42,7 @@ typedef struct _krb5_pk_authenticator { krb5_timestamp ctime; krb5_int32 nonce; /* (0..4294967295) */ krb5_checksum paChecksum; + krb5_data *freshnessToken; } krb5_pk_authenticator; /* PKAuthenticator draft9 */ diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index e81bb0a6d..833e72335 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -1879,6 +1879,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype, #define KRB5_PADATA_OTP_PIN_CHANGE 144 /**< RFC 6560 section 4.3 */ #define KRB5_PADATA_PKINIT_KX 147 /**< RFC 6112 */ #define KRB5_ENCPADATA_REQ_ENC_PA_REP 149 /**< RFC 6806 */ +#define KRB5_PADATA_AS_FRESHNESS 150 /**< RFC 8070 */ #define KRB5_SAM_USE_SAD_AS_KEY 0x80000000 #define KRB5_SAM_SEND_ENCRYPTED_SAD 0x40000000 diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c index 889460989..3b23fe34a 100644 --- a/src/lib/krb5/asn.1/asn1_k_encode.c +++ b/src/lib/krb5/asn.1/asn1_k_encode.c @@ -1442,9 +1442,12 @@ DEFFIELD(pk_authenticator_1, krb5_pk_authenticator, ctime, 1, kerberos_time); DEFFIELD(pk_authenticator_2, krb5_pk_authenticator, nonce, 2, int32); DEFFIELD(pk_authenticator_3, krb5_pk_authenticator, paChecksum, 3, ostring_checksum); +DEFFIELD(pk_authenticator_4, krb5_pk_authenticator, freshnessToken, 4, + opt_ostring_data_ptr); static const struct atype_info *pk_authenticator_fields[] = { &k5_atype_pk_authenticator_0, &k5_atype_pk_authenticator_1, - &k5_atype_pk_authenticator_2, &k5_atype_pk_authenticator_3 + &k5_atype_pk_authenticator_2, &k5_atype_pk_authenticator_3, + &k5_atype_pk_authenticator_4 }; DEFSEQTYPE(pk_authenticator, krb5_pk_authenticator, pk_authenticator_fields); diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index 47a00bf2c..1d96ff163 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -895,7 +895,7 @@ krb5_init_creds_init(krb5_context context, ctx->request = k5alloc(sizeof(krb5_kdc_req), &code); if (code != 0) goto cleanup; - ctx->enc_pa_rep_permitted = TRUE; + ctx->info_pa_permitted = TRUE; code = krb5_copy_principal(context, client, &ctx->request->client); if (code != 0) goto cleanup; @@ -1389,7 +1389,11 @@ init_creds_step_request(krb5_context context, krb5_free_data(context, ctx->encoded_previous_request); ctx->encoded_previous_request = NULL; } - if (ctx->enc_pa_rep_permitted) { + if (ctx->info_pa_permitted) { + code = add_padata(&ctx->request->padata, KRB5_PADATA_AS_FRESHNESS, + NULL, 0); + if (code) + goto cleanup; code = add_padata(&ctx->request->padata, KRB5_ENCPADATA_REQ_ENC_PA_REP, NULL, 0); } @@ -1530,7 +1534,7 @@ init_creds_step_reply(krb5_context context, ctx->selected_preauth_type == KRB5_PADATA_NONE) { /* The KDC didn't like our informational padata (probably a pre-1.7 * MIT krb5 KDC). Retry without it. */ - ctx->enc_pa_rep_permitted = FALSE; + ctx->info_pa_permitted = FALSE; ctx->restarted = TRUE; code = restart_init_creds_loop(context, ctx, FALSE); } else if (reply_code == KDC_ERR_PREAUTH_EXPIRED) { @@ -1574,7 +1578,7 @@ init_creds_step_reply(krb5_context context, goto cleanup; /* Reset per-realm negotiation state. */ ctx->restarted = FALSE; - ctx->enc_pa_rep_permitted = TRUE; + ctx->info_pa_permitted = TRUE; code = restart_init_creds_loop(context, ctx, FALSE); } else { if (retry && ctx->selected_preauth_type != KRB5_PADATA_NONE) { diff --git a/src/lib/krb5/krb/init_creds_ctx.h b/src/lib/krb5/krb/init_creds_ctx.h index fe769685b..b19410a13 100644 --- a/src/lib/krb5/krb/init_creds_ctx.h +++ b/src/lib/krb5/krb/init_creds_ctx.h @@ -58,7 +58,7 @@ struct _krb5_init_creds_context { krb5_data s2kparams; krb5_keyblock as_key; krb5_enctype etype; - krb5_boolean enc_pa_rep_permitted; + krb5_boolean info_pa_permitted; krb5_boolean restarted; struct krb5_responder_context_st rctx; krb5_preauthtype selected_preauth_type; diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h index f3de9ad7a..8489a3e23 100644 --- a/src/plugins/preauth/pkinit/pkinit.h +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -148,6 +148,7 @@ typedef struct _pkinit_plg_opts { int allow_upn; /* allow UPN-SAN instead of pkinit-SAN */ int dh_or_rsa; /* selects DH or RSA based pkinit */ int require_crl_checking; /* require CRL for a CA (default is false) */ + int disable_freshness; /* disable freshness token on client for testing */ int dh_min_bits; /* minimum DH modulus size allowed */ } pkinit_plg_opts; @@ -162,6 +163,7 @@ typedef struct _pkinit_req_opts { int require_crl_checking; int dh_size; /* initial request DH modulus size (default=1024) */ int require_hostname_match; + int disable_freshness; } pkinit_req_opts; /* @@ -214,6 +216,7 @@ struct _pkinit_req_context { int identity_initialized; int identity_prompted; krb5_error_code identity_prompt_retval; + krb5_data *freshness_token; }; typedef struct _pkinit_req_context *pkinit_req_context; diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c index f1bc6b21d..9483d69e5 100644 --- a/src/plugins/preauth/pkinit/pkinit_clnt.c +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -231,6 +231,8 @@ pkinit_as_req_create(krb5_context context, auth_pack.pkAuthenticator.cusec = cusec; auth_pack.pkAuthenticator.nonce = nonce; auth_pack.pkAuthenticator.paChecksum = *cksum; + if (!reqctx->opts->disable_freshness) + auth_pack.pkAuthenticator.freshnessToken = reqctx->freshness_token; auth_pack.clientDHNonce.length = 0; auth_pack.clientPublicValue = &info; auth_pack.supportedKDFs = (krb5_data **)supported_kdf_alg_ids; @@ -1162,6 +1164,7 @@ pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata, pkinit_context plgctx = (pkinit_context)moddata; pkinit_req_context reqctx = (pkinit_req_context)modreq; krb5_keyblock as_key; + krb5_data d; pkiDebug("pkinit_client_process %p %p %p %p\n", context, plgctx, reqctx, request); @@ -1174,6 +1177,12 @@ pkinit_client_process(krb5_context context, krb5_clpreauth_moddata moddata, case KRB5_PADATA_PKINIT_KX: reqctx->rfc6112_kdc = 1; return 0; + case KRB5_PADATA_AS_FRESHNESS: + TRACE_PKINIT_CLIENT_FRESHNESS_TOKEN(context); + krb5_free_data(context, reqctx->freshness_token); + reqctx->freshness_token = NULL; + d = make_data(in_padata->contents, in_padata->length); + return krb5_copy_data(context, &d, &reqctx->freshness_token); case KRB5_PADATA_PK_AS_REQ: reqctx->rfc4556_kdc = 1; pkiDebug("processing KRB5_PADATA_PK_AS_REQ\n"); @@ -1359,7 +1368,7 @@ cleanup: static int pkinit_client_get_flags(krb5_context kcontext, krb5_preauthtype patype) { - if (patype == KRB5_PADATA_PKINIT_KX) + if (patype == KRB5_PADATA_PKINIT_KX || patype == KRB5_PADATA_AS_FRESHNESS) return PA_INFO; return PA_REAL; } @@ -1376,6 +1385,7 @@ static krb5_preauthtype supported_client_pa_types[] = { KRB5_PADATA_PK_AS_REP_OLD, KRB5_PADATA_PK_AS_REQ_OLD, KRB5_PADATA_PKINIT_KX, + KRB5_PADATA_AS_FRESHNESS, 0 }; @@ -1400,6 +1410,7 @@ pkinit_client_req_init(krb5_context context, reqctx->opts = NULL; reqctx->idctx = NULL; reqctx->idopts = NULL; + reqctx->freshness_token = NULL; retval = pkinit_init_req_opts(&reqctx->opts); if (retval) @@ -1410,6 +1421,7 @@ pkinit_client_req_init(krb5_context context, reqctx->opts->dh_or_rsa = plgctx->opts->dh_or_rsa; reqctx->opts->allow_upn = plgctx->opts->allow_upn; reqctx->opts->require_crl_checking = plgctx->opts->require_crl_checking; + reqctx->opts->disable_freshness = plgctx->opts->disable_freshness; retval = pkinit_init_req_crypto(&reqctx->cryptoctx); if (retval) @@ -1468,6 +1480,8 @@ pkinit_client_req_fini(krb5_context context, krb5_clpreauth_moddata moddata, if (reqctx->idopts != NULL) pkinit_fini_identity_opts(reqctx->idopts); + krb5_free_data(context, reqctx->freshness_token); + free(reqctx); return; } @@ -1580,6 +1594,9 @@ handle_gic_opt(krb5_context context, pkiDebug("Setting flag to use RSA_PROTOCOL\n"); plgctx->opts->dh_or_rsa = RSA_PROTOCOL; } + } else if (strcmp(attr, "disable_freshness") == 0) { + if (strcmp(value, "yes") == 0) + plgctx->opts->disable_freshness = 1; } return 0; } diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c index 2f88545da..d5858c424 100644 --- a/src/plugins/preauth/pkinit/pkinit_lib.c +++ b/src/plugins/preauth/pkinit/pkinit_lib.c @@ -82,6 +82,8 @@ pkinit_init_plg_opts(pkinit_plg_opts **plgopts) opts->dh_or_rsa = DH_PROTOCOL; opts->allow_upn = 0; opts->require_crl_checking = 0; + opts->require_freshness = 0; + opts->disable_freshness = 0; opts->dh_min_bits = PKINIT_DEFAULT_DH_MIN_BITS; @@ -145,6 +147,7 @@ free_krb5_auth_pack(krb5_auth_pack **in) free((*in)->clientPublicValue); } free((*in)->pkAuthenticator.paChecksum.contents); + krb5_free_data(NULL, (*in)->pkAuthenticator.freshnessToken); if ((*in)->supportedCMSTypes != NULL) free_krb5_algorithm_identifiers(&((*in)->supportedCMSTypes)); if ((*in)->supportedKDFs) { diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h index 2d95da94a..7f95206c0 100644 --- a/src/plugins/preauth/pkinit/pkinit_trace.h +++ b/src/plugins/preauth/pkinit/pkinit_trace.h @@ -41,6 +41,8 @@ TRACE(c, "PKINIT client found no acceptable EKU in KDC cert") #define TRACE_PKINIT_CLIENT_EKU_SKIP(c) \ TRACE(c, "PKINIT client skipping EKU check due to configuration") +#define TRACE_PKINIT_CLIENT_FRESHNESS_TOKEN(c) \ + TRACE(c, "PKINIT client received freshness token from KDC") #define TRACE_PKINIT_CLIENT_KDF_ALG(c, kdf, keyblock) \ TRACE(c, "PKINIT client used KDF {hexdata} to compute reply key " \ "{keyblock}", kdf, keyblock) diff --git a/src/tests/asn.1/ktest.c b/src/tests/asn.1/ktest.c index 43084cbbd..cf63f3f66 100644 --- a/src/tests/asn.1/ktest.c +++ b/src/tests/asn.1/ktest.c @@ -725,6 +725,8 @@ ktest_make_sample_pk_authenticator(krb5_pk_authenticator *p) ktest_make_sample_checksum(&p->paChecksum); /* We don't encode the checksum type, only the contents. */ p->paChecksum.checksum_type = 0; + p->freshnessToken = ealloc(sizeof(krb5_data)); + ktest_make_sample_data(p->freshnessToken); } static void @@ -1651,6 +1653,8 @@ ktest_empty_pk_authenticator(krb5_pk_authenticator *p) { ktest_empty_checksum(&p->paChecksum); p->paChecksum.contents = NULL; + krb5_free_data(NULL, p->freshnessToken); + p->freshnessToken = NULL; } static void diff --git a/src/tests/asn.1/pkinit_encode.out b/src/tests/asn.1/pkinit_encode.out index 463128de0..3b0f7190a 100644 --- a/src/tests/asn.1/pkinit_encode.out +++ b/src/tests/asn.1/pkinit_encode.out @@ -4,7 +4,7 @@ encode_krb5_pa_pk_as_rep(dhInfo): A0 28 30 26 80 08 6B 72 62 35 64 61 74 61 A1 0 encode_krb5_pa_pk_as_rep(encKeyPack): 81 08 6B 72 62 35 64 61 74 61 encode_krb5_pa_pk_as_rep_draft9(dhSignedData): 80 08 6B 72 62 35 64 61 74 61 encode_krb5_pa_pk_as_rep_draft9(encKeyPack): 81 08 6B 72 62 35 64 61 74 61 -encode_krb5_auth_pack: 30 81 93 A0 29 30 27 A0 05 02 03 01 E2 40 A1 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A2 03 02 01 2A A3 06 04 04 31 32 33 34 A1 22 30 20 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 03 09 00 6B 72 62 35 64 61 74 61 A2 24 30 22 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 30 0B 06 09 2A 86 48 86 F7 12 01 02 02 A3 0A 04 08 6B 72 62 35 64 61 74 61 A4 10 30 0E 30 0C A0 0A 06 08 6B 72 62 35 64 61 74 61 +encode_krb5_auth_pack: 30 81 9F A0 35 30 33 A0 05 02 03 01 E2 40 A1 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A2 03 02 01 2A A3 06 04 04 31 32 33 34 A4 0A 04 08 6B 72 62 35 64 61 74 61 A1 22 30 20 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 03 09 00 6B 72 62 35 64 61 74 61 A2 24 30 22 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 30 0B 06 09 2A 86 48 86 F7 12 01 02 02 A3 0A 04 08 6B 72 62 35 64 61 74 61 A4 10 30 0E 30 0C A0 0A 06 08 6B 72 62 35 64 61 74 61 encode_krb5_auth_pack_draft9: 30 75 A0 4F 30 4D A0 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A1 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A2 05 02 03 01 E2 40 A3 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A A4 03 02 01 2A A1 22 30 20 30 13 06 09 2A 86 48 86 F7 12 01 02 02 04 06 70 61 72 61 6D 73 03 09 00 6B 72 62 35 64 61 74 61 encode_krb5_kdc_dh_key_info: 30 25 A0 0B 03 09 00 6B 72 62 35 64 61 74 61 A1 03 02 01 2A A2 11 18 0F 31 39 39 34 30 36 31 30 30 36 30 33 31 37 5A encode_krb5_reply_key_pack: 30 26 A0 13 30 11 A0 03 02 01 01 A1 0A 04 08 31 32 33 34 35 36 37 38 A1 0F 30 0D A0 03 02 01 01 A1 06 04 04 31 32 33 34 diff --git a/src/tests/asn.1/pkinit_trval.out b/src/tests/asn.1/pkinit_trval.out index 58d870631..f9edbe154 100644 --- a/src/tests/asn.1/pkinit_trval.out +++ b/src/tests/asn.1/pkinit_trval.out @@ -57,6 +57,7 @@ encode_krb5_auth_pack: . . [1] [Generalized Time] "19940610060317Z" . . [2] [Integer] 42 . . [3] [Octet String] "1234" +. . [4] [Octet String] "krb5data" . [1] [Sequence/Sequence Of] . . [Sequence/Sequence Of] . . . [Object Identifier] <9>