The branch, master has been updated
       via  8cb97336ac3 third_party:heimdal: import 
lorikeet-heimdal-202509242121
      from  8aaa73e5e90 vfs_fruit: Call fruit_fstatat() from fruit_[l]stat()

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 8cb97336ac3955cdea2cf81c141148c1b258abd7
Author: Gary Lockyer <[email protected]>
Date:   Tue Sep 30 12:25:51 2025 +1300

    third_party:heimdal: import lorikeet-heimdal-202509242121
    
    (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 <[email protected]>
    Reviewed-by: Jennifer Sutton <[email protected]>
    
    Autobuild-User(master): Jennifer Sutton <[email protected]>
    Autobuild-Date(master): Wed Oct  8 21:12:44 UTC 2025 on atb-devel-224

-----------------------------------------------------------------------

Summary of changes:
 third_party/heimdal/.gitignore                   |   4 +
 third_party/heimdal/kdc/kerberos5.c              |   2 +
 third_party/heimdal/kdc/pkinit.c                 | 351 ++++++++++++++++++++++-
 third_party/heimdal/lib/hdb/Makefile.am          |   3 +
 third_party/heimdal/lib/hdb/hdb.asn1             |  26 ++
 third_party/heimdal/lib/hdb/version-script.map   |   3 +
 third_party/heimdal/lib/hx509/cert.c             |  92 ++++++
 third_party/heimdal/lib/hx509/version-script.map |   2 +
 third_party/heimdal/lib/krb5/krb5_err.et         |   1 +
 9 files changed, 478 insertions(+), 6 deletions(-)


Changeset truncated at 500 lines:

diff --git a/third_party/heimdal/.gitignore b/third_party/heimdal/.gitignore
index 6a56c208980..aecf4f611a4 100644
--- a/third_party/heimdal/.gitignore
+++ b/third_party/heimdal/.gitignore
@@ -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
diff --git a/third_party/heimdal/kdc/kerberos5.c 
b/third_party/heimdal/kdc/kerberos5.c
index d0c646c328c..80048109493 100644
--- a/third_party/heimdal/kdc/kerberos5.c
+++ b/third_party/heimdal/kdc/kerberos5.c
@@ -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;
        }
diff --git a/third_party/heimdal/kdc/pkinit.c b/third_party/heimdal/kdc/pkinit.c
index 6fdae939f8c..ea734dd50c7 100644
--- a/third_party/heimdal/kdc/pkinit.c
+++ b/third_party/heimdal/kdc/pkinit.c
@@ -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) {
            /*
diff --git a/third_party/heimdal/lib/hdb/Makefile.am 
b/third_party/heimdal/lib/hdb/Makefile.am
index d36e6faecd3..5be8d97f9c6 100644
--- a/third_party/heimdal/lib/hdb/Makefile.am
+++ b/third_party/heimdal/lib/hdb/Makefile.am
@@ -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 \
diff --git a/third_party/heimdal/lib/hdb/hdb.asn1 
b/third_party/heimdal/lib/hdb/hdb.asn1
index 9102e2b8f94..c890334df63 100644
--- a/third_party/heimdal/lib/hdb/hdb.asn1
+++ b/third_party/heimdal/lib/hdb/hdb.asn1
@@ -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,
                ...
        },
        ...
diff --git a/third_party/heimdal/lib/hdb/version-script.map 
b/third_party/heimdal/lib/hdb/version-script.map
index 6ef67410b2d..5e17e8b3813 100644
--- a/third_party/heimdal/lib/hdb/version-script.map
+++ b/third_party/heimdal/lib/hdb/version-script.map
@@ -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;
diff --git a/third_party/heimdal/lib/hx509/cert.c 
b/third_party/heimdal/lib/hx509/cert.c
index 9c7997dc46e..0034416e9fe 100644
--- a/third_party/heimdal/lib/hx509/cert.c
+++ b/third_party/heimdal/lib/hx509/cert.c
@@ -33,6 +33,7 @@
 


-- 
Samba Shared Repository

Reply via email to