]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
third_party:heimdal: import lorikeet-heimdal-202509242121
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 29 Sep 2025 23:25:51 +0000 (12:25 +1300)
committerJennifer Sutton <jsutton@samba.org>
Wed, 8 Oct 2025 21:12:44 +0000 (21:12 +0000)
(commit beffefde5c6767589603cca98065378250eaae2c)

Changes to heimdal to implement Windows strong and flexible certificate mapping
as outlined in

KB5014754: Certificate-based authentication changes
           on Windows domain controllers
https://support.microsoft.com/en-us/topic/kb5014754-certificate-based-
                                          authentication-changes-on-windows-
                                          domain-controllers-
                                          ad2c23b0-15d8-4340-a468-4d4f3b188f16

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
Autobuild-User(master): Jennifer Sutton <jsutton@samba.org>
Autobuild-Date(master): Wed Oct  8 21:12:44 UTC 2025 on atb-devel-224

third_party/heimdal/.gitignore
third_party/heimdal/kdc/kerberos5.c
third_party/heimdal/kdc/pkinit.c
third_party/heimdal/lib/hdb/Makefile.am
third_party/heimdal/lib/hdb/hdb.asn1
third_party/heimdal/lib/hdb/version-script.map
third_party/heimdal/lib/hx509/cert.c
third_party/heimdal/lib/hx509/version-script.map
third_party/heimdal/lib/krb5/krb5_err.et

index 6a56c208980105362e3b2451fe218862ca4e128c..aecf4f611a4eebcd65e04aac89e3b20818912e43 100644 (file)
@@ -361,9 +361,13 @@ asn1_*_asn1.c
 /lib/hdb/asn1_HDB_EncTypeList.c
 /lib/hdb/asn1_HDB_EntryOrAlias.c
 /lib/hdb/asn1_HDB_Ext_Aliases.c
+/lib/hdb/asn1_HDB_Ext_CertificateMapping.c
+/lib/hdb/asn1_HDB_Ext_CertificateMappings.c
 /lib/hdb/asn1_HDB_Ext_Constrained_delegation_acl.c
+/lib/hdb/asn1_HDB_Ext_EnforcementMode.c
 /lib/hdb/asn1_HDB_Ext_KeyRotation.c
 /lib/hdb/asn1_HDB_Ext_KeySet.c
+/lib/hdb/asn1_HDB_Ext_KeyTrust.c
 /lib/hdb/asn1_HDB_Ext_Lan_Manager_OWF.c
 /lib/hdb/asn1_HDB_Ext_PKINIT_acl.c
 /lib/hdb/asn1_HDB_Ext_PKINIT_cert.c
index d0c646c328ccb56f0f26ccd505c5a587e191ca0e..80048109493140ae6b0ad7eea347a435e9664418 100644 (file)
@@ -563,6 +563,8 @@ pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
            ret == KRB5_KDC_ERR_CLIENT_NOT_TRUSTED) {
 
            ret = KRB5_KDC_ERR_CLIENT_NOT_TRUSTED;
+       } else if (ret == KRB5_KDC_ERR_CERTIFICATE_MISMATCH) {
+           ret = KRB5_KDC_ERR_CERTIFICATE_MISMATCH;
        } else {
            ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
        }
index 6fdae939f8c19125aadf3fb6dec5faddf5774366..ea734dd50c732aab91c4742273fd9aac3accd4a3 100644 (file)
@@ -35,7 +35,6 @@
 
 #include "hdb_asn1.h"
 #include "kdc_locl.h"
-
 #ifdef PKINIT
 
 #include <heim_asn1.h>
@@ -427,7 +426,7 @@ pk_check_key_trust( krb5_context context, hdb_entry *client, hx509_cert *cert)
        ret = KRB5_KDC_ERR_CLIENT_NOT_TRUSTED;
        krb5_set_error_message(
            context, ret, "Unable to get certificate public key");
