]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
s4:kdc:sdb Support Windows flexible cert mappings
authorGary Lockyer <gary@catalyst.net.nz>
Mon, 1 Sep 2025 21:59:13 +0000 (09:59 +1200)
committerJennifer Sutton <jsutton@samba.org>
Fri, 10 Oct 2025 01:27:31 +0000 (01:27 +0000)
Extract certificate mappings from the altSecurityIdentities attribute and
populate the new sdb_certificate_mappings element of sdb

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Jennifer Sutton <jennifersutton@catalyst.net.nz>
source4/auth/sam.c
source4/kdc/db-glue.c
source4/kdc/sdb.c
source4/kdc/sdb.h
source4/kdc/tests/db-glue-test.c

index 32d90712aae0f8f24b53b01dcec82402f0657ee5..55bcd746b7c9b140e07c65f3e7b00c948e7e442d 100644 (file)
@@ -69,7 +69,9 @@
        "msDS-ManagedPasswordInterval",         \
        "whenCreated",                          \
        /* Required for Key Trust authentication */\
-       "msDS-KeyCredentialLink"
+       "msDS-KeyCredentialLink",                \
+       /* Required for certificate mapping */  \
+       "altSecurityIdentities"
 
 #define AUTHN_POLICY_ATTRS                     \
        /* Required for authentication policies / silos */ \
index aecf51f2443092a8b0ce2ed88d6fb891d9b4091e..c7dc01fb812cdde3ab7e981479b61ebadf386bfb 100644 (file)
@@ -47,7 +47,9 @@
 #include "lib/messaging/irpc.h"
 #include "librpc/gen_ndr/ndr_keycredlink.h"
 #include "talloc.h"
+#include "util/data_blob.h"
 #include "util/debug.h"
+#include "util/samba_util.h"
 
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_KERBEROS
@@ -1187,6 +1189,467 @@ static krb5_error_code samba_kdc_get_entry_principal(
 
        return code;
 }
