From ghudson at mit.edu Wed Mar 5 14:20:58 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Wed, 5 Mar 2025 14:20:58 -0500 (EST) Subject: krb5 commit: Use k5_path_join() in krb5int_open_plugin_dirs() Message-ID: <20250305192058.F2CBA102C09@krbdev.mit.edu> https://github.com/krb5/krb5/commit/80001c8b7b00773a9eb59050aff3b0056080aaf2 commit 80001c8b7b00773a9eb59050aff3b0056080aaf2 Author: Ken Hornstein Date: Sat Mar 1 23:02:58 2025 -0500 Use k5_path_join() in krb5int_open_plugin_dirs() Simplify and improve the portability of krb5int_open_plugin_dirs() using k5_path_join(). (There is no immediate practical benefit as this function is only used to find kdb5, authdata, and locate plugin modules.) [ghudson at mit.edu: further simplified code; edited commit message] src/util/support/plugins.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/util/support/plugins.c b/src/util/support/plugins.c index 678d052e7..752f08519 100644 --- a/src/util/support/plugins.c +++ b/src/util/support/plugins.c @@ -403,12 +403,8 @@ krb5int_open_plugin_dirs (const char * const *dirnames, struct plugin_file_handle *handle = NULL; char *filepath = NULL; - if (!err) { - if (asprintf(&filepath, "%s/%s", dirnames[i], filenames[j]) < 0) { - filepath = NULL; - err = ENOMEM; - } - } + if (!err) + err = k5_path_join(dirnames[i], filenames[j], &filepath); if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { err = krb5int_plugin_file_handle_array_add (&h, &count, handle); @@ -432,10 +428,7 @@ krb5int_open_plugin_dirs (const char * const *dirnames, strcmp(fnames[j], "..") == 0) continue; - if (asprintf(&filepath, "%s/%s", dirnames[i], fnames[j]) < 0) { - filepath = NULL; - err = ENOMEM; - } + err = k5_path_join(dirnames[i], fnames[j], &filepath); if (!err && krb5int_open_plugin(filepath, &handle, ep) == 0) { err = krb5int_plugin_file_handle_array_add(&h, &count, From ghudson at mit.edu Tue Mar 11 00:08:43 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Tue, 11 Mar 2025 00:08:43 -0400 (EDT) Subject: krb5 commit: Display NetBIOS ticket addresses in klist Message-ID: <20250311040843.7F40A102C5C@krbdev.mit.edu> https://github.com/krb5/krb5/commit/c1e0348c95f00c352faeb849b6e7fabb57b8b159 commit c1e0348c95f00c352faeb849b6e7fabb57b8b159 Author: Ivan Korytov Date: Wed Mar 5 15:30:10 2025 +0300 Display NetBIOS ticket addresses in klist [ghudson at mit.edu: simplified code] ticket: 9165 (new) src/clients/klist/klist.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/clients/klist/klist.c b/src/clients/klist/klist.c index 92d9d6dbd..59a02bedc 100644 --- a/src/clients/klist/klist.c +++ b/src/clients/klist/klist.c @@ -832,8 +832,9 @@ one_addr(krb5_address *a) struct sockaddr_storage ss; struct sockaddr_in *sinp; struct sockaddr_in6 *sin6p; - int err; + int err, i; char namebuf[NI_MAXHOST]; + const uint8_t *p; memset(&ss, 0, sizeof(ss)); @@ -858,6 +859,16 @@ one_addr(krb5_address *a) sin6p->sin6_family = AF_INET6; memcpy(&sin6p->sin6_addr, a->contents, 16); break; + case ADDRTYPE_NETBIOS: + if (a->length != 16) { + printf(_("broken address (type %d length %d)"), + a->addrtype, a->length); + return; + } + p = a->contents; + for (i = 0; i < 15 && p[i] != '\0' && p[i] != ' '; i++) + putchar(p[i]); + return; default: printf(_("unknown addrtype %d"), a->addrtype); return; From ghudson at mit.edu Thu Mar 13 19:17:41 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Thu, 13 Mar 2025 19:17:41 -0400 (EDT) Subject: krb5 commit: Add PKINIT paChecksum2 from MS-PKCA v20230920 Message-ID: <20250313231741.2D1C2102BCA@krbdev.mit.edu> https://github.com/krb5/krb5/commit/310793ba63782af5ffa3a95d20e41f8f03ca7e00 commit 310793ba63782af5ffa3a95d20e41f8f03ca7e00 Author: Julien Rische Date: Tue Jan 14 13:31:11 2025 +0100 Add PKINIT paChecksum2 from MS-PKCA v20230920 In 2023, Microsoft updated MS-PKCA to add the optional paChecksum2 element in the PKAuthenticator sequence. This checksum accepts SHA-1, SHA-256, SHA-384, and SHA-512 digests. In Windows Server 2025, this checksum becomes mandatory when using PKINIT with FFDH (but strangely not with ECDH if SHA-1 is configured as allowed). [ghudson at mit.edu: refactored crypto interfaces to reduce complexity of calling code] ticket: 9166 (new) src/include/k5-int-pkinit.h | 25 +++-- src/lib/krb5/asn.1/asn1_k_encode.c | 18 +++- src/plugins/preauth/pkinit/pkinit.h | 1 + src/plugins/preauth/pkinit/pkinit_clnt.c | 41 +++----- src/plugins/preauth/pkinit/pkinit_constants.c | 42 ++++++-- src/plugins/preauth/pkinit/pkinit_crypto.h | 24 ++++- src/plugins/preauth/pkinit/pkinit_crypto_openssl.c | 116 ++++++++++++++++++++- src/plugins/preauth/pkinit/pkinit_kdf_test.c | 6 +- src/plugins/preauth/pkinit/pkinit_lib.c | 16 ++- src/plugins/preauth/pkinit/pkinit_srv.c | 38 ++----- src/plugins/preauth/pkinit/pkinit_trace.h | 5 +- src/tests/asn.1/krb5_decode_test.c | 2 +- src/tests/asn.1/ktest.c | 7 +- src/tests/asn.1/ktest_equal.c | 2 +- src/tests/asn.1/pkinit_encode.out | 2 +- src/tests/asn.1/pkinit_trval.out | 2 +- 16 files changed, 251 insertions(+), 96 deletions(-) diff --git a/src/include/k5-int-pkinit.h b/src/include/k5-int-pkinit.h index 915904e51..cf6b1f99c 100644 --- a/src/include/k5-int-pkinit.h +++ b/src/include/k5-int-pkinit.h @@ -36,21 +36,28 @@ * pkinit structures */ -/* PKAuthenticator */ -typedef struct _krb5_pk_authenticator { - krb5_int32 cusec; /* (0..999999) */ - krb5_timestamp ctime; - krb5_int32 nonce; /* (0..4294967295) */ - krb5_checksum paChecksum; - krb5_data *freshnessToken; -} krb5_pk_authenticator; - /* AlgorithmIdentifier */ typedef struct _krb5_algorithm_identifier { krb5_data algorithm; /* OID */ krb5_data parameters; /* Optional */ } krb5_algorithm_identifier; +/* PAChecksum2 */ +typedef struct _krb5_pachecksum2 { + krb5_data checksum; + krb5_algorithm_identifier algorithmIdentifier; +} krb5_pachecksum2; + +/* PKAuthenticator */ +typedef struct _krb5_pk_authenticator { + krb5_int32 cusec; /* (0..999999) */ + krb5_timestamp ctime; + krb5_int32 nonce; /* (0..4294967295) */ + krb5_data paChecksum; + krb5_data *freshnessToken; /* Optional */ + krb5_pachecksum2 *paChecksum2; /* Optional */ +} krb5_pk_authenticator; + /** AuthPack from RFC 4556*/ typedef struct _krb5_auth_pack { krb5_pk_authenticator pkAuthenticator; diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c index 1a250c98c..f30cbd77c 100644 --- a/src/lib/krb5/asn.1/asn1_k_encode.c +++ b/src/lib/krb5/asn.1/asn1_k_encode.c @@ -1394,20 +1394,30 @@ DEFSEQTYPE(pkinit_supp_pub_info, krb5_pkinit_supp_pub_info, MAKE_ENCODER(encode_krb5_pkinit_supp_pub_info, pkinit_supp_pub_info); MAKE_ENCODER(encode_krb5_sp80056a_other_info, sp80056a_other_info); -/* A krb5_checksum encoded as an OCTET STRING, for PKAuthenticator. */ -DEFCOUNTEDTYPE(ostring_checksum, krb5_checksum, contents, length, octetstring); +DEFFIELD(pachecksum2_0, krb5_pachecksum2, checksum, 0, ostring_data); +DEFFIELD(pachecksum2_1, krb5_pachecksum2, algorithmIdentifier, 1, + algorithm_identifier); +static const struct atype_info *pachecksum2_fields[] = { + &k5_atype_pachecksum2_0, &k5_atype_pachecksum2_1 +}; +DEFSEQTYPE(pachecksum2, krb5_pachecksum2, pachecksum2_fields); + +DEFPTRTYPE(pachecksum2_ptr, pachecksum2); +DEFOPTIONALZEROTYPE(opt_pachecksum2_ptr, pachecksum2_ptr); DEFFIELD(pk_authenticator_0, krb5_pk_authenticator, cusec, 0, int32); 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); + ostring_data); DEFFIELD(pk_authenticator_4, krb5_pk_authenticator, freshnessToken, 4, opt_ostring_data_ptr); +DEFFIELD(pk_authenticator_5, krb5_pk_authenticator, paChecksum2, 5, + opt_pachecksum2_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_4 + &k5_atype_pk_authenticator_4, &k5_atype_pk_authenticator_5 }; DEFSEQTYPE(pk_authenticator, krb5_pk_authenticator, pk_authenticator_fields); diff --git a/src/plugins/preauth/pkinit/pkinit.h b/src/plugins/preauth/pkinit/pkinit.h index 200e75afe..40e712b24 100644 --- a/src/plugins/preauth/pkinit/pkinit.h +++ b/src/plugins/preauth/pkinit/pkinit.h @@ -340,6 +340,7 @@ void free_krb5_external_principal_identifier(krb5_external_principal_identifier void free_krb5_algorithm_identifiers(krb5_algorithm_identifier ***in); void free_krb5_algorithm_identifier(krb5_algorithm_identifier *in); void free_krb5_kdc_dh_key_info(krb5_kdc_dh_key_info **in); +void free_pachecksum2(krb5_context context, krb5_pachecksum2 **in); krb5_error_code pkinit_copy_krb5_data(krb5_data *dst, const krb5_data *src); diff --git a/src/plugins/preauth/pkinit/pkinit_clnt.c b/src/plugins/preauth/pkinit/pkinit_clnt.c index 9312ff4b1..94beeb8cf 100644 --- a/src/plugins/preauth/pkinit/pkinit_clnt.c +++ b/src/plugins/preauth/pkinit/pkinit_clnt.c @@ -55,10 +55,9 @@ use_content_info(krb5_context context, pkinit_req_context req, static krb5_error_code pkinit_as_req_create(krb5_context context, pkinit_context plgctx, pkinit_req_context reqctx, krb5_timestamp ctsec, - krb5_int32 cusec, krb5_ui_4 nonce, - const krb5_checksum *cksum, - krb5_principal client, krb5_principal server, - krb5_data **as_req); + krb5_int32 cusec, krb5_ui_4 nonce, const krb5_data *cksum, + const krb5_pachecksum2 *cksum2, krb5_principal client, + krb5_principal server, krb5_data **as_req); static krb5_error_code pkinit_as_rep_parse(krb5_context context, pkinit_context plgctx, @@ -88,7 +87,8 @@ pa_pkinit_gen_req(krb5_context context, krb5_timestamp ctsec = 0; krb5_int32 cusec = 0; krb5_ui_4 nonce = 0; - krb5_checksum cksum; + krb5_data cksum = empty_data(); + krb5_pachecksum2 *cksum2 = NULL; krb5_data *der_req = NULL; krb5_pa_data **return_pa_data = NULL; @@ -117,15 +117,10 @@ pa_pkinit_gen_req(krb5_context context, goto cleanup; } - retval = krb5_c_make_checksum(context, CKSUMTYPE_SHA1, NULL, 0, der_req, - &cksum); + retval = crypto_generate_checksums(context, der_req, &cksum, &cksum2); if (retval) goto cleanup; - TRACE_PKINIT_CLIENT_REQ_CHECKSUM(context, &cksum); -#ifdef DEBUG_CKSUM - pkiDebug("calculating checksum on buf size (%d)\n", der_req->length); - print_buffer(der_req->data, der_req->length); -#endif + TRACE_PKINIT_CLIENT_REQ_CHECKSUMS(context, &cksum, cksum2); retval = cb->get_preauth_time(context, rock, TRUE, &ctsec, &cusec); if (retval) @@ -139,7 +134,8 @@ pa_pkinit_gen_req(krb5_context context, nonce = request->nonce; retval = pkinit_as_req_create(context, plgctx, reqctx, ctsec, cusec, - nonce, &cksum, request->client, request->server, &out_data); + nonce, &cksum, cksum2, request->client, + request->server, &out_data); if (retval) { pkiDebug("error %d on pkinit_as_req_create; aborting PKINIT\n", (int) retval); @@ -167,23 +163,19 @@ pa_pkinit_gen_req(krb5_context context, cleanup: krb5_free_data(context, der_req); - krb5_free_checksum_contents(context, &cksum); + krb5_free_data_contents(context, &cksum); + free_pachecksum2(context, &cksum2); krb5_free_data(context, out_data); krb5_free_pa_data(context, return_pa_data); return retval; } static krb5_error_code -pkinit_as_req_create(krb5_context context, - pkinit_context plgctx, - pkinit_req_context reqctx, - krb5_timestamp ctsec, - krb5_int32 cusec, - krb5_ui_4 nonce, - const krb5_checksum * cksum, - krb5_principal client, - krb5_principal server, - krb5_data ** as_req) +pkinit_as_req_create(krb5_context context, pkinit_context plgctx, + pkinit_req_context reqctx, krb5_timestamp ctsec, + krb5_int32 cusec, krb5_ui_4 nonce, const krb5_data *cksum, + const krb5_pachecksum2 *cksum2, krb5_principal client, + krb5_principal server, krb5_data **as_req) { krb5_error_code retval = ENOMEM; krb5_data spki = empty_data(), *coded_auth_pack = NULL; @@ -201,6 +193,7 @@ pkinit_as_req_create(krb5_context context, auth_pack.pkAuthenticator.paChecksum = *cksum; if (!reqctx->opts->disable_freshness) auth_pack.pkAuthenticator.freshnessToken = reqctx->freshness_token; + auth_pack.pkAuthenticator.paChecksum2 = (krb5_pachecksum2 *)cksum2; auth_pack.clientDHNonce.length = 0; auth_pack.supportedKDFs = (krb5_data **)supported_kdf_alg_ids; diff --git a/src/plugins/preauth/pkinit/pkinit_constants.c b/src/plugins/preauth/pkinit/pkinit_constants.c index 905e90d29..a32b373c3 100644 --- a/src/plugins/preauth/pkinit/pkinit_constants.c +++ b/src/plugins/preauth/pkinit/pkinit_constants.c @@ -34,25 +34,49 @@ /* RFC 8636 id-pkinit-kdf-ah-sha1: iso(1) identified-organization(3) dod(6) * internet(1) security(5) kerberosv5(2) pkinit(3) kdf(6) sha1(1) */ -static char sha1_oid[8] = { 0x2B, 0x06, 0x01, 0x05, 0x02, 0x03, 0x06, 0x01 }; +static char kdf_sha1[8] = { 0x2B, 0x06, 0x01, 0x05, 0x02, 0x03, 0x06, 0x01 }; /* RFC 8636 id-pkinit-kdf-ah-sha256: iso(1) identified-organization(3) dod(6) * internet(1) security(5) kerberosv5(2) pkinit(3) kdf(6) sha256(2) */ -static char sha256_oid[8] = { 0x2B, 0x06, 0x01, 0x05, 0x02, 0x03, 0x06, 0x02 }; +static char kdf_sha256[8] = { 0x2B, 0x06, 0x01, 0x05, 0x02, 0x03, 0x06, 0x02 }; /* RFC 8636 id-pkinit-kdf-ah-sha512: iso(1) identified-organization(3) dod(6) * internet(1) security(5) kerberosv5(2) pkinit(3) kdf(6) sha512(3) */ -static char sha512_oid[8] = { 0x2B, 0x06, 0x01, 0x05, 0x02, 0x03, 0x06, 0x03 }; +static char kdf_sha512[8] = { 0x2B, 0x06, 0x01, 0x05, 0x02, 0x03, 0x06, 0x03 }; -const krb5_data sha1_id = { KV5M_DATA, sizeof(sha1_oid), sha1_oid }; -const krb5_data sha256_id = { KV5M_DATA, sizeof(sha256_oid), sha256_oid }; -const krb5_data sha512_id = { KV5M_DATA, sizeof(sha512_oid), sha512_oid }; +const krb5_data kdf_sha1_id = { KV5M_DATA, sizeof(kdf_sha1), kdf_sha1 }; +const krb5_data kdf_sha256_id = { KV5M_DATA, sizeof(kdf_sha256), kdf_sha256 }; +const krb5_data kdf_sha512_id = { KV5M_DATA, sizeof(kdf_sha512), kdf_sha512 }; krb5_data const * const supported_kdf_alg_ids[] = { - &sha256_id, - &sha1_id, - &sha512_id, + &kdf_sha256_id, + &kdf_sha1_id, + &kdf_sha512_id, NULL }; +/* RFC 3370 sha-1: iso(1) identified-organization(3) oiw(14) secsig(3) + * algorithm(2) 26 */ +static char cms_sha1[] = { 0x2b, 0x0e, 0x03, 0x02, 0x1a }; +/* RFC 5754 id-sha256: joint-iso-itu-t(2) country(16) us(840) organization(1) + * gov(101) csor(3) nistalgorithm(4) hashalgs(2) 1 */ +static char cms_sha256[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 +}; +/* RFC 5754 id-sha384: joint-iso-itu-t(2) country(16) us(840) organization(1) + * gov(101) csor(3) nistalgorithm(4) hashalgs(2) 2 */ +static char cms_sha384[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 +}; +/* RFC 5754 id-sha512: joint-iso-itu-t(2) country(16) us(840) organization(1) + * gov(101) csor(3) nistalgorithm(4) hashalgs(2) 3 */ +static char cms_sha512[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 +}; + +const krb5_data cms_sha1_id = { KV5M_DATA, sizeof(cms_sha1), cms_sha1 }; +const krb5_data cms_sha256_id = { KV5M_DATA, sizeof(cms_sha256), cms_sha256 }; +const krb5_data cms_sha384_id = { KV5M_DATA, sizeof(cms_sha384), cms_sha384 }; +const krb5_data cms_sha512_id = { KV5M_DATA, sizeof(cms_sha512), cms_sha512 }; + /* RFC 4055 sha256WithRSAEncryption: iso(1) member-body(2) us(840) * rsadsi(113549) pkcs(1) 1 11 */ static char sha256WithRSAEncr_oid[9] = { diff --git a/src/plugins/preauth/pkinit/pkinit_crypto.h b/src/plugins/preauth/pkinit/pkinit_crypto.h index 8e4a81362..57bb3cb84 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto.h +++ b/src/plugins/preauth/pkinit/pkinit_crypto.h @@ -542,9 +542,13 @@ pkinit_kdf(krb5_context context, krb5_data *secret, const krb5_data *alg_oid, const krb5_data *as_req, const krb5_data *pk_as_rep, krb5_keyblock *key_block); -extern const krb5_data sha1_id; -extern const krb5_data sha256_id; -extern const krb5_data sha512_id; +extern const krb5_data kdf_sha1_id; +extern const krb5_data kdf_sha256_id; +extern const krb5_data kdf_sha512_id; +extern const krb5_data cms_sha1_id; +extern const krb5_data cms_sha256_id; +extern const krb5_data cms_sha384_id; +extern const krb5_data cms_sha512_id; extern const krb5_data oakley_1024; extern const krb5_data oakley_2048; extern const krb5_data oakley_4096; @@ -577,4 +581,18 @@ crypto_req_cert_matching_data(krb5_context context, int parse_dh_min_bits(krb5_context context, const char *str); +/* Generate a SHA-1 checksum over body in *cksum1_out and a SHA-256 checksum + * over body in *cksum2_out with appropriate metadata. */ +krb5_error_code +crypto_generate_checksums(krb5_context context, const krb5_data *body, + krb5_data *cksum1_out, + krb5_pachecksum2 **cksum2_out); + +/* Verify the SHA-1 checksum in cksum1 and the tagged checksum in cksum2. + * cksum2 may be NULL, in which case only cksum1 is verified. */ +krb5_error_code +crypto_verify_checksums(krb5_context context, krb5_data *body, + const krb5_data *cksum1, + const krb5_pachecksum2 *cksum2); + #endif /* _PKINIT_CRYPTO_H */ diff --git a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c index 8098028eb..f222dbdf9 100644 --- a/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c +++ b/src/plugins/preauth/pkinit/pkinit_crypto_openssl.c @@ -2651,11 +2651,11 @@ cleanup: static const EVP_MD * algid_to_md(const krb5_data *alg_id) { - if (data_eq(*alg_id, sha1_id)) + if (data_eq(*alg_id, kdf_sha1_id)) return EVP_sha1(); - if (data_eq(*alg_id, sha256_id)) + if (data_eq(*alg_id, kdf_sha256_id)) return EVP_sha256(); - if (data_eq(*alg_id, sha512_id)) + if (data_eq(*alg_id, kdf_sha512_id)) return EVP_sha512(); return NULL; } @@ -5699,6 +5699,116 @@ parse_dh_min_bits(krb5_context context, const char *str) return PKINIT_DEFAULT_DH_MIN_BITS; } +/* Return the OpenSSL message digest type matching the given CMS OID, or NULL + * if it doesn't match any of the CMS OIDs we know about. */ +static const EVP_MD * +md_from_cms_oid(const krb5_data *alg_id) +{ + if (data_eq(*alg_id, cms_sha1_id)) + return EVP_sha1(); + if (data_eq(*alg_id, cms_sha256_id)) + return EVP_sha256(); + if (data_eq(*alg_id, cms_sha384_id)) + return EVP_sha384(); + if (data_eq(*alg_id, cms_sha512_id)) + return EVP_sha512(); + return NULL; +} + +/* Compute a message digest of the given type over body, placing the result in + * *digest_out in allocated storage. Return true on success. */ +static krb5_boolean +make_digest(const krb5_data *body, const EVP_MD *md, krb5_data *digest_out) +{ + krb5_error_code ret; + krb5_data d; + + if (md == NULL) + return FALSE; + ret = alloc_data(&d, EVP_MD_size(md)); + if (ret) + return FALSE; + if (!EVP_Digest(body->data, body->length, (uint8_t *)d.data, &d.length, md, + NULL)) { + free(d.data); + return FALSE; + } + *digest_out = d; + return TRUE; +} + +/* Return true if digest verifies for the given body and message digest + * type. */ +static krb5_boolean +check_digest(const krb5_data *body, const EVP_MD *md, const krb5_data *digest) +{ + unsigned int digest_len; + uint8_t buf[EVP_MAX_MD_SIZE]; + + if (md == NULL) + return FALSE; + if (!EVP_Digest(body->data, body->length, buf, &digest_len, md, NULL)) + return FALSE; + return (digest->length == digest_len && + CRYPTO_memcmp(digest->data, buf, digest_len) == 0); +} + +krb5_error_code +crypto_generate_checksums(krb5_context context, const krb5_data *body, + krb5_data *cksum1_out, krb5_pachecksum2 **cksum2_out) +{ + krb5_data cksum1 = empty_data(); + krb5_pachecksum2 *cksum2 = NULL; + krb5_error_code ret; + + if (!make_digest(body, EVP_sha1(), &cksum1)) + goto fail; + + cksum2 = k5alloc(sizeof(*cksum2), &ret); + if (cksum2 == NULL) + goto fail; + + if (!make_digest(body, EVP_sha256(), &cksum2->checksum)) + goto fail; + + if (krb5int_copy_data_contents(context, &cms_sha256_id, + &cksum2->algorithmIdentifier.algorithm)) + goto fail; + + cksum2->algorithmIdentifier.parameters = empty_data(); + + *cksum1_out = cksum1; + *cksum2_out = cksum2; + return 0; + +fail: + krb5_free_data_contents(context, &cksum1); + free_pachecksum2(context, &cksum2); + return KRB5_CRYPTO_INTERNAL; +} + +krb5_error_code +crypto_verify_checksums(krb5_context context, krb5_data *body, + const krb5_data *cksum1, + const krb5_pachecksum2 *cksum2) +{ + const EVP_MD *md; + + /* RFC 4556 doesn't say what error to return if the checksum doesn't match. + * Windows returns this one. */ + if (!check_digest(body, EVP_sha1(), cksum1)) + return KRB5KRB_AP_ERR_MODIFIED; + + if (cksum2 == NULL) + return 0; + + md = md_from_cms_oid(&cksum2->algorithmIdentifier.algorithm); + if (!check_digest(body, md, &cksum2->checksum)) + return KRB5KRB_AP_ERR_MODIFIED; + + return 0; +} + #ifdef _WIN32 BOOL WINAPI DllMain(HANDLE hModule, DWORD fdwReason, LPVOID lpvReserved) diff --git a/src/plugins/preauth/pkinit/pkinit_kdf_test.c b/src/plugins/preauth/pkinit/pkinit_kdf_test.c index 0a8a69b2a..3d0f7198e 100644 --- a/src/plugins/preauth/pkinit/pkinit_kdf_test.c +++ b/src/plugins/preauth/pkinit/pkinit_kdf_test.c @@ -122,7 +122,7 @@ main(int argc, char **argv) /* TEST 1: SHA-1/AES */ /* set up algorithm id */ - alg_id.algorithm = sha1_id; + alg_id.algorithm = kdf_sha1_id; enctype = enctype_aes; @@ -150,7 +150,7 @@ main(int argc, char **argv) /* TEST 2: SHA-256/AES */ /* set up algorithm id */ - alg_id.algorithm = sha256_id; + alg_id.algorithm = kdf_sha256_id; enctype = enctype_aes; @@ -178,7 +178,7 @@ main(int argc, char **argv) /* TEST 3: SHA-512/DES3 */ /* set up algorithm id */ - alg_id.algorithm = sha512_id; + alg_id.algorithm = kdf_sha512_id; enctype = enctype_des3; diff --git a/src/plugins/preauth/pkinit/pkinit_lib.c b/src/plugins/preauth/pkinit/pkinit_lib.c index 25965eb5d..891f47fd2 100644 --- a/src/plugins/preauth/pkinit/pkinit_lib.c +++ b/src/plugins/preauth/pkinit/pkinit_lib.c @@ -29,6 +29,7 @@ * SUCH DAMAGES. */ +#include "k5-int.h" #include "pkinit.h" #define FAKECERT @@ -119,8 +120,9 @@ free_krb5_auth_pack(krb5_auth_pack **in) { if ((*in) == NULL) return; krb5_free_data_contents(NULL, &(*in)->clientPublicValue); - free((*in)->pkAuthenticator.paChecksum.contents); + free((*in)->pkAuthenticator.paChecksum.data); krb5_free_data(NULL, (*in)->pkAuthenticator.freshnessToken); + free_pachecksum2(NULL, &(*in)->pkAuthenticator.paChecksum2); if ((*in)->supportedCMSTypes != NULL) free_krb5_algorithm_identifiers(&((*in)->supportedCMSTypes)); if ((*in)->supportedKDFs) { @@ -196,6 +198,18 @@ free_krb5_kdc_dh_key_info(krb5_kdc_dh_key_info **in) free(*in); } +void +free_pachecksum2(krb5_context context, krb5_pachecksum2 **in) +{ + if (*in == NULL) + return; + krb5_free_data_contents(context, &(*in)->checksum); + krb5_free_data_contents(context, &(*in)->algorithmIdentifier.algorithm); + krb5_free_data_contents(context, &(*in)->algorithmIdentifier.parameters); + free(*in); + *in = NULL; +} + void init_krb5_pa_pk_as_req(krb5_pa_pk_as_req **in) { diff --git a/src/plugins/preauth/pkinit/pkinit_srv.c b/src/plugins/preauth/pkinit/pkinit_srv.c index 1f3db4f27..7a0fa69ee 100644 --- a/src/plugins/preauth/pkinit/pkinit_srv.c +++ b/src/plugins/preauth/pkinit/pkinit_srv.c @@ -428,11 +428,12 @@ pkinit_server_verify_padata(krb5_context context, krb5_data authp_data = {0, 0, NULL}, krb5_authz = {0, 0, NULL}; krb5_pa_pk_as_req *reqp = NULL; krb5_auth_pack *auth_pack = NULL; + krb5_pk_authenticator *pka; pkinit_kdc_context plgctx = NULL; pkinit_kdc_req_context reqctx = NULL; krb5_checksum cksum = {0, 0, 0, NULL}; krb5_data *der_req = NULL; - krb5_data k5data, *ftoken; + krb5_data k5data; int is_signed = 1; krb5_pa_data **e_data = NULL; krb5_kdcpreauth_modreq modreq = NULL; @@ -524,8 +525,9 @@ pkinit_server_verify_padata(krb5_context context, pkiDebug("failed to decode krb5_auth_pack\n"); goto cleanup; } + pka = &auth_pack->pkAuthenticator; - retval = krb5_check_clockskew(context, auth_pack->pkAuthenticator.ctime); + retval = krb5_check_clockskew(context, pka->ctime); if (retval) goto cleanup; @@ -548,36 +550,14 @@ pkinit_server_verify_padata(krb5_context context, goto cleanup; } der_req = cb->request_body(context, rock); - retval = krb5_c_make_checksum(context, CKSUMTYPE_SHA1, NULL, 0, der_req, - &cksum); - if (retval) { - pkiDebug("unable to calculate AS REQ checksum\n"); - goto cleanup; - } - if (cksum.length != auth_pack->pkAuthenticator.paChecksum.length || - k5_bcmp(cksum.contents, auth_pack->pkAuthenticator.paChecksum.contents, - cksum.length) != 0) { - pkiDebug("failed to match the checksum\n"); -#ifdef DEBUG_CKSUM - pkiDebug("calculating checksum on buf size (%d)\n", req_pkt->length); - print_buffer(req_pkt->data, req_pkt->length); - pkiDebug("received checksum type=%d size=%d ", - auth_pack->pkAuthenticator.paChecksum.checksum_type, - auth_pack->pkAuthenticator.paChecksum.length); - print_buffer(auth_pack->pkAuthenticator.paChecksum.contents, - auth_pack->pkAuthenticator.paChecksum.length); - pkiDebug("expected checksum type=%d size=%d ", - cksum.checksum_type, cksum.length); - print_buffer(cksum.contents, cksum.length); -#endif - retval = KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED; + retval = crypto_verify_checksums(context, der_req, &pka->paChecksum, + pka->paChecksum2); + if (retval) goto cleanup; - } - ftoken = auth_pack->pkAuthenticator.freshnessToken; - if (ftoken != NULL) { - retval = cb->check_freshness_token(context, rock, ftoken); + if (pka->freshnessToken != NULL) { + retval = cb->check_freshness_token(context, rock, pka->freshnessToken); if (retval) goto cleanup; valid_freshness_token = TRUE; diff --git a/src/plugins/preauth/pkinit/pkinit_trace.h b/src/plugins/preauth/pkinit/pkinit_trace.h index c0b1e2c13..7d19167eb 100644 --- a/src/plugins/preauth/pkinit/pkinit_trace.h +++ b/src/plugins/preauth/pkinit/pkinit_trace.h @@ -52,8 +52,9 @@ TRACE(c, "PKINIT client verified DH reply") #define TRACE_PKINIT_CLIENT_REP_DH_FAIL(c) \ TRACE(c, "PKINIT client could not verify DH reply") -#define TRACE_PKINIT_CLIENT_REQ_CHECKSUM(c, cksum) \ - TRACE(c, "PKINIT client computed kdc-req-body checksum {cksum}", cksum) +#define TRACE_PKINIT_CLIENT_REQ_CHECKSUMS(c, ck1, ck2) \ + TRACE(c, "PKINIT client computed checksums: {hexdata} {hexdata}", \ + ck1, &(ck2)->checksum) #define TRACE_PKINIT_CLIENT_REQ_DH(c) \ TRACE(c, "PKINIT client making DH request") #define TRACE_PKINIT_CLIENT_SAN_CONFIG_DNSNAME(c, host) \ diff --git a/src/tests/asn.1/krb5_decode_test.c b/src/tests/asn.1/krb5_decode_test.c index 6ead19d5d..25ed30e42 100644 --- a/src/tests/asn.1/krb5_decode_test.c +++ b/src/tests/asn.1/krb5_decode_test.c @@ -1178,7 +1178,7 @@ main(int argc, char **argv) /* decode_krb5_auth_pack */ { setup(krb5_auth_pack,ktest_make_sample_auth_pack); - decode_run("krb5_auth_pack","","30 81 85 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 08 04 06 70 76 61 6C 75 65 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", + decode_run("krb5_auth_pack","","30 81 89 A0 39 30 37 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 0A 04 08 6B 72 62 35 64 61 74 61 A4 0A 04 08 6B 72 62 35 64 61 74 61 A1 08 04 06 70 76 61 6C 75 65 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", acc.decode_krb5_auth_pack, ktest_equal_auth_pack,ktest_free_auth_pack); ktest_empty_auth_pack(&ref); diff --git a/src/tests/asn.1/ktest.c b/src/tests/asn.1/ktest.c index 02d26c085..20360c8ff 100644 --- a/src/tests/asn.1/ktest.c +++ b/src/tests/asn.1/ktest.c @@ -700,9 +700,7 @@ ktest_make_sample_pk_authenticator(krb5_pk_authenticator *p) p->cusec = SAMPLE_USEC; p->ctime = SAMPLE_TIME; p->nonce = SAMPLE_NONCE; - ktest_make_sample_checksum(&p->paChecksum); - /* We don't encode the checksum type, only the contents. */ - p->paChecksum.checksum_type = 0; + ktest_make_sample_data(&p->paChecksum); p->freshnessToken = ealloc(sizeof(krb5_data)); ktest_make_sample_data(p->freshnessToken); } @@ -1604,8 +1602,7 @@ ktest_empty_pa_otp_req(krb5_pa_otp_req *p) static void ktest_empty_pk_authenticator(krb5_pk_authenticator *p) { - ktest_empty_checksum(&p->paChecksum); - p->paChecksum.contents = NULL; + ktest_empty_data(&p->paChecksum); krb5_free_data(NULL, p->freshnessToken); p->freshnessToken = NULL; } diff --git a/src/tests/asn.1/ktest_equal.c b/src/tests/asn.1/ktest_equal.c index b48a0285d..13786dd1e 100644 --- a/src/tests/asn.1/ktest_equal.c +++ b/src/tests/asn.1/ktest_equal.c @@ -844,7 +844,7 @@ ktest_equal_pk_authenticator(krb5_pk_authenticator *ref, p = p && scalar_equal(cusec); p = p && scalar_equal(ctime); p = p && scalar_equal(nonce); - p = p && struct_equal(paChecksum, ktest_equal_checksum); + p = p && data_eq(ref->paChecksum, var->paChecksum); return p; } diff --git a/src/tests/asn.1/pkinit_encode.out b/src/tests/asn.1/pkinit_encode.out index 6ec7aaa36..a764182e1 100644 --- a/src/tests/asn.1/pkinit_encode.out +++ b/src/tests/asn.1/pkinit_encode.out @@ -1,7 +1,7 @@ encode_krb5_pa_pk_as_req: 30 38 80 08 6B 72 62 35 64 61 74 61 A1 22 30 20 30 1E 80 08 6B 72 62 35 64 61 74 61 81 08 6B 72 62 35 64 61 74 61 82 08 6B 72 62 35 64 61 74 61 82 08 6B 72 62 35 64 61 74 61 encode_krb5_pa_pk_as_rep(dhInfo): A0 28 30 26 80 08 6B 72 62 35 64 61 74 61 A1 0A 04 08 6B 72 62 35 64 61 74 61 A2 0E 30 0C A0 0A 06 08 6B 72 62 35 64 61 74 61 encode_krb5_pa_pk_as_rep(encKeyPack): 81 08 6B 72 62 35 64 61 74 61 -encode_krb5_auth_pack: 30 81 85 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 08 04 06 70 76 61 6C 75 65 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 89 A0 39 30 37 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 0A 04 08 6B 72 62 35 64 61 74 61 A4 0A 04 08 6B 72 62 35 64 61 74 61 A1 08 04 06 70 76 61 6C 75 65 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_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 encode_krb5_sp80056a_other_info: 30 81 81 30 0B 06 09 2A 86 48 86 F7 12 01 02 02 A0 32 04 30 30 2E A0 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A1 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 32 04 30 30 2E A0 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A1 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 A2 0A 04 08 6B 72 62 35 64 61 74 61 diff --git a/src/tests/asn.1/pkinit_trval.out b/src/tests/asn.1/pkinit_trval.out index 46f4a3410..c47bd71f6 100644 --- a/src/tests/asn.1/pkinit_trval.out +++ b/src/tests/asn.1/pkinit_trval.out @@ -38,7 +38,7 @@ encode_krb5_auth_pack: . . [0] [Integer] 123456 . . [1] [Generalized Time] "19940610060317Z" . . [2] [Integer] 42 -. . [3] [Octet String] "1234" +. . [3] [Octet String] "krb5data" . . [4] [Octet String] "krb5data" . [1] [Octet String] "pvalue" . [2] [Sequence/Sequence Of] From ghudson at mit.edu Tue Mar 25 11:12:41 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Tue, 25 Mar 2025 11:12:41 -0400 (EDT) Subject: krb5 commit: Add initiator-side IAKERB realm discovery Message-ID: <20250325151241.C27CE102C86@krbdev.mit.edu> https://github.com/krb5/krb5/commit/cc3511f66de78a955d0bd50d3f5bf2662bd3eda8 commit cc3511f66de78a955d0bd50d3f5bf2662bd3eda8 Author: Alexander Bokovoy Date: Mon Mar 10 09:40:37 2025 +0200 Add initiator-side IAKERB realm discovery When importing a name to IAKERB, don't add the default realm when we parse strings. Host-based name imports will continue to use krb5_sname_to_principal(), which may add a realm from [domain_realm] but won't add the default realm. In the IAKERB state machine, query for the service's realm if the client name doesn't have a realm. To reduce code duplication, make iakerb_make_token() responsible for saving the token and incrementing the message count. [ghudson at mit.edu: added tests; added a discovery state to the machine; expanded import; adjusted iakerb_make_token() contract; rewrote commit message] ticket: 9167 (new) src/appl/gss-sample/t_gss_sample.py | 7 ++- src/lib/gssapi/krb5/gssapiP_krb5.h | 7 +++ src/lib/gssapi/krb5/gssapi_krb5.c | 2 +- src/lib/gssapi/krb5/iakerb.c | 67 +++++++++++++++++----------- src/lib/gssapi/krb5/import_name.c | 26 +++++++++-- src/tests/gssapi/t_gssapi.py | 32 +++++++++++-- src/tests/gssapi/t_iakerb.c | 89 ++++++++++++++++++------------------- 7 files changed, 149 insertions(+), 81 deletions(-) diff --git a/src/appl/gss-sample/t_gss_sample.py b/src/appl/gss-sample/t_gss_sample.py index 360835918..dad31e4b3 100755 --- a/src/appl/gss-sample/t_gss_sample.py +++ b/src/appl/gss-sample/t_gss_sample.py @@ -78,7 +78,12 @@ def tgs_test(realm, options, server_options=[]): def pw_test(realm, options, server_options=[]): if os.path.exists(realm.ccache): os.remove(realm.ccache) - options = options + ['-user', realm.user_princ, '-pass', password('user')] + if '-iakerb' in options: + # Use IAKERB realm discovery. + user = realm.user_princ.split('@')[0] + else: + user = realm.user_princ + options = options + ['-user', user, '-pass', password('user')] server_client_test(realm, options, server_options) if os.path.exists(realm.ccache): fail('gss_acquire_cred_with_password created ccache') diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index da7c1cfac..1ed71fc81 100644 --- a/src/lib/gssapi/krb5/gssapiP_krb5.h +++ b/src/lib/gssapi/krb5/gssapiP_krb5.h @@ -706,6 +706,13 @@ OM_uint32 KRB5_CALLCONV krb5_gss_import_name gss_name_t* /* output_name */ ); +OM_uint32 KRB5_CALLCONV iakerb_gss_import_name +(OM_uint32*, /* minor_status */ + gss_buffer_t, /* input_name_buffer */ + gss_OID, /* input_name_type */ + gss_name_t* /* output_name */ +); + OM_uint32 KRB5_CALLCONV krb5_gss_release_name (OM_uint32*, /* minor_status */ gss_name_t* /* input_name */ diff --git a/src/lib/gssapi/krb5/gssapi_krb5.c b/src/lib/gssapi/krb5/gssapi_krb5.c index 6c7cf2344..8bc6f072f 100644 --- a/src/lib/gssapi/krb5/gssapi_krb5.c +++ b/src/lib/gssapi/krb5/gssapi_krb5.c @@ -934,7 +934,7 @@ static struct gss_config iakerb_mechanism = { krb5_gss_indicate_mechs, krb5_gss_compare_name, krb5_gss_display_name, - krb5_gss_import_name, + iakerb_gss_import_name, krb5_gss_release_name, krb5_gss_inquire_cred, NULL, /* add_cred */ diff --git a/src/lib/gssapi/krb5/iakerb.c b/src/lib/gssapi/krb5/iakerb.c index 539b23195..603433608 100644 --- a/src/lib/gssapi/krb5/iakerb.c +++ b/src/lib/gssapi/krb5/iakerb.c @@ -31,6 +31,7 @@ */ enum iakerb_state { + IAKERB_REALM_DISCOVERY, /* querying server for its realm */ IAKERB_AS_REQ, /* acquiring ticket with initial creds */ IAKERB_TGS_REQ, /* acquiring ticket with TGT */ IAKERB_AP_REQ /* hand-off to normal GSS AP-REQ exchange */ @@ -220,7 +221,8 @@ cleanup: } /* - * Create a token from IAKERB-HEADER and KRB-KDC-REQ/REP + * Create a token from IAKERB-HEADER and KRB-KDC-REQ/REP. Save the generated + * token for the finish checksum and increment the message count. */ static krb5_error_code iakerb_make_token(iakerb_ctx_id_t ctx, @@ -276,6 +278,11 @@ iakerb_make_token(iakerb_ctx_id_t ctx, k5_buf_add_len(&buf, data->data, data->length); assert(buf.len == token->length); + code = iakerb_save_token(ctx, token); + if (code != 0) + goto cleanup; + ctx->count++; + cleanup: krb5_free_data(ctx->k5c, data); @@ -315,11 +322,6 @@ iakerb_acceptor_realm(iakerb_ctx_id_t ctx, gss_cred_id_t verifier_cred, ret = iakerb_make_token(ctx, &realm, NULL, &reply, output_token); if (ret) goto cleanup; - ret = iakerb_save_token(ctx, output_token); - if (ret) - goto cleanup; - - ctx->count++; cleanup: if (ret) @@ -409,14 +411,6 @@ iakerb_acceptor_step(iakerb_ctx_id_t ctx, gss_cred_id_t verifier_cred, goto cleanup; code = iakerb_make_token(ctx, &realm, NULL, &reply, output_token); - if (code != 0) - goto cleanup; - - code = iakerb_save_token(ctx, output_token); - if (code != 0) - goto cleanup; - - ctx->count++; cleanup: if (code != 0) @@ -548,17 +542,21 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, gss_buffer_t output_token) { krb5_error_code code = 0; - krb5_data in = empty_data(), out = empty_data(), realm = empty_data(); + krb5_data in = empty_data(), out = empty_data(); + krb5_data realm = empty_data(), server_realm = empty_data(); krb5_data *cookie = NULL; OM_uint32 tmp; unsigned int flags = 0; krb5_ticket_times times; + krb5_boolean first_token; output_token->length = 0; output_token->value = NULL; - if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) { - code = iakerb_parse_token(ctx, input_token, NULL, &cookie, &in); + first_token = (input_token == GSS_C_NO_BUFFER || input_token->length == 0); + if (!first_token) { + code = iakerb_parse_token(ctx, input_token, &server_realm, &cookie, + &in); if (code != 0) goto cleanup; @@ -568,6 +566,25 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, } switch (ctx->state) { + case IAKERB_REALM_DISCOVERY: + if (first_token) { + /* Send the discovery request. */ + code = iakerb_make_token(ctx, &realm, cookie, &out, output_token); + goto cleanup; + } + + /* The acceptor should have sent us its realm. */ + if (server_realm.length == 0) { + code = KRB5_BAD_MSIZE; + goto cleanup; + } + + /* Steal the received server realm for the client principal. */ + krb5_free_data_contents(ctx->k5c, &cred->name->princ->realm); + cred->name->princ->realm = server_realm; + server_realm = empty_data(); + + /* Done with realm discovery; fall through to AS request. */ case IAKERB_AS_REQ: if (ctx->icc == NULL) { code = iakerb_init_creds_ctx(ctx, cred, time_req); @@ -626,17 +643,7 @@ iakerb_initiator_step(iakerb_ctx_id_t ctx, if (out.length != 0) { assert(ctx->state != IAKERB_AP_REQ); - code = iakerb_make_token(ctx, &realm, cookie, &out, output_token); - if (code != 0) - goto cleanup; - - /* Save the token for generating a future checksum */ - code = iakerb_save_token(ctx, output_token); - if (code != 0) - goto cleanup; - - ctx->count++; } cleanup: @@ -644,6 +651,7 @@ cleanup: gss_release_buffer(&tmp, output_token); krb5_free_data(ctx->k5c, cookie); krb5_free_data_contents(ctx->k5c, &out); + krb5_free_data_contents(ctx->k5c, &server_realm); krb5_free_data_contents(ctx->k5c, &realm); return code; @@ -663,6 +671,11 @@ iakerb_get_initial_state(iakerb_ctx_id_t ctx, krb5_creds in_creds, *out_creds = NULL; krb5_error_code code; + if (cred->name->princ->realm.length == 0) { + *state = IAKERB_REALM_DISCOVERY; + return 0; + } + memset(&in_creds, 0, sizeof(in_creds)); in_creds.client = cred->name->princ; diff --git a/src/lib/gssapi/krb5/import_name.c b/src/lib/gssapi/krb5/import_name.c index cc6883b5f..a067d0742 100644 --- a/src/lib/gssapi/krb5/import_name.c +++ b/src/lib/gssapi/krb5/import_name.c @@ -119,9 +119,10 @@ parse_hostbased(const char *str, size_t len, return 0; } -OM_uint32 KRB5_CALLCONV -krb5_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, - gss_OID input_name_type, gss_name_t *output_name) +static OM_uint32 KRB5_CALLCONV +import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, krb5_boolean iakerb, + gss_name_t *output_name) { krb5_context context; krb5_principal princ = NULL; @@ -304,6 +305,9 @@ krb5_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, /* At this point, stringrep is set, or if not, code is. */ if (stringrep) { + /* For IAKERB, use realm discovery instead of the default realm. */ + if (iakerb) + flags |= KRB5_PRINCIPAL_PARSE_NO_DEF_REALM; code = krb5_parse_name_flags(context, stringrep, flags, &princ); if (code) goto cleanup; @@ -340,3 +344,19 @@ cleanup: free(host); return status; } + +OM_uint32 KRB5_CALLCONV +krb5_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, gss_name_t *output_name) +{ + return import_name(minor_status, input_name_buffer, input_name_type, FALSE, + output_name); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_import_name(OM_uint32 *minor_status, gss_buffer_t input_name_buffer, + gss_OID input_name_type, gss_name_t *output_name) +{ + return import_name(minor_status, input_name_buffer, input_name_type, TRUE, + output_name); +} diff --git a/src/tests/gssapi/t_gssapi.py b/src/tests/gssapi/t_gssapi.py index e1ed571fd..5e566563f 100755 --- a/src/tests/gssapi/t_gssapi.py +++ b/src/tests/gssapi/t_gssapi.py @@ -10,11 +10,37 @@ for realm in multipass_realms(): realm = K5Realm() +remove_default = {'libdefaults': {'default_realm': None}} +change_default = {'libdefaults': {'default_realm': 'WRONG.REALM'}} +no_default = realm.special_env('no_default', False, krb5_conf=remove_default) +wrong_default = realm.special_env('wrong_default', False, + krb5_conf=change_default) + +# Test IAKERB with credentials. +realm.run(['./t_iakerb', 'p:' + realm.user_princ, '-', 'h:host@' + hostname, + 'h:host']) + +# Test IAKERB getting initial credentials. +realm.run(['./t_iakerb', 'p:' + realm.user_princ, password('user'), + 'h:host@' + hostname, 'h:host']) + +# Test IAKERB realm discovery. +realm.run(['./t_iakerb', 'e:user', password('user'), 'h:host@' + hostname, + 'h:host']) + +# Test IAKERB realm discovery without default_realm set. (Use a +# GSS_KRB5_NT_PRINCIPAL_NAME acceptor name so that +# gss_accept_sec_context() knows the realm.) +realm.run(['./t_iakerb', 'e:user', password('user'), 'h:host@' + hostname, + 'p:' + realm.host_princ], env=no_default) + +# Test IAKERB realm discovery with a non-useful default_realm set. +realm.run(['./t_iakerb', 'e:user', password('user'), 'h:host@' + hostname, + 'p:' + realm.host_princ], env=wrong_default) + # Test gss_add_cred(). realm.run(['./t_add_cred']) -realm.run(['./t_iakerb']) - ### Test acceptor name behavior. # Create some host-based principals and put most of them into the @@ -34,8 +60,6 @@ realm.run([kadminl, 'renprinc', 'service1/abraham', 'service1/andrew']) # Test with no default realm and no dots in the server name. realm.run(['./t_accname', 'h:http at localhost'], expected_msg='http/localhost') -remove_default = {'libdefaults': {'default_realm': None}} -no_default = realm.special_env('no_default', False, krb5_conf=remove_default) realm.run(['./t_accname', 'h:http at localhost'], expected_msg='http/localhost', env=no_default) diff --git a/src/tests/gssapi/t_iakerb.c b/src/tests/gssapi/t_iakerb.c index a81b526e7..1bab1823f 100644 --- a/src/tests/gssapi/t_iakerb.c +++ b/src/tests/gssapi/t_iakerb.c @@ -1,7 +1,7 @@ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* tests/gssapi/t_iakerb.c - IAKERB tests */ /* - * Copyright (C) 2024 by the Massachusetts Institute of Technology. + * Copyright (C) 2024, 2025 by the Massachusetts Institute of Technology. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,57 +32,56 @@ #include #include -#include #include "common.h" -static uint8_t -realm_query[] = { - /* ASN.1 wrapper for IAKERB mech */ - 0x60, 0x10, - 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x02, 0x05, - /* IAKERB_PROXY token type */ - 0x05, 0x01, - /* IAKERB-HEADER with empty target-realm */ - 0x30, 0x04, - 0xA1, 0x02, 0x0C, 0x00 -}; - -static uint8_t -realm_response[] = { - /* ASN.1 wrapper for IAKERB mech */ - 0x60, 0x1B, - 0x06, 0x06, 0x2B, 0x06, 0x01, 0x05, 0x02, 0x05, - /* IAKERB_PROXY token type */ - 0x05, 0x01, - /* IAKERB-HEADER with configured realm */ - 0x30, 0x0F, - 0xA1, 0x0D, 0x0C, 0x0B, - 'K', 'R', 'B', 'T', 'E', 'S', 'T', '.', 'C', 'O', 'M' -}; - int -main(void) +main(int argc, char **argv) { OM_uint32 major, minor; - gss_cred_id_t cred; - gss_buffer_desc in, out; - gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + const char *password; + gss_name_t iname, tname, aname; + gss_cred_id_t icred, acred; + gss_ctx_id_t ictx, actx; + gss_buffer_desc pwbuf; + + if (argc != 5) { + fprintf(stderr, "Usage: %s initiatorname password|- targetname " + "acceptorname\n", argv[0]); + return 1; + } + + iname = import_name(argv[1]); + password = argv[2]; + tname = import_name(argv[3]); + aname = import_name(argv[4]); + + if (strcmp(password, "-") != 0) { + pwbuf.value = (void *)password; + pwbuf.length = strlen(password); + major = gss_acquire_cred_with_password(&minor, iname, &pwbuf, 0, + &mechset_iakerb, GSS_C_INITIATE, + &icred, NULL, NULL); + check_gsserr("gss_acquire_cred_with_password", major, minor); + } else { + major = gss_acquire_cred(&minor, iname, GSS_C_INDEFINITE, + &mechset_iakerb, GSS_C_INITIATE, &icred, NULL, + NULL); + check_gsserr("gss_acquire_cred(iname)", major, minor); + } - major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, &mechset_iakerb, - GSS_C_ACCEPT, &cred, NULL, NULL); - check_gsserr("gss_acquire_cred", major, minor); + major = gss_acquire_cred(&minor, aname, GSS_C_INDEFINITE, &mechset_iakerb, + GSS_C_ACCEPT, &acred, NULL, NULL); + check_gsserr("gss_acquire_cred(aname)", major, minor); - in.value = realm_query; - in.length = sizeof(realm_query); - major = gss_accept_sec_context(&minor, &ctx, cred, &in, - GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL, &out, - NULL, NULL, NULL); - check_gsserr("gss_accept_sec_context", major, minor); - assert(out.length == sizeof(realm_response)); - assert(memcmp(out.value, realm_response, out.length) == 0); + establish_contexts(&mech_iakerb, icred, acred, tname, 0, &ictx, &actx, + NULL, NULL, NULL); - gss_release_buffer(&minor, &out); - gss_delete_sec_context(&minor, &ctx, NULL); - gss_release_cred(&minor, &cred); + (void)gss_release_name(&minor, &iname); + (void)gss_release_name(&minor, &tname); + (void)gss_release_name(&minor, &aname); + (void)gss_release_cred(&minor, &icred); + (void)gss_release_cred(&minor, &acred); + (void)gss_delete_sec_context(&minor, &ictx, NULL); + (void)gss_delete_sec_context(&minor, &actx, NULL); return 0; } From ghudson at mit.edu Tue Mar 25 14:45:14 2025 From: ghudson at mit.edu (ghudson at mit.edu) Date: Tue, 25 Mar 2025 14:45:14 -0400 (EDT) Subject: krb5 commit: Fix IAKERB accept_sec_context null pointer crash Message-ID: <20250325184514.575C7102C89@krbdev.mit.edu> https://github.com/krb5/krb5/commit/f0230605c4ffe475e158d1a4ab17ed2c7f4c6189 commit f0230605c4ffe475e158d1a4ab17ed2c7f4c6189 Author: Alexander Bokovoy Date: Fri Mar 21 09:52:47 2025 +0200 Fix IAKERB accept_sec_context null pointer crash When iakerb_gss_accept_sec_context() processes an initial token which is not an IAKERB token (because the client already has a service ticket), set *context_handle. Otherwise subsequent GSS calls using this context will dereference a null pointer and crash. [ghudson at mit.edu: moved fix to cleanup handler to avoid code duplication; added tests; rewrote commit message] ticket: 9168 (new) src/appl/gss-sample/t_gss_sample.py | 7 +++++++ src/lib/gssapi/krb5/iakerb.c | 18 +++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/appl/gss-sample/t_gss_sample.py b/src/appl/gss-sample/t_gss_sample.py index dad31e4b3..f823979e1 100755 --- a/src/appl/gss-sample/t_gss_sample.py +++ b/src/appl/gss-sample/t_gss_sample.py @@ -116,6 +116,13 @@ for realm in multipass_realms(): # test default (i.e., krb5) mechanism with GSS_C_DCE_STYLE tgs_test(realm, ['-dce']) + mark('AP') + ccache_save(realm) + tgs_test(realm, ['-krb5']) + tgs_test(realm, ['-spnego']) + tgs_test(realm, ['-iakerb'], ['-iakerb']) + tgs_test(realm, ['-dce']) + mark('pw') pw_test(realm, ['-krb5']) pw_test(realm, ['-spnego']) diff --git a/src/lib/gssapi/krb5/iakerb.c b/src/lib/gssapi/krb5/iakerb.c index 603433608..1dd34287b 100644 --- a/src/lib/gssapi/krb5/iakerb.c +++ b/src/lib/gssapi/krb5/iakerb.c @@ -811,9 +811,9 @@ iakerb_gss_accept_sec_context(OM_uint32 *minor_status, OM_uint32 major_status = GSS_S_FAILURE; OM_uint32 code; iakerb_ctx_id_t ctx; - int initialContextToken = (*context_handle == GSS_C_NO_CONTEXT); + krb5_boolean first_token = (*context_handle == GSS_C_NO_CONTEXT); - if (initialContextToken) { + if (first_token) { code = iakerb_alloc_context(&ctx, 0); if (code != 0) goto cleanup; @@ -834,10 +834,6 @@ iakerb_gss_accept_sec_context(OM_uint32 *minor_status, major_status = GSS_S_DEFECTIVE_TOKEN; if (code != 0) goto cleanup; - if (initialContextToken) { - *context_handle = (gss_ctx_id_t)ctx; - ctx = NULL; - } if (src_name != NULL) *src_name = GSS_C_NO_NAME; if (ret_flags != NULL) @@ -872,9 +868,13 @@ iakerb_gss_accept_sec_context(OM_uint32 *minor_status, *mech_type = gss_mech_iakerb; cleanup: - if (initialContextToken && GSS_ERROR(major_status)) { - iakerb_release_context(ctx); - *context_handle = GSS_C_NO_CONTEXT; + if (first_token) { + if (GSS_ERROR(major_status)) { + iakerb_release_context(ctx); + *context_handle = GSS_C_NO_CONTEXT; + } else { + *context_handle = (gss_ctx_id_t)ctx; + } } *minor_status = code;