-       goto out1;
+       goto out;
     }
 
     /*
@@ -439,7 +438,7 @@ pk_check_key_trust( krb5_context context, hdb_entry *client, hx509_cert *cert)
     if (ret) {
        krb5_set_error_message(
            context, ret, "Unable to encode certificate public key");
-       goto out2;
+       goto out1;
     }
 
     keys = ext->data.u.key_trust;
@@ -456,10 +455,346 @@ pk_check_key_trust( krb5_context context, hdb_entry *client, hx509_cert *cert)
     }
     der_free_octet_string(&buf);
 
-out2:
-    free_SubjectPublicKeyInfo(&spki);
 out1:
-    free_HDB_extension(ext);
+    free_SubjectPublicKeyInfo(&spki);
+out:
+    return ret;
+}
+
+/**
+ * @brief Match the target name against the value on the certificate
+ *
+ * name is converted to it's rfc4514 form and compared to the target
+ *
+ * @param[in] context krb5 context
+ * @param[in] name    name on the certificate
+ * @param[in] target  value to match name against
+ *
+ * @return TRUE the name matches
+ *         FALSE the name DOES NOT match
+ */
+static krb5_boolean match_name(
+    const krb5_context context,
+    const hx509_name *name,
+    const heim_octet_string *target)
+{
+    krb5_boolean matched = FALSE;
+    char *ns = NULL;
+    char *ts = NULL;
+    krb5_error_code ret = 0;
+
+    ret = hx509_name_to_string(*name, &ns);
+    if (ret != 0) {
+       return FALSE;
+    }
+
+    ts = calloc(target->length + 1, sizeof(char));
+    if (ts == NULL) {
+       goto out;
+    }
+    memcpy(ts, target->data, target->length);
+    if (strncmp(ts, ns, target->length) == 0) {
+       matched = TRUE;
+    }
+    free(ts);
+
+out:
+    free(ns);
+    return matched;
+}
+
+/**
+ * @brief does the rfc822 name of the certificate match the value in mapping?
+ *
+ * @param[in] context krb5 context
+ * @param[in] cert    X509 certificate
+ * @param[in] m       certificate mapping
+ *
+ * @return TRUE the certificate matches
+ *         FALSE the certificate DOES NOT match
+ */
+static krb5_boolean
+match_rfc822_name(
+    const krb5_context context,
+    const hx509_cert *cert,
+    const HDB_Ext_CertificateMapping *m)
+{
+    krb5_error_code ret = 0;
+    krb5_boolean matched = FALSE;
+    size_t j = 0;
+    hx509_octet_string_list list;
+
+    ret = hx509_cert_find_subjectAltName_rfc822(context->hx509ctx,
+                                               *cert,
+                                               &list);
+    if (ret != 0) {
+       return FALSE;
+    }
+
+    for (j = 0, matched = FALSE; j < list.len && !matched; j++) {
+       if (list.val[j].length == m->rfc822->length &&
+           memcmp(m->rfc822->data, list.val[j].data, list.val[j].length) == 0)
+       {
+           matched = TRUE;
+       }
+    }
+    hx509_free_octet_string_list(&list);
+    return matched;
+}
+
+/**
+ * @brief does the SHA1 hash of the certificate public key match the
+ *        value in mapping?
+ *
+ * @param[in] context krb5 context
+ * @param[in] cert    X509 certificate
+ * @param[in] m       certificate mapping
+ *
+ * @return TRUE the certificate matches
+ *         FALSE the certificate DOES NOT match
+ */
+static krb5_boolean
+match_public_key(
+    const krb5_context context,
+    const hx509_cert *cert,
+    const HDB_Ext_CertificateMapping *m)
+{
+    krb5_error_code ret = 0;
+    krb5_boolean matched = FALSE;
+    unsigned char digest[EVP_MAX_MD_SIZE];
+    EVP_MD_CTX *ctx = NULL;
+    unsigned int size = 0;
+
+    SubjectPublicKeyInfo spki;
+    ret = hx509_cert_get_SPKI(context->hx509ctx, *cert, &spki);
+    if (ret != 0) {
+       return FALSE;
+    }
+
+    /*
+     * Compute the SHA1 hash of the certificate subject public key
+     */
+    ctx = EVP_MD_CTX_create();
+    if (ctx == NULL) {
+       goto out;
+    }
+    EVP_DigestInit_ex(ctx, EVP_sha1(), NULL);
+    EVP_DigestUpdate(
+       ctx, spki.subjectPublicKey.data, spki.subjectPublicKey.length/8);
+    EVP_DigestFinal_ex(ctx, digest, &size);
+    EVP_MD_CTX_destroy(ctx);
+
+    /*
+     * Now compare them
+     */
+    if (size == m->public_key->length &&
+       memcmp(digest, m->public_key->data, m->public_key->length) == 0) {
+       matched = TRUE;
+    }
+
+out:
+    free_SubjectPublicKeyInfo(&spki);
+    return matched;
+}
+
+/**
+ * @brief does the certificate SKI match the SKI in mapping?
+ *
+ * @param[in] context krb5 context
+ * @param[in] cert    X509 certificate
+ * @param[in] m       certificate mapping
+ *
+ * @return TRUE the certificate matches
+ *         FALSE the certificate DOES NOT match
+ */
+static krb5_boolean
+match_subject_key_identifier(
+    const krb5_context context,
+    const hx509_cert *cert,
+    const HDB_Ext_CertificateMapping *m)
+{
+    krb5_error_code ret = 0;
+    krb5_boolean matched = FALSE;
+
+    SubjectKeyIdentifier ski;
+    ret = hx509_cert_get_subject_key_identifier(context->hx509ctx, *cert, &ski);
+    if (ret != 0) {
+       return FALSE;
+    }
+    if (der_heim_octet_string_cmp(m->ski, &ski) == 0) {
+       matched = TRUE;
+    }
+    free_SubjectKeyIdentifier(&ski);
+    return matched;
+}
+
+/**
+ * @brief does the certificate serial number match the serial number in mapping?
+ *
+ * @param[in] cert X509 certificate
+ * @param[in] m    certificate mapping
+ *
+ * @return TRUE the certificate matches
+ *         FALSE the certificate DOES NOT match
+ */
+static krb5_boolean
+match_serial_number(
+    const hx509_cert *cert,
+    const HDB_Ext_CertificateMapping *m)
+{
+    krb5_error_code ret = 0;
+    krb5_boolean matched = FALSE;
+    heim_integer serial_number;
+
+    ret = hx509_cert_get_serialnumber(*cert, &serial_number);
+    if (ret != 0) {
+       return FALSE;
+    }
+    if (serial_number.length == m->serial_number->length &&
+       memcmp(serial_number.data,
+               m->serial_number->data,
+               serial_number.length) == 0) {
+       matched = TRUE;
+    }
+    der_free_heim_integer(&serial_number);
+    return matched;
+}
+
+/**
+ * @brief Validate the certificate against the criteria outlined in KB5014754
+ *
+ * @see KB5014754: Certificate-based authentication changes on Windows domain
+ *                 controllers
+ *      https://support.microsoft.com/en-us/topic/
+ *      kb5014754-certificate-based-authentication-changes-on-windows
+ *      -domain-controllers-ad2c23b0-15d8-4340-a468-4d4f3b188f16
+ *
+ * @param context[in] krb5 context
+ * @param client[in]  client hdb record
+ * @param cert[in]    X509 certificate used to sign the request.
+ *
+ * @return 0 no error
+ *         KRB5_KDC_ERR_CLIENT_NOT_TRUSTED certificate fails KB5014754
+ *         otherwise an error occurred processing the request.
+ */
+static krb5_error_code
+pk_check_certificate_binding(
+    krb5_context context,
+    hdb_entry *client,
+    hx509_cert *cert)
+{
+
+    krb5_error_code ret = 0;
+    HDB_extension *ext = NULL;
+    HDB_Ext_CertificateMappings mappings;
+    unsigned int i = 0;
+    krb5_boolean matched = FALSE;
+    krb5_boolean strong_mapping = FALSE;
+
+    memset(&mappings, 0, sizeof(mappings));
+
+    /*
+       * If there is no extension or the enforcement mode is none
+       * then there is nothing to do.
+       */
+    ext = hdb_find_extension(client, choice_HDB_extension_data_cert_mappings);
+    if (ext == NULL) {
+       return 0;
+    }
+    mappings = ext->data.u.cert_mappings;
+    if (mappings.enforcement_mode == hdb_enf_mode_none) {
+       ret = 0;
+       goto out;
+    }
+
+    /*
+     * If there are no mappings then reject the logon
+     */
+    if (mappings.mappings == NULL) {
+       ret = KRB5_KDC_ERR_CERTIFICATE_MISMATCH;
+       krb5_set_error_message(
+           context, ret, "Client has no certificate mappings");
+       goto out;
+    }
+
+    for (i = 0, matched = FALSE; i < mappings.mappings->len && !matched; i++) {
+       HDB_Ext_CertificateMapping *m = &mappings.mappings->val[i];
+
+       strong_mapping = m->strong_mapping;
+       /*
+        * When enforcement mode is full only consider strong mappings
+        */
+       if (mappings.enforcement_mode == hdb_enf_mode_full && !strong_mapping) {
+           continue;
+       }
+
+       if (m->issuer_name != NULL) {
+           hx509_name issuer;
+           ret = hx509_cert_get_issuer(*cert, &issuer);
+           if (ret != 0) {
+               continue;
+           }
+           matched = match_name(context, &issuer, m->issuer_name);
+           hx509_name_free(&issuer);
+           if (!matched) {
+               continue;
+           }
+       }
+       if (m->subject_name != NULL) {
+           hx509_name subject;
+           ret = hx509_cert_get_subject(*cert, &subject);
+           if (ret != 0) {
+               continue;
+           }
+           matched = match_name(context, &subject, m->subject_name);
+           hx509_name_free(&subject);
+           if (!matched) {
+               continue;
+           }
+       }
+       if (m->rfc822 != NULL) {
+           matched = match_rfc822_name(context, cert, m);
+           if (!matched) {
+               continue;
+           }
+       }
+       if (m->ski != NULL) {
+           matched = match_subject_key_identifier(context, cert, m);
+           if (!matched) {
+               continue;
+           }
+       }
+       if (m->public_key != NULL) {
+           matched = match_public_key(context, cert, m);
+           if (!matched) {
+               continue;
+           }
+       }
+       if (m->serial_number != NULL) {
+           matched = match_serial_number(cert, m);
+           if (!matched) {
+               continue;
+           }
+       }
+    }
+
+    /*
+     * When enforcement mode is compatibility need to consider
+     * the age of the certificate for weak mappings
+     */
+    if (mappings.enforcement_mode == hdb_enf_mode_compatibility &&
+       matched &&
+       !strong_mapping) {
+
+       time_t certificate_start = hx509_cert_get_notBefore(*cert);
+       if (mappings.valid_certificate_start > certificate_start) {
+           matched = FALSE;
+       }
+    }
+    if (!matched) {
+       krb5_warnx(context, "PKINIT: No matching certificate mappings");
+       ret = KRB5_KDC_ERR_CERTIFICATE_MISMATCH;
+    }
 out:
     return ret;
 }