+
+
+/**
+ * @brief Copy the contents of a data blob to a krb5_data element
+ *
+ * @param[in]  blob  The source data blob
+ * @param[out] krb5  The target krb5_data element
+ *
+ * @return 0      No error
+ *         ENOMEM memory allocation error
+ *
+ * @note Memory is allocated, with malloc and needs to be freed
+ */
+static krb5_error_code data_blob_to_krb5_data( DATA_BLOB *blob, krb5_data *krb5)
+{
+       krb5->data = malloc(blob->length);
+       if (krb5->data == NULL) {
+               return ENOMEM;
+       }
+       memcpy(krb5->data, blob->data, blob->length);
+       krb5->length = blob->length;
+       return 0;
+}
+
+
+/**
+ * @brief Copy the contents of a hex string data blob to a binary
+ *        krb5_data element
+ *
+ * @param[in]  blob  The source data blob
+ * @param[out] krb5  The target krb5_data element
+ *
+ * @return 0      No error
+ *         ENOMEM memory allocation error
+ *         EINVAL data blob is not a valid hex string encoding
+ *
+ * @note Memory is allocated, with malloc and needs to be freed
+ */
+static krb5_error_code db_hex_str_to_krb5_data(
+       DATA_BLOB *blob,
+       krb5_data *krb5)
+{
+
+       size_t size = 0;
+
+       if( (blob->length%2) != 0) {
+               DBG_ERR(
+                       "Hex string [%*.*s] "
+                       "does not have an even length",
+                       (int) blob->length,
+                       (int) blob->length,
+                       (char *) blob->data);
+               return EINVAL;
+       }
+       krb5->length = (blob->length/2);
+       krb5->data = malloc(krb5->length);
+       if (krb5->data == NULL) {
+               krb5->length = 0;
+               return ENOMEM;
+       }
+       size = strhex_to_str(krb5->data,
+                            krb5->length,
+                            (const char *) blob->data,
+                            blob->length);
+       if (size != krb5->length) {
+               krb5->length = 0;
+               SAFE_FREE(krb5->data);
+               return EINVAL;
+       }
+       return 0;
+}
+
+/*
+ * Helper macro to populate the data blob constants used by
+ * populate_certificate_mapping and parse_certificate_mapping
+ */
+#define DATA_BLOB_STRING(str) {\
+       .data = discard_const_p(uint8_t, str), \
+       .length = sizeof(str) - 1 \
+}
+static const DATA_BLOB ISSUER_NAME = DATA_BLOB_STRING("I");
+static const DATA_BLOB SUBJECT_NAME = DATA_BLOB_STRING("S");
+static const DATA_BLOB SERIAL_NUMBER = DATA_BLOB_STRING("SR");
+static const DATA_BLOB SUBJECT_KEY_IDENTIFIER = DATA_BLOB_STRING("SKI");
+static const DATA_BLOB PUBLIC_KEY = DATA_BLOB_STRING("SHA1-PUKEY");
+static const DATA_BLOB RFC822 = DATA_BLOB_STRING("RFC822");
+static const DATA_BLOB X509_HEADER = DATA_BLOB_STRING("X509:");
+#undef DATA_BLOB_STRING
+
+/**
+ * @brief Populate the certificate mapping from the tag and value
+ *
+ * @param[in]     tag      the tag i.e. I, S, SKI, .....
+ * @param[in]     value    the value associated with the tag
+ * @param[in,out] mapping  the mapping to be updated
+ *
+ * @return      0 No error
+ *         EINVAL tag or value are invalid
+ *         ENOMEM memory allocation error
+ *
+ * @note Memory is allocated, with malloc and needs to be freed with
+ *       sdb_certificate_mapping_free
+ */
+static krb5_error_code populate_certificate_mapping(
+       DATA_BLOB *tag,
+       DATA_BLOB *value,
+       struct sdb_certificate_mapping *mapping)
+{
+       krb5_error_code ret = 0;
+
+       if (tag->length == 0) {
+               DBG_WARNING("altSecurityIdentities empty tag");
+               return EINVAL;
+       }
+       if (value->length == 0) {
+               DBG_WARNING("altSecurityIdentities no value for %*.*s",
+                           (int) tag->length,
+                           (int) tag->length,
+                           tag->data);
+               return EINVAL;
+       }
+
+       if (data_blob_cmp(&ISSUER_NAME, tag) == 0) {
+               /* discard any previous value */
+               if (mapping->issuer_name.data != NULL) {
+                       SAFE_FREE(mapping->issuer_name.data);
+                       mapping->issuer_name.length = 0;
+               }
+               ret = data_blob_to_krb5_data(value, &mapping->issuer_name);
+
+       } else if (data_blob_cmp(&SUBJECT_NAME, tag) == 0) {
+               /* discard any previous value */
+               if (mapping->subject_name.data != NULL) {
+                       SAFE_FREE(mapping->subject_name.data);
+                       mapping->subject_name.length = 0;
+               }
+               ret = data_blob_to_krb5_data(value, &mapping->subject_name);
+
+       } else if (data_blob_cmp(&RFC822, tag) == 0) {
+               /* discard any previous value */
+               if (mapping->rfc822.data != NULL) {
+                       SAFE_FREE(mapping->rfc822.data);
+                       mapping->rfc822.length = 0;
+               }
+               ret = data_blob_to_krb5_data(value, &mapping->rfc822);
+
+       } else if (data_blob_cmp(&SERIAL_NUMBER, tag ) == 0) {
+               /* discard any previous value */
+               if (mapping->serial_number.data != NULL) {
+                       SAFE_FREE(mapping->serial_number.data);
+                       mapping->serial_number.length = 0;
+               }
+               ret = db_hex_str_to_krb5_data(value, &mapping->serial_number);
+
+       } else if (data_blob_cmp(&SUBJECT_KEY_IDENTIFIER, tag) == 0) {
+               /* discard any previous value */
+               if (mapping->ski.data != NULL) {
+                       SAFE_FREE(mapping->ski.data);
+                       mapping->ski.length = 0;
+               }
+               ret = db_hex_str_to_krb5_data(value, &mapping->ski);
+
+       } else if (data_blob_cmp(&PUBLIC_KEY, tag) == 0) {
+               /* discard any previous value */
+               if (mapping->public_key.data != NULL) {
+                       SAFE_FREE(mapping->public_key.data);
+                       mapping->public_key.length = 0;
+               }
+               ret = db_hex_str_to_krb5_data(value, &mapping->public_key);
+
+       } else {
+               DBG_WARNING("altSecurityIdentities invalid tag %*.*s",
+                           (int) tag->length,
+                           (int) tag->length,
+                           tag->data);
+               ret = EINVAL;
+       }
+       return ret;
+}
+
+
+/**
+ * @brief does the krb5 element have a value?
+ *
+ * @param[in] krb5  The target krb5_data element
+ *
+ * @return TRUE  krb5 has a value
+ *         FALSE krb5 has no value i.e. it's empty
+ */
+static krb5_boolean krb5_data_has_value(krb5_data *krb5)
+{
+       if (krb5->data == NULL || krb5->length == 0) {
+               return FALSE;
+       }
+       return TRUE;
+}
+/**
+ * @brief is the certificate mapping a strong mapping?
+ *
+ * @param[in] mapping the certificate mapping to examine.
+ *
+ * @return TRUE  mapping is strong
+ *         FALSE mapping is weak
+ */
+static krb5_boolean is_strong_certificate_mapping(
+       struct sdb_certificate_mapping *mapping)
+{
+       /* Subject Key Identifier */
+       if (krb5_data_has_value(&mapping->ski)) {
+               return TRUE;
+       }
+       /* Public Key */
+       if (krb5_data_has_value(&mapping->public_key)) {
+               return TRUE;
+       }
+       /* Issuer Serial Number */
+       if (krb5_data_has_value(&mapping->issuer_name) &&
+           krb5_data_has_value(&mapping->serial_number)
+       ) {
+               return TRUE;
+       }
+       return FALSE;
+}
+
+
+/**
+ * @brief Parse a certificate mapping string
+ *
+ *  The expected format is a header "X509:" and then a series of
+ *  tag value pairs "<tag>value"
+ *  where tag is one of:
+ *     <I>           Issuer Name
+ *     <S>           Subject Name
+ *     <SR>          Serial Number
+ *     <SKI>         SKI Subject Key Identifier
+ *     <SHA1-PUBKEY> SHA1 checksum of the public key
+ *     <RFC822>      Email address
+ *
+ *
+ * @param[in]  value   ldb value containing an altSecurityIdentities entry
+ * @param[out] mapping data parsed from value
+ *
+ * @note it is the callers responsibility to free any memory allocated
+ *       in the mapping with a call to sdb_certificate_mapping_free.
+ *       EVEN if an error is returned, as mapping may have been partially
+ *       updated.
+ *
+ * @return 0      No error
+ *         EINVAL altSecurityIdentities entry was invalid
+ *         ENOMEM memory allocation error
+ */
+static krb5_error_code parse_certificate_mapping(
+       struct ldb_val *ldb_value,
+       struct sdb_certificate_mapping *mapping)
+{
+       krb5_error_code ret = 0;
+       size_t length = ldb_value->length;
+       uint8_t *data = ldb_value->data;
+       DATA_BLOB tag = data_blob_null;
+       DATA_BLOB value = data_blob_null;
+       enum {
+               start_state,
+               tag_state,
+               value_state
+       } state;
+       size_t i = 0;
+
+       TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+       if (tmp_ctx == NULL) {
+               return ENOMEM;
+       }
+
+       /*
+        * Ensure that there is data, and it starts with X509:
+        * other wise ignore the entry and return ENOENT
+        */
+       if (data == NULL || length == 0) {
+               DBG_DEBUG("altSecurityIdentities, is empty");
+               ret = ENOENT;
+               goto out;
+       }
+       if (length <= X509_HEADER.length ||
+           memcmp(X509_HEADER.data, data, X509_HEADER.length) != 0) {
+               DBG_DEBUG("altSecurityIdentities, entry is not X509 ignoring");
+               ret = ENOENT;
+               goto out;
+       }
+
+       tag = data_blob_talloc(tmp_ctx, NULL, ldb_value->length + 1);
+       if (tag.data == NULL) {
+               ret = ENOMEM;
+               goto out;
+       }
+       tag.length = 0;
+       value = data_blob_talloc(tmp_ctx, NULL, ldb_value->length + 1);
+       if (value.data == NULL) {
+               ret = ENOMEM;
+               goto out;
+       }
+       value.length = 0;
+
+       state = start_state;
+       /* point to the first byte after the header "X509:" */
+       for( i = 5; i < length; i++) {
+               uint8_t c = data[i];
+               switch (state) {
+               case start_state:
+                       /* Ignore characters between the : and the first < */
+                       if (c == '<') {
+                               state = tag_state;
+                               tag.length = 0;
+                       }
+                       break;
+               case tag_state:
+                       if (c == '>') {
+                               state = value_state;
+                               tag.data[tag.length] = '\0';
+                               value.length = 0;
+                       } else {
+                               tag.data[tag.length] = c;
+                               tag.length++;
+                       }
+                       break;
+               case value_state:
+                       if (c == '<') {
+                               value.data[value.length] = '\0';
+                               ret = populate_certificate_mapping(
+                                       &tag, &value, mapping);
+                               if (ret != 0) {
+                                       goto out;
+                               }
+                               state = tag_state;
+                               value.length = 0;
+                               tag.length = 0;
+                       } else {
+                               value.data[value.length] = c;
+                               value.length++;
+                       }
+                       break;
+               }
+       }
+       if (state != value_state) {
+               DBG_WARNING("altSecurityIdentities, expected a value");
+               ret = EINVAL;
+               goto out;
+       }
+       value.data[value.length] = '\0';
+       ret = populate_certificate_mapping(
+               &tag, &value, mapping);
+       if (ret != 0) {
+               goto out;
+       }
+       mapping->strong_mapping = is_strong_certificate_mapping(mapping);
+
+out:
+       TALLOC_FREE(tmp_ctx);
+       return ret;
+}
+
+
+/**
+ * @brief extract the certificate mappings for PKINIT from the
+ *        ldb message.
+ *
+ * Processes the "X509:" certificate mappings in altSecurityIdentities.
+ *
+ * @param mem_ctx[in]  talloc memory context
+ * @param lp_ctx[in]   parameter context containing the config options
+ * @param msg[in]      ldb message containing the certificate mappings
+ * @param entry[out]   entry will be updated with the certificate mappings
+ *
+ * @note Invalid entries will be ignored
+ *
+ * @return 0  No error, and there are zero or more certificate mappings
+ *         >0 Errors detected
+ */
+static krb5_error_code get_certificate_mappings(
+       TALLOC_CTX *mem_ctx,
+       struct loadparm_context *lp_ctx,
+       struct ldb_message *msg,
+       struct sdb_entry *entry)
+{
+       krb5_error_code ret = 0;
+       struct ldb_message_element *el = NULL;
+       size_t i = 0;
+       struct sdb_certificate_mappings mappings = {};
+       unsigned int backdating = 0;
+       time_t created = 0;
+
+       TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+       if (tmp_ctx == NULL) {
+               return ENOMEM;
+       }
+
+       mappings.enforcement_mode =
+               lpcfg_strong_certificate_binding_enforcement(lp_ctx);
+
+       backdating = lpcfg_certificate_backdating_compensation(lp_ctx);
+       created = ldb_msg_find_krb5time_ldap_time(msg, "whenCreated", 0);
+       if (created == 0) {
+               DBG_ERR("No whenCreated entry, unable to continue");
+               ret = EINVAL;
+               goto out;
+       }
+       mappings.valid_certificate_start = created - (backdating * 60);
+
+       el = ldb_msg_find_element(msg, "altSecurityIdentities");
+       if (el == NULL || el->num_values == 0) {
+               DBG_DEBUG("No altSecurityIdentities nothing to do");
+               ret = 0;
+               entry->mappings = mappings;
+               goto out;
+       }
+
+       for (i = 0; i < el->num_values; i++) {
+               struct sdb_certificate_mapping mapping = {};
+               ret = parse_certificate_mapping(&el->values[i], &mapping);
+               if (ret != 0) {
+                       DBG_DEBUG("Ignoring invalid altSecurityIdentities"
+                                 " entry [%*.*s]",
+                                 (int)el->values[i].length,
+                                 (int)el->values[i].length,
+                                 (char *)el->values[i].data);
+                       sdb_certificate_mapping_free(&mapping);
+                       continue;
+               }
+               if (mappings.mappings == NULL) {
+                       mappings.len = 0;
+                       mappings.mappings = calloc(1, sizeof(mapping));
+                       if (mappings.mappings == NULL) {
+                               sdb_certificate_mapping_free(&mapping);
+                               ret = ENOMEM;
+                               goto out;
+                       }
+               } else {
+                       struct sdb_certificate_mapping *old_mappings =
+                               mappings.mappings;
+                       mappings.mappings= realloc_p(
+                               mappings.mappings,
+                               struct sdb_certificate_mapping,
+                               mappings.len + 1);
+                       if (mappings.mappings == NULL) {
+                               mappings.mappings = old_mappings;
+                               sdb_certificate_mappings_free(&mappings);
+                               sdb_certificate_mapping_free(&mapping);
+                               ret = ENOMEM;
+                               goto out;
+                       }
+               }
+               mappings.mappings[mappings.len] = mapping;
+               mappings.len++;
+       }
+       entry->mappings = mappings;
+       ret = 0;
+
+out:
+       TALLOC_FREE(tmp_ctx);
+       return ret;
+}
+
+
 /**
  * @brief Extract the KeyMaterial from a KEYCREDENTIALLINK_BLOB
  *        as a KeyMaterialInternal structure.
@@ -1232,7 +1695,6 @@ static krb5_error_code unpack_key_credential_link_blob(
        }
        *pub_key = NULL;
 
-       /* Unpack the binary DN */
        dsdb_dn = dsdb_dn_parse(tmp_ctx, ldb, value, DSDB_SYNTAX_BINARY_DN);
        if (dsdb_dn == NULL) {
                DBG_WARNING("Unable to parse KEYCREDENTIALLINK_BLOB, BinaryDn");
@@ -2189,6 +2651,11 @@ static krb5_error_code samba_kdc_message2entry(krb5_context context,
        if (ret != 0) {
                goto out;
        }
+       ret = get_certificate_mappings(
+               tmp_ctx, kdc_db_ctx->lp_ctx, msg, entry);
+       if (ret != 0) {
+               goto out;
+       }
 
        talloc_steal(kdc_db_ctx, p);
 
index d9399452eaa3484d30b4bce95b090c84f2b1ca0a..280e7930480bee4b02de44359d468aec51de6cb5 100644 (file)
@@ -105,6 +105,48 @@ void sdb_pub_keys_free(struct sdb_pub_keys *keys)
        ZERO_STRUCTP(keys);
 }
 
+/**
+ * @brief free the memory allocated to a sdb certificate mapping structure.
+ *
+ * @param[in,out] m mapping to be freed, will be zeroed on return
+ */
+void sdb_certificate_mapping_free(struct sdb_certificate_mapping *m)
+{
+       if (m == NULL) {
+               return;
+       }
+
+       SAFE_FREE(m->subject_name.data);
+       SAFE_FREE(m->issuer_name.data);
+       SAFE_FREE(m->serial_number.data);
+       SAFE_FREE(m->ski.data);
+       SAFE_FREE(m->public_key.data);
+       SAFE_FREE(m->rfc822.data);
+
+       ZERO_STRUCTP(m);
+}
+/**
+ *
+ * @brief free the memory allocated to a sdb certificate mappings structure.
+ *
+ * @param[in,out] m mappings to be freed, will be zeroed on return
+ */
+void sdb_certificate_mappings_free(struct sdb_certificate_mappings *m)
+{
+       unsigned int i;
+
+       if (m == NULL) {
+               return;
+       }
+
+       for (i = 0; i < m->len; i++) {
+               sdb_certificate_mapping_free(&m->mappings[i]);
+       }
+
+       SAFE_FREE(m->mappings);
+       ZERO_STRUCTP(m);
+}
+
 void sdb_entry_free(struct sdb_entry *s)
 {
        if (s->skdc_entry != NULL) {
@@ -121,6 +163,7 @@ void sdb_entry_free(struct sdb_entry *s)
 
        sdb_keys_free(&s->keys);
        sdb_pub_keys_free(&s->pub_keys);
+       sdb_certificate_mappings_free(&s->mappings);
 
        if (s->etypes != NULL) {
                SAFE_FREE(s->etypes->val);
index 59676c2842ce6399c1d71085c336cfe79ee28354..f7efc1f304c471cf82c3e4f44281e1ac1f840929 100644 (file)
@@ -95,6 +95,25 @@ struct sdb_pub_keys {
        struct sdb_pub_key *keys;
 };
 
+struct sdb_certificate_mappings {
+       int enforcement_mode;
+       time_t valid_certificate_start;
+       unsigned int len;
+       struct sdb_certificate_mapping *mappings;
+};
+
+
+struct sdb_certificate_mapping {
+       krb5_boolean strong_mapping;
+       krb5_data issuer_name;
+       krb5_data subject_name;
+       krb5_data serial_number;
+       krb5_data ski;
+       krb5_data public_key;
+       krb5_data rfc822;
+};
+
+
 struct sdb_entry {
        struct samba_kdc_entry *skdc_entry;
        krb5_principal principal;
@@ -113,6 +132,7 @@ struct sdb_entry {
        int *max_renew;
        struct SDBFlags flags;
        struct sdb_pub_keys pub_keys;
+       struct sdb_certificate_mappings mappings;
 };
 
 #define SDB_ERR_NOENTRY 36150275
@@ -159,6 +179,8 @@ struct sdb_entry {
 #define SDB_F_FORCE_CANON              0x4000  /* force canonicalization */
 #define SDB_F_RODC_NUMBER_SPECIFIED    0x8000  /* we want a particular RODC number */
 
+void sdb_certificate_mapping_free(struct sdb_certificate_mapping *m);
+void sdb_certificate_mappings_free(struct sdb_certificate_mappings *m);
 void sdb_pub_key_free(struct sdb_pub_key *key);
 void sdb_pub_keys_free(struct sdb_pub_keys *keys);
 void sdb_key_free(struct sdb_key *key);
index caf365d1093f48f2f2a78bc68099252639f2d08f..d98b143779df30134a9e0531b8f42093210d6a45 100644 (file)
@@ -43,6 +43,7 @@
 #include "krb5-protos.h"
 #include "ldb.h"
 #include "samdb/samdb.h"
+#include "sdb.h"
 #include "talloc.h"
 #include "util/data_blob.h"
 #include "util/debug.h"
@@ -56,34 +57,82 @@ int dsdb_functional_level(struct ldb_context *ldb)
        return 1;
 }
 
+int certificate_binding_enforcement = 0;
+int lpcfg_strong_certificate_binding_enforcement(
+       struct loadparm_context *lp_ctx)
+{
+       return certificate_binding_enforcement;
+}
+
+int certificate_backdating_compensation = 0;
+int lpcfg_certificate_backdating_compensation(
+       struct loadparm_context *lp_ctx)
+{
+       return certificate_backdating_compensation;
+}
+
 /******************************************************************************
  * Test helper functions
  *****************************************************************************/
-static void add_msDS_KeyCredentialLink(TALLOC_CTX *mem_ctx,
-                                      struct ldb_message *msg,
+static void add_msDS_KeyCredentialLink(struct ldb_message *msg,
                                       size_t size,
                                       uint8_t *data)
 {
        DATA_BLOB key_cred_val = {.length = size, .data = data};
-       char *hex_value = data_blob_hex_string_upper(mem_ctx, &key_cred_val);
+       char *hex_value = data_blob_hex_string_upper(msg, &key_cred_val);
        size_t hex_len = strlen(hex_value);
        char *binary_dn = talloc_asprintf(
-               mem_ctx, "B:%zu:%s:DC=EXAMPLE,DC=COM", hex_len, hex_value);
+               msg, "B:%zu:%s:DC=EXAMPLE,DC=COM", hex_len, hex_value);
        TALLOC_FREE(hex_value);
 
        /* Add the data to msDS-KeyCredentialLink */
        ldb_msg_add_string(msg, "msDS-KeyCredentialLink", binary_dn);
 }
 
+static struct ldb_val *get_ldb_string(TALLOC_CTX *mem_ctx, const char * str) {
+       char *string = talloc_asprintf(
+               mem_ctx, "%s", str);
+
+       size_t len = strlen(string);
+
+       struct ldb_val *value = talloc_zero(mem_ctx, struct ldb_val);
+
+       value->data = (uint8_t *) string;
+       value->length = len;
+       return value;
+}
+
+static void add_altSecurityIdentities(struct ldb_message *msg,
+                                     const char *str)
+{
+       /* Add the data to altSecurityIdentities */
+       ldb_msg_add_string(msg, "altSecurityIdentities", str);
+}
+
+static void add_whenCreated(struct ldb_message *msg,
+                           time_t created)
+{
+       char* ts = ldb_timestring(msg, created);
+       ldb_msg_add_string(msg, "whenCreated", ts);
+}
+
 static void add_empty_msDS_KeyCredentialLink_DN(TALLOC_CTX *mem_ctx,
                                                struct ldb_message *msg)
 {
-       char *binary_dn = talloc_asprintf(mem_ctx, "B:0::DC=EXAMPLE,DC=COM");
+       char *binary_dn = talloc_asprintf(msg, "B:0::DC=EXAMPLE,DC=COM");
 
        /* Add the data to msDS-KeyCredentialLink */
        ldb_msg_add_string(msg, "msDS-KeyCredentialLink", binary_dn);
 }
 
+static void add_empty_altSecurities(
+       TALLOC_CTX *mem_ctx,
+       struct ldb_message *msg)
+{
+       /* Add an empty altSecurityIdentities */
+       ldb_msg_add_string(msg, "altSecurityIdentifiers", "");
+}
+
 static struct ldb_message *create_ldb_message(TALLOC_CTX *mem_ctx)
 {
        DATA_BLOB sid_val = data_blob_null;
@@ -473,6 +522,7 @@ static void empty_message2entry(void **state)
                                  "atestuser@test.samba.org");
 
        msg = ldb_msg_new(mem_ctx);
+
        err = samba_kdc_message2entry(krb5_ctx,
                                      kdc_db_ctx,
                                      mem_ctx,
@@ -515,6 +565,7 @@ static void minimal_message2entry(void **state)
 
        struct sdb_entry entry = {};
        krb5_error_code err = 0;
+       time_t now = time(NULL);
 
        /* Set up */
        smb_krb5_init_context_common(&krb5_ctx);
@@ -525,6 +576,7 @@ static void minimal_message2entry(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
+       add_whenCreated(msg, now);
 
        err = samba_kdc_message2entry(krb5_ctx,
                                      kdc_db_ctx,
@@ -570,6 +622,7 @@ static void empty_binary_dn_message2entry(void **state)
 
        struct sdb_entry entry = {};
        krb5_error_code err = 0;
+       time_t now = time(NULL);
 
        /* Set up */
        smb_krb5_init_context_common(&krb5_ctx);
@@ -582,6 +635,7 @@ static void empty_binary_dn_message2entry(void **state)
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
        add_empty_msDS_KeyCredentialLink_DN(mem_ctx, msg);
+       add_whenCreated(msg, now);
 
        err = samba_kdc_message2entry(krb5_ctx,
                                      kdc_db_ctx,
@@ -627,6 +681,7 @@ static void msDS_KeyCredentialLink_message2entry(void **state)
 
        struct sdb_entry entry = {};
        krb5_error_code err = 0;
+       time_t now = time(NULL);
 
        /* Set up */
        smb_krb5_init_context_common(&krb5_ctx);
@@ -638,10 +693,10 @@ static void msDS_KeyCredentialLink_message2entry(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(BCRYPT_KEY_CREDENTIAL_LINK),
                                   BCRYPT_KEY_CREDENTIAL_LINK);
+       add_whenCreated(msg, now);
 
        err = samba_kdc_message2entry(krb5_ctx,
                                      kdc_db_ctx,
@@ -706,8 +761,7 @@ static void invalid_version_keycredlink(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(KEY_CREDENTIAL_LINK),
                                   KEY_CREDENTIAL_LINK);
 
@@ -755,8 +809,7 @@ static void duplicate_key_material_keycredlink(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(KEY_CREDENTIAL_LINK),
                                   KEY_CREDENTIAL_LINK);
 
@@ -800,8 +853,7 @@ static void duplicate_key_usage_keycredlink(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(KEY_CREDENTIAL_LINK),
                                   KEY_CREDENTIAL_LINK);
 
@@ -844,8 +896,7 @@ static void invalid_key_usage_keycredlink(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(KEY_CREDENTIAL_LINK),
                                   KEY_CREDENTIAL_LINK);
 
@@ -888,8 +939,7 @@ static void invalid_key_material_keycredlink(void **state)
 
        /* Create the ldb_message */
        msg = ldb_msg_new(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(KEY_CREDENTIAL_LINK),
                                   KEY_CREDENTIAL_LINK);
 
@@ -921,8 +971,7 @@ static void keycred_bcrypt_key_material(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(BCRYPT_KEY_CREDENTIAL_LINK),
                                   BCRYPT_KEY_CREDENTIAL_LINK);
 
@@ -966,8 +1015,7 @@ static void keycred_tpm_key_material(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(TPM_KEY_CREDENTIAL_LINK),
                                   TPM_KEY_CREDENTIAL_LINK);
 
@@ -1011,8 +1059,7 @@ static void keycred_der_key_material(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(DER_KEY_CREDENTIAL_LINK),
                                   DER_KEY_CREDENTIAL_LINK);
 
@@ -1056,16 +1103,13 @@ static void keycred_multiple(void **state)
 
        /* Create the ldb_message */
        msg = create_ldb_message(mem_ctx);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(DER_KEY_CREDENTIAL_LINK),
                                   DER_KEY_CREDENTIAL_LINK);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(TPM_KEY_CREDENTIAL_LINK),
                                   TPM_KEY_CREDENTIAL_LINK);
-       add_msDS_KeyCredentialLink(mem_ctx,
-                                  msg,
+       add_msDS_KeyCredentialLink(msg,
                                   sizeof(BCRYPT_KEY_CREDENTIAL_LINK),
                                   BCRYPT_KEY_CREDENTIAL_LINK);
 
@@ -1124,6 +1168,815 @@ static void keycred_multiple(void **state)
        TALLOC_FREE(mem_ctx);
 }
 
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an empty ldb string value
+ */
+static void empty_string_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(ENOENT, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an ldb string value containing
+ * just the X509: tag
+ */
+static void header_only_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(ENOENT, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an ldb string value containing
+ * a non X509 mapping
+ */
+static void not_x509_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "KERBEROS:");
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(ENOENT, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an ldb string value without
+ * a tag
+ */
+static void no_tag_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:No tag here");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(EINVAL, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an ldb string value without
+ * a tag close character '>'
+ */
+static void no_tag_close_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:<No tag close");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(EINVAL, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an ldb string value with
+ * an empty tag
+ */
+static void empty_tag_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:<>Empty tag");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(EINVAL, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an ldb string value with
+ * no value
+ */
+static void no_value_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:<I>");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(EINVAL, err);
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an issuer name
+ *
+ */
+static void issuer_name_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:<I>Issuer");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(6, mapping.issuer_name.length);
+       assert_memory_equal("Issuer", mapping.issuer_name.data, 6);
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles duplicate issuer names
+ *
+ */
+static void duplicate_issuer_name_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value
+               = get_ldb_string(mem_ctx, "X509:<I>Issuer<I>Duplicate");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+
+       /* Only use the last value in the event of duplicate values */
+       assert_int_equal(9, mapping.issuer_name.length);
+       assert_memory_equal("Duplicate", mapping.issuer_name.data, 9);
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles a subject name
+ *
+ */
+static void subject_name_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(mem_ctx, "X509:<S>Subject");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(7, mapping.subject_name.length);
+       assert_memory_equal("Subject", mapping.subject_name.data, 7);
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles duplicate subject names
+ *
+ */
+static void duplicate_subject_name_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value =
+               get_ldb_string(mem_ctx, "X509:<S>Subject<S>A repeat");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       /* Only use the last value in the event of duplicate values */
+       assert_int_equal(8, mapping.subject_name.length);
+       assert_memory_equal("A repeat", mapping.subject_name.data, 8);
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an issuer name and subject
+ * name.
+ *
+ */
+static void issuer_and_subject_name_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<S>SubjectsName<I>TheNameOfTheIssuer");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(12, mapping.subject_name.length);
+       assert_memory_equal("SubjectsName", mapping.subject_name.data, 12);
+       assert_int_equal(18, mapping.issuer_name.length);
+       assert_memory_equal("TheNameOfTheIssuer", mapping.issuer_name.data, 18);
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles a serial number
+ *
+ */
+static void serial_number_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t sn[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value
+               = get_ldb_string(mem_ctx, "X509:<SR>0123456789abcdef");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(sn), mapping.serial_number.length);
+       assert_memory_equal(sn, mapping.serial_number.data, sizeof(sn));
+       /* The Serial number on it's own is not a strong mapping */
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles multiple serial numbers
+ *
+ */
+static void duplicate_serial_number_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t sn[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value
+               = get_ldb_string(
+                       mem_ctx,
+                       "X509:<SR>fedcba98765410<SR>0123456789abcdef");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(sn), mapping.serial_number.length);
+       assert_memory_equal(sn, mapping.serial_number.data, sizeof(sn));
+       /* The Serial number on it's own is not a strong mapping */
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles a serial number and
+ * issuer name
+ *
+ */
+static void serial_number_and_issuer_name_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t sn[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<SR>0123456789abcdef<I>TheIssuer");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(sn), mapping.serial_number.length);
+       assert_memory_equal(sn, mapping.serial_number.data, sizeof(sn));
+       assert_int_equal(9, mapping.issuer_name.length);
+       assert_memory_equal("TheIssuer", mapping.issuer_name.data, 9);
+       assert_true(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an SKI (Subject Key Identifier)
+ */
+static void ski_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t ski[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<SKI>0123456789abcdef");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(ski), mapping.ski.length);
+       assert_memory_equal(ski, mapping.ski.data, sizeof(ski));
+       assert_true(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles multiple
+ * SKI (Subject Key Identifier) values
+ */
+static void duplicate_ski_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t ski[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<SKI>010203040506<SKI>0123456789abcdef");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(ski), mapping.ski.length);
+       assert_memory_equal(ski, mapping.ski.data, sizeof(ski));
+       assert_true(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles a public key
+ */
+static void public_key_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t pubkey[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<SHA1-PUKEY>0123456789abcdef");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(pubkey), mapping.public_key.length);
+       assert_memory_equal(pubkey, mapping.public_key.data, sizeof(pubkey));
+       assert_true(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles multiple public keys
+ */
+static void duplicate_public_key_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       uint8_t pubkey[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx,
+               "X509:<SHA1-PUKEY>adcdefabcdefabcdef"
+               "<SHA1-PUKEY>0123456789abcdef");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(sizeof(pubkey), mapping.public_key.length);
+       assert_memory_equal(pubkey, mapping.public_key.data, sizeof(pubkey));
+       assert_true(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that non hex strings are rejected
+ */
+static void non_hex_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<SHA1-PUKEY>This is not a hex string");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(EINVAL, err);
+       assert_int_equal(0, mapping.public_key.length);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that odd length hex strings are rejected
+ */
+static void odd_length_hex_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<SHA1-PUKEY>abcde");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(EINVAL, err);
+       assert_int_equal(0, mapping.public_key.length);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Ensure that parse_certificate_mapping handles an RFC822 identifier
+ */
+static void RFC822_parse_certificate_mapping(void **state)
+{
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       krb5_error_code err = 0;
+       struct sdb_certificate_mapping mapping = {};
+       struct ldb_val *value = get_ldb_string(
+               mem_ctx, "X509:<RFC822>test@example.com");
+
+       err = parse_certificate_mapping(value, &mapping);
+
+       assert_int_equal(0, err);
+       assert_int_equal(16, mapping.rfc822.length);
+       assert_memory_equal("test@example.com", mapping.rfc822.data, 16);
+       assert_false(mapping.strong_mapping);
+
+       sdb_certificate_mapping_free(&mapping);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Test get_certificate_mapping handles multiple entries
+ *
+ */
+static void multiple_cert_mappings(void **state)
+{
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       struct loadparm_context *lp_ctx = loadparm_init(mem_ctx);
+       struct ldb_message *msg = NULL;
+       krb5_error_code err = 0;
+       struct sdb_entry entry = {};
+       uint8_t ski[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+
+       time_t now = time(NULL);
+       const int backdate = 26280000;  /* Fifty years */
+       const int expected_val_cert_start = now - (backdate * 60);
+
+       /* Create the ldb_message */
+       msg = create_ldb_message(mem_ctx);
+       add_altSecurityIdentities(msg,
+                                 "X509:<SKI>0123456789abcdef");
+       add_altSecurityIdentities(msg,
+                                 "X509:<RFC822>test@example.com");
+       add_whenCreated(msg, now);
+
+       certificate_binding_enforcement = 1;
+       certificate_backdating_compensation = backdate;
+       err = get_certificate_mappings(mem_ctx, lp_ctx, msg, &entry);
+
+       assert_int_equal(0, err);
+
+       assert_int_equal(2, entry.mappings.len);
+       assert_int_equal(1, entry.mappings.enforcement_mode);
+       assert_int_equal(
+               expected_val_cert_start,
+               entry.mappings.valid_certificate_start);
+
+       assert_int_equal(sizeof(ski), entry.mappings.mappings[0].ski.length);
+       assert_memory_equal(
+               ski, entry.mappings.mappings[0].ski.data, sizeof(ski));
+       assert_true(entry.mappings.mappings[0].strong_mapping);
+
+       assert_int_equal(16, entry.mappings.mappings[1].rfc822.length);
+       assert_memory_equal(
+               "test@example.com", entry.mappings.mappings[1].rfc822.data, 16);
+       assert_false(entry.mappings.mappings[1].strong_mapping);
+
+       sdb_entry_free(&entry);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Test get_certificate_mapping handles a single entry
+ *
+ */
+static void single_cert_mapping(void **state)
+{
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       struct loadparm_context *lp_ctx = loadparm_init(mem_ctx);
+       struct ldb_message *msg = NULL;
+       krb5_error_code err = 0;
+       struct sdb_entry entry = {};
+       uint8_t ski[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+
+       time_t now = time(NULL);
+       const int backdate = 525600;  /* One year */
+       const int expected_val_cert_start = now - (backdate * 60);
+
+       /* Create the ldb_message */
+       msg = create_ldb_message(mem_ctx);
+       add_altSecurityIdentities(msg,
+                                 "X509:<SKI>0123456789abcdef");
+       add_whenCreated(msg, now);
+
+       certificate_binding_enforcement = 2;
+       certificate_backdating_compensation = backdate;
+       err = get_certificate_mappings(mem_ctx, lp_ctx, msg, &entry);
+
+       assert_int_equal(0, err);
+
+       assert_int_equal(1, entry.mappings.len);
+       assert_int_equal(2, entry.mappings.enforcement_mode);
+       assert_int_equal(
+               expected_val_cert_start,
+               entry.mappings.valid_certificate_start);
+
+       assert_int_equal(sizeof(ski), entry.mappings.mappings[0].ski.length);
+       assert_memory_equal(
+               ski, entry.mappings.mappings[0].ski.data, sizeof(ski));
+       assert_true(entry.mappings.mappings[0].strong_mapping);
+
+       sdb_entry_free(&entry);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Test get_certificate_mapping handles an ldb message with no
+ * altSecurityIdentities attribute
+ *
+ */
+static void cert_mapping_no_altSecurityIdentities(void **state)
+{
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       struct loadparm_context *lp_ctx = loadparm_init(mem_ctx);
+       struct ldb_message *msg = NULL;
+       krb5_error_code err = 0;
+       struct sdb_entry entry = {};
+
+       time_t now = time(NULL);
+       const int backdate = 10080;  /* 1 week */
+       const int expected_val_cert_start = now - (backdate * 60);
+
+       /* Create the ldb_message */
+       msg = create_ldb_message(mem_ctx);
+       add_whenCreated(msg, now);
+
+       certificate_binding_enforcement = 0;
+       certificate_backdating_compensation = backdate;
+       err = get_certificate_mappings(mem_ctx, lp_ctx, msg, &entry);
+
+       assert_int_equal(0, err);
+
+       assert_int_equal(0, entry.mappings.len);
+       assert_int_equal(0, entry.mappings.enforcement_mode);
+       assert_int_equal(
+               expected_val_cert_start,
+               entry.mappings.valid_certificate_start);
+
+       sdb_entry_free(&entry);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Test get_certificate_mapping handles an ldb message with an
+ * altSecurityIdentities attribute containing no X509 entries
+ *
+ */
+static void no_X509_altSecurityIdentities(void **state)
+{
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       struct loadparm_context *lp_ctx = loadparm_init(mem_ctx);
+       struct ldb_message *msg = NULL;
+       krb5_error_code err = 0;
+       struct sdb_entry entry = {};
+
+       time_t now = time(NULL);
+       const int backdate = 1440;  /* 24 hours */
+       const int expected_val_cert_start = now - (backdate * 60);
+
+       /* Create the ldb_message */
+       msg = create_ldb_message(mem_ctx);
+       add_altSecurityIdentities(msg,
+                                 "KERBEROS:0123456789abcdef");
+       add_altSecurityIdentities(msg,
+                                 "ANOTHER:0123456789abcdef");
+       add_whenCreated(msg, now);
+
+       certificate_binding_enforcement = 0;
+       certificate_backdating_compensation = backdate;
+       err = get_certificate_mappings(mem_ctx, lp_ctx, msg, &entry);
+
+       assert_int_equal(0, err);
+
+       assert_int_equal(0, entry.mappings.len);
+       assert_int_equal(0, entry.mappings.enforcement_mode);
+       assert_int_equal(
+               expected_val_cert_start,
+               entry.mappings.valid_certificate_start);
+
+
+       sdb_entry_free(&entry);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Test get_certificate_mapping handles an ldb message with an
+ * altSecurityIdentities attribute containing X509,and KERBEROS
+ * entries.
+ *
+ */
+static void mixed_altSecurityIdentities(void **state)
+{
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       struct loadparm_context *lp_ctx = loadparm_init(mem_ctx);
+       struct ldb_message *msg = NULL;
+       krb5_error_code err = 0;
+       struct sdb_entry entry = {};
+       uint8_t ski[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef};
+
+       time_t now = time(NULL);
+       const int backdate = 10;
+       const int expected_val_cert_start = now - (backdate * 60);
+
+       /* Create the ldb_message */
+       msg = create_ldb_message(mem_ctx);
+       add_altSecurityIdentities(msg,
+                                 "X509:<SKI>0123456789abcdef");
+       add_altSecurityIdentities(msg,
+                                 "KERBEROS:0123456789abcdef");
+       add_altSecurityIdentities(msg,
+                                 "X509:<RFC822>test@example.com");
+       add_whenCreated(msg, now);
+
+       certificate_binding_enforcement = 0;
+       certificate_backdating_compensation = backdate;
+       err = get_certificate_mappings(mem_ctx, lp_ctx, msg, &entry);
+
+       assert_int_equal(0, err);
+
+       assert_int_equal(2, entry.mappings.len);
+       assert_int_equal(0, entry.mappings.enforcement_mode);
+       assert_int_equal(
+               expected_val_cert_start,
+               entry.mappings.valid_certificate_start);
+
+       assert_int_equal(sizeof(ski), entry.mappings.mappings[0].ski.length);
+       assert_memory_equal(
+               ski, entry.mappings.mappings[0].ski.data, sizeof(ski));
+       assert_true(entry.mappings.mappings[0].strong_mapping);
+
+       assert_int_equal(16, entry.mappings.mappings[1].rfc822.length);
+       assert_memory_equal(
+               "test@example.com", entry.mappings.mappings[1].rfc822.data, 16);
+       assert_false(entry.mappings.mappings[1].strong_mapping);
+
+       sdb_entry_free(&entry);
+       TALLOC_FREE(mem_ctx);
+}
+
+
+/*
+ * Test get_certificate_mapping handles an empty
+ * altSecurityIdentities attribute
+ *
+ */
+static void cert_mapping_empty_altSecurityIdentities(void **state)
+{
+
+       TALLOC_CTX *mem_ctx = talloc_new(NULL);
+
+       struct loadparm_context *lp_ctx = loadparm_init(mem_ctx);
+       struct ldb_message *msg = NULL;
+       krb5_error_code err = 0;
+       struct sdb_entry entry = {};
+
+       time_t now = time(NULL);
+       const int backdate = 43800;  /* One month */
+       const int expected_val_cert_start = now - (backdate * 60);
+
+       /* Create the ldb_message */
+       msg = create_ldb_message(mem_ctx);
+       add_empty_altSecurities(mem_ctx, msg);
+       add_whenCreated(msg, now);
+
+       certificate_binding_enforcement = 0;
+       certificate_backdating_compensation = backdate;
+       err = get_certificate_mappings(mem_ctx, lp_ctx, msg, &entry);
+
+       assert_int_equal(0, err);
+
+       assert_int_equal(0, entry.mappings.len);
+       assert_int_equal(0, entry.mappings.enforcement_mode);
+       assert_int_equal(
+               expected_val_cert_start,
+               entry.mappings.valid_certificate_start);
+
+       sdb_entry_free(&entry);
+       TALLOC_FREE(mem_ctx);
+}
+
+
 int main(int argc, const char **argv)
 {
        const struct CMUnitTest tests[] = {
@@ -1140,6 +1993,42 @@ int main(int argc, const char **argv)
                cmocka_unit_test(keycred_tpm_key_material),
                cmocka_unit_test(keycred_der_key_material),
                cmocka_unit_test(keycred_multiple),
+               cmocka_unit_test(empty_string_parse_certificate_mapping),
+               cmocka_unit_test(header_only_parse_certificate_mapping),
+               cmocka_unit_test(not_x509_parse_certificate_mapping),
+               cmocka_unit_test(no_tag_parse_certificate_mapping),
+               cmocka_unit_test(no_tag_close_parse_certificate_mapping),
+               cmocka_unit_test(empty_tag_parse_certificate_mapping),
+               cmocka_unit_test(no_value_parse_certificate_mapping),
+               cmocka_unit_test(issuer_name_parse_certificate_mapping),
+               cmocka_unit_test(
+                       duplicate_issuer_name_parse_certificate_mapping),
+               cmocka_unit_test(subject_name_parse_certificate_mapping),
+               cmocka_unit_test(
+                       duplicate_subject_name_parse_certificate_mapping),
+               cmocka_unit_test(
+                       issuer_and_subject_name_parse_certificate_mapping),
+               cmocka_unit_test(serial_number_parse_certificate_mapping),
+               cmocka_unit_test(
+                       duplicate_serial_number_parse_certificate_mapping),
+               cmocka_unit_test(
+                       serial_number_and_issuer_name_parse_certificate_mapping
+               ),
+               cmocka_unit_test(ski_parse_certificate_mapping),
+               cmocka_unit_test(duplicate_ski_parse_certificate_mapping),
+               cmocka_unit_test(public_key_parse_certificate_mapping),
+               cmocka_unit_test(
+                       duplicate_public_key_parse_certificate_mapping),
+               cmocka_unit_test(non_hex_parse_certificate_mapping),
+               cmocka_unit_test(odd_length_hex_parse_certificate_mapping),
+               cmocka_unit_test(RFC822_parse_certificate_mapping),
+               cmocka_unit_test(multiple_cert_mappings),
+               cmocka_unit_test(single_cert_mapping),
+               cmocka_unit_test(cert_mapping_no_altSecurityIdentities),
+               cmocka_unit_test(cert_mapping_empty_altSecurityIdentities),
+               cmocka_unit_test(no_X509_altSecurityIdentities),
+               cmocka_unit_test(mixed_altSecurityIdentities),
+
        };
 
        cmocka_set_message_output(CM_OUTPUT_SUBUNIT);