@@ -723,6 +1058,10 @@ _kdc_pk_rd_padata(astgs_request_t priv,
            if (ret) {
                goto out;
            }
+           ret = pk_check_certificate_binding(context, client, &cp->cert);
+           if (ret) {
+               goto out;
+           }
        } else if (ret == HX509_CMS_NO_RECIPIENT_CERTIFICATE ||
                   ret == HX509_ISSUER_NOT_FOUND) {
            /*
index d36e6faecd3772f5cce5e2e48f078ebaf74e6de7..5be8d97f9c6da3e181b605aa80fc0f3a6c224c40 100644 (file)
@@ -27,6 +27,9 @@ gen_files_hdb = \
        asn1_HDB_EncTypeList.c \
        asn1_HDB_Ext_Aliases.c \
        asn1_HDB_Ext_Constrained_delegation_acl.c \
+       asn1_HDB_Ext_CertificateMapping.c \
+       asn1_HDB_Ext_CertificateMappings.c \
+       asn1_HDB_Ext_EnforcementMode.c \
        asn1_HDB_Ext_KeyRotation.c \
        asn1_HDB_Ext_KeySet.c \
        asn1_HDB_Ext_KeyTrust.c \
index 9102e2b8f94d58a6ff8350824017f2a4f6df14e8..c890334df635df9254d239d0b8a2ab83a5d296ff 100644 (file)
@@ -196,6 +196,31 @@ HDB-Ext-KeyTrust ::= SEQUENCE OF SEQUENCE {
        pub_key[0] OCTET STRING
 }
 
+HDB-Ext-CertificateMapping ::= SEQUENCE {
+       strong-mapping[0]       BOOLEAN,
+       issuer-name[1]          OCTET STRING OPTIONAL,
+       subject-name[2]         OCTET STRING OPTIONAL,
+       serial-number[3]        OCTET STRING OPTIONAL,
+       ski[4]                  OCTET STRING OPTIONAL,
+       public-key[5]           OCTET STRING OPTIONAL,
+       rfc822[6]               OCTET STRING OPTIONAL,
+       ...
+}
+
+HDB-Ext-EnforcementMode ::= ENUMERATED {
+       hdb_enf_mode_none               (0),
+       hdb_enf_mode_compatibility      (1),
+       hdb_enf_mode_full               (2),
+       ...
+}
+
+HDB-Ext-CertificateMappings ::= SEQUENCE {
+       enforcement_mode[0]         HDB-Ext-EnforcementMode,
+       valid_certificate_start[1]  KerberosTime,
+       mappings[2] SEQUENCE OF     HDB-Ext-CertificateMapping OPTIONAL,
+       ...
+}
+
 HDB-extension ::= SEQUENCE {
         mandatory[0]    BOOLEAN,        -- kdc MUST understand this extension,
                                         --   if not the whole entry must
@@ -218,6 +243,7 @@ HDB-extension ::= SEQUENCE {
                key-rotation[14]                HDB-Ext-KeyRotation,
                krb5-config[15]                 OCTET STRING,
                key-trust[16]                   HDB-Ext-KeyTrust,
+               cert-mappings[17]               HDB-Ext-CertificateMappings,
                ...
        },
        ...
index 6ef67410b2d36c4e703dd4a2c0aadbca7f2d0d0c..5e17e8b38130defcf9d92ac950e8de605b85d895 100644 (file)
@@ -160,6 +160,9 @@ HEIMDAL_HDB_1.0 {
                free_HDB_Ext_KeyRotation;
                free_HDB_Ext_KeySet;
                free_HDB_Ext_KeyTrust;
+               free_HDB_Ext_KeyTrust;
+               free_HDB_Ext_CertificateMapping;
+               free_HDB_Ext_CertificateMappings;
                free_HDB_Ext_PKINIT_acl;
                free_hdb_keyset;
                free_HDB_keyset;
index 9c7997dc46e4923d98c91ee10f4df97e5e00485c..0034416e9feb43677e1acb1a150e989fadbf9e4f 100644 (file)
@@ -33,6 +33,7 @@
 
 #include "hx_locl.h"
 #include "crypto-headers.h"
+#include "rfc2459_asn1.h"
 #include <rtbl.h>
 
 /**
@@ -964,6 +965,63 @@ hx509_cert_find_subjectAltName_otherName(hx509_context context,
     }
 }
 
+/**
+ * @brief Return a list of rfc822 subject alternative names (email addresses)
+ *
+ * @param[in]  context hx509 context.
+ * @param[in]  cert    hx509 certificate
+ * @param[out] list    of rfc822 subjectAltNames.
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @ingroup hx509_cert
+ *
+ * @note The returned list of octet string should be freed with
+ *        hx509_free_octet_string_list().
+ *
+ */
+HX509_LIB_FUNCTION int HX509_LIB_CALL
+hx509_cert_find_subjectAltName_rfc822(hx509_context context,
+                                     hx509_cert cert,
+                                     hx509_octet_string_list *list)
+{
+    GeneralNames sa;
+    int ret = 0;
+    size_t i = 0;
+    size_t j = 0;
+
+    list->val = NULL;
+    list->len = 0;
+
+    i = 0;
+    while (1) {
+       ret = find_extension_subject_alt_name(_hx509_get_cert(cert), &i, &sa);
+       if (ret == HX509_EXTENSION_NOT_FOUND) {
+           return 0;
+       } else if (ret != 0) {
+           hx509_set_error_string(
+               context, 0, ret, "Error searching for RFC822 names");
+           hx509_free_octet_string_list(list);
+           return ret;
+       }
+
+       for (j = 0; j < sa.len; j++) {
+           if (sa.val[j].element == choice_GeneralName_rfc822Name) {
+               ret = add_to_list(list, &sa.val[j].u.rfc822Name);
+               if (ret) {
+                   hx509_set_error_string(context, 0, ret,
+                                          "Error adding an extra RFC822 name "
+                                          "to return list");
+                   hx509_free_octet_string_list(list);
+                   free_GeneralNames(&sa);
+                   return ret;
+               }
+           }
+       }
+       free_GeneralNames(&sa);
+    }
+}
+
 
 static int
 check_key_usage(hx509_context context, const Certificate *cert,
@@ -1867,6 +1925,40 @@ hx509_cert_get_subject_unique_id(hx509_context context, hx509_cert p, heim_bit_s
     return get_x_unique_id(context, "subject", p->data->tbsCertificate.subjectUniqueID, subject);
 }
 
+/**
+ * @brief get the (SKI) Subject Key Identifier from a certificate
+ *
+ * @param[in]  context hx509 context
+ * @param[in]  cert    hx509 certificate
+ * @param[out] ski     subject key identifier
+ *
+ * @return An hx509 error code, see hx509_get_error_string().
+ *
+ * @note ski should be freed with a call to free_SubjectKeyIdentifier
+ */
+HX509_LIB_FUNCTION int HX509_LIB_CALL
+hx509_cert_get_subject_key_identifier(hx509_context context,
+                                      hx509_cert cert,
+                                      SubjectKeyIdentifier *ski)
+{
+    size_t i = 0;
+    size_t size = 0;
+    const Extension *ext = NULL;
+    int ret = 0;
+
+    ext = find_extension(cert->data,
+                        &asn1_oid_id_x509_ce_subjectKeyIdentifier,
+                        &i);
+    if (ext == NULL) {
+       return HX509_EXTENSION_NOT_FOUND;
+    }
+    ret = decode_SubjectKeyIdentifier(ext->extnValue.data,
+                                     ext->extnValue.length,
+                                     ski,
+                                     &size);
+    return ret;
+}
+
 
 HX509_LIB_FUNCTION hx509_private_key HX509_LIB_CALL
 _hx509_cert_private_key(hx509_cert p)
index 27f5f51f76776929ebc649bb2b195e6957ed7955..9ba621d68a25603a47ef8ca4ac3e30d45d646fe3 100644 (file)
@@ -95,6 +95,7 @@ HEIMDAL_X509_1.2 {
                hx509_cert_check_eku;
                hx509_cert_cmp;
                hx509_cert_find_subjectAltName_otherName;
+               hx509_cert_find_subjectAltName_rfc822;
                hx509_cert_free;
                hx509_cert_get_SPKI;
                hx509_cert_get_SPKI_AlgorithmIdentifier;
@@ -109,6 +110,7 @@ HEIMDAL_X509_1.2 {
                hx509_cert_get_subject;
                hx509_cert_get_issuer_unique_id;
                hx509_cert_get_subject_unique_id;
+               hx509_cert_get_subject_key_identifier;
                hx509_cert_have_private_key;
                hx509_cert_have_private_key_only;
                hx509_cert_init;
index 2b4062e6abd97838be52b833981575bc6f4d5aee..1294c1e4bd050c58063645d466c083b34809e313 100644 (file)
@@ -79,6 +79,7 @@ error_code CLIENT_NOT_TRUSTED,        "Client not trusted"
 error_code KDC_NOT_TRUSTED,    "KDC not trusted"
 error_code INVALID_SIG,                "Invalid signature"
 error_code DH_KEY_PARAMETERS_NOT_ACCEPTED, "DH parameters not accepted"
+error_code CERTIFICATE_MISMATCH, "The incorrect certificate was used"
 
 index 68
 prefix KRB5_KDC_ERR