From f52ea1082bf245f6bfd424b6ba76c74881df97b5 Mon Sep 17 00:00:00 2001 From: Gary Lockyer Date: Mon, 11 Aug 2025 12:00:03 +1200 Subject: [PATCH] s4:kdc:sdb_to_hdb key trust support Convert key trust public keys contained in the clients sdb records, and add to the HDB_Ext_KeyTrust extension on the clients HDB record Signed-off-by: Gary Lockyer Reviewed-by: Jennifer Sutton Autobuild-User(master): Douglas Bagnall Autobuild-Date(master): Tue Sep 16 23:23:42 UTC 2025 on atb-devel-224 --- selftest/knownfail | 1 - selftest/tests.py | 2 + source4/kdc/sdb_to_hdb.c | 127 ++++++++++++++++++++ source4/kdc/tests/sdb_to_hdb_test.c | 176 ++++++++++++++++++++++++++++ source4/kdc/wscript_build | 11 ++ 5 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 source4/kdc/tests/sdb_to_hdb_test.c diff --git a/selftest/knownfail b/selftest/knownfail index 63637c774a2..ab2d79d7114 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -338,4 +338,3 @@ # We currently don't send referrals for LDAP modify of non-replicated attrs ^samba4.ldap.rodc.python\(rodc\).__main__.RodcTests.test_modify_nonreplicated.* -^samba.tests.krb5.key_trust_tests.* diff --git a/selftest/tests.py b/selftest/tests.py index c00acb434e3..f466dbb9fee 100644 --- a/selftest/tests.py +++ b/selftest/tests.py @@ -636,6 +636,8 @@ plantestsuite("samba.unittests.cmdline", "none", if have_heimdal_support: plantestsuite("samba.source4.kdc.tests.db_glue", "none", [os.path.join(bindir(), "default/source4/kdc/test_db_glue")]) + plantestsuite("samba.source4.kdc.tests.sdb_to_hdb", "none", + [os.path.join(bindir(), "default/source4/kdc/test_sdb_to_hdb")]) # Run the Rust cargo tests planpythontestsuite("none", "samba.tests.rust") diff --git a/source4/kdc/sdb_to_hdb.c b/source4/kdc/sdb_to_hdb.c index 4c4f2a57626..ef02ce5be2a 100644 --- a/source4/kdc/sdb_to_hdb.c +++ b/source4/kdc/sdb_to_hdb.c @@ -22,7 +22,11 @@ */ #include "includes.h" +#include "hdb_asn1.h" #include +#include +#include +#include "rfc2459_asn1.h" #include "sdb.h" #include "sdb_hdb.h" #include "lib/krb5_wrap/krb5_samba.h" @@ -193,11 +197,118 @@ static int sdb_event_to_Event(krb5_context context, return 0; } + +/** +* @brief Convert a sdb_pub_key to a HDB_Ext_KeyTrust_val +* +* @param s[in] A public key in sdb +* @param h[in,out] The HDb_Ext_KeyTrust_val to populate +* +* @return 0 no error +* >0 an error occurred +*/ +static int sdb_pub_key_to_hdb_key_trust_val(const struct sdb_pub_key *s, + struct HDB_Ext_KeyTrust_val *h) +{ + krb5_error_code ret; + SubjectPublicKeyInfo spki = {}; + RSAPublicKey rsa = {}; + krb5_data buf = {}; + AlgorithmIdentifier alg = {}; + HEIM_ANY parameters = {}; + uint8_t none[] = {0x05, 0x00}; + size_t size = 0; + + rsa.publicExponent.length = s->exponent.length; + rsa.publicExponent.data = s->exponent.data; + rsa.publicExponent.negative = 0; + + rsa.modulus.length = s->modulus.length; + rsa.modulus.data = s->modulus.data; + rsa.modulus.negative = 0; + + ASN1_MALLOC_ENCODE( + RSAPublicKey, buf.data , buf.length, &rsa, &size, ret); + if (ret != 0) { + goto out; + } + + spki.subjectPublicKey.data = buf.data; + /* + * The public key length is in bits, but the buffer len is in bytes + * so need to convert it to bits. + */ + spki.subjectPublicKey.length = buf.length * 8; + + ret = der_copy_oid(ASN1_OID_ID_PKCS1_RSAENCRYPTION, &alg.algorithm); + if (ret != 0) { + goto out1; + } + parameters.data = &none; + parameters.length = sizeof(none); + alg.parameters = ¶meters; + spki.algorithm = alg; + + /* + * This will be freed when sdb_pub_keys_to_hdb calls + * free_HDB_Ext_KeyTrust + */ + ASN1_MALLOC_ENCODE(SubjectPublicKeyInfo, + h->pub_key.data, + h->pub_key.length, + &spki, + &size, + ret); + if (ret != 0) { + goto out2; + } + +out2: + der_free_oid(&alg.algorithm); +out1: + der_free_octet_string(&buf); +out: + return ret; +} + +/** +* @brief Convert any public keys for key trust authentication. +* +* @param s[in] The public keys that can be used for authentication +* @param h[out] The converted public keys +* @return 0 if there are no errors +* >0 an error occurred +*/ +static int sdb_pub_keys_to_hdb_ext(const struct sdb_pub_keys *s, + HDB_Ext_KeyTrust *h) +{ + int ret, i; + *h = (struct HDB_Ext_KeyTrust) {}; + + if (s->keys != NULL) { + h->val = malloc(s->len * sizeof(heim_octet_string)); + if (h->val == NULL) { + return ENOMEM; + } + for (i = 0; i < s->len; i++) { + ret = sdb_pub_key_to_hdb_key_trust_val( + &s->keys[i], &h->val[i]); + if (ret != 0) { + free_HDB_Ext_KeyTrust(h); + return ret; + } + h->len++; + } + } + return 0; +} + int sdb_entry_to_hdb_entry(krb5_context context, const struct sdb_entry *s, hdb_entry *h) { struct samba_kdc_entry *ske = s->skdc_entry; + struct HDB_Ext_KeyTrust kt = {}; unsigned int i; int rc; @@ -348,6 +459,22 @@ int sdb_entry_to_hdb_entry(krb5_context context, } } + rc = sdb_pub_keys_to_hdb_ext(&s->pub_keys, &kt); + if (rc != 0) { + goto error; + } + if (kt.len > 0) { + HDB_extension ext = {}; + ext.mandatory = FALSE; + ext.data.element = choice_HDB_extension_data_key_trust; + ext.data.u.key_trust = kt; + rc = hdb_replace_extension(context, h, &ext); + free_HDB_Ext_KeyTrust(&kt); + if (rc != 0) { + goto error; + } + } + h->context = ske; if (ske != NULL) { ske->kdc_entry = h; diff --git a/source4/kdc/tests/sdb_to_hdb_test.c b/source4/kdc/tests/sdb_to_hdb_test.c new file mode 100644 index 00000000000..fc6ff0ff7f1 --- /dev/null +++ b/source4/kdc/tests/sdb_to_hdb_test.c @@ -0,0 +1,176 @@ +/* + * Unit tests for source4/kdc/sdb_to_hdb.c + * + * Copyright (C) Gary Lockyer 2025 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include + * #include + * #include + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +#include +#include +#include + +#include "../../../third_party/cmocka/cmocka.h" + +#include "../sdb_to_hdb.c" +#include "hdb_asn1.h" + +/* + * Test that an empty sdb_pub_key is handled without error and that + * the expected value is generated + */ +static void empty_key(void **state) +{ + uint8_t empty_key[] = { + 0x30, 0x1a, /* Sequence 26 bytes, 2 elements */ + 0x30, 0x0d, /* Sequence 13 bytes, 2 elements */ + 0x06, 0x09, /* OID 9 bytes, 1.2.840.113549.1.1.1 */ + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, /* Null */ + 0x03, 0x09, 0x00, /* Bit string 9 bytes, zero unused bits */ + 0x30, 0x06, /* Sequence 6 bytes, 2 elements */ + 0x02, 0x01, 0x00, /* Integer 1 byte, value 0, Modulus */ + 0x02, 0x01, 0x00, /* Integer 1 byte, value 0, Exponent */ + }; + struct sdb_pub_key in = {}; + struct HDB_Ext_KeyTrust_val out = {}; + int ret = 0; + + ret = sdb_pub_key_to_hdb_key_trust_val(&in, &out); + + assert_int_equal(0, ret); + assert_int_equal(sizeof(empty_key), out.pub_key.length); + assert_memory_equal(empty_key, + out.pub_key.data, + sizeof(empty_key)); + + free(out.pub_key.data); +} + +/* + * Test that modulus and exponent with the leading bit set, + * are handled correctly + */ +static void test_leading_bit_handling(void **state) +{ + uint8_t expected_key[] = { + 0x30, 0x1c, /* Sequence 281 bytes, 2 elements */ + 0x30, 0x0d, /* Sequence 13 bytes, 2 elements */ + 0x06, 0x09, /* OID 9 bytes, 1.2.840.113549.1.1.1 */ + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, /* Null */ + 0x03, 0x0b, 0x00, /* Bit string 11 bytes, zero unused bits */ + 0x30, 0x08, /* Sequence 8 bytes, 2 elements */ + 0x02, 0x02, 0x00, 0x80, /* Integer 2 byte, value 0, Modulus */ + 0x02, 0x02, 0x00, 0x81, /* Integer 2 byte, value 0, Exponent */ + /* + * As the modulus and exponent have the leading bit set + * They should get encoded with a leading 0 byte + */ + }; + uint8_t modulus[] = {0x80}; + uint8_t exponent[] = {0x81}; + struct sdb_pub_key in = {}; + struct HDB_Ext_KeyTrust_val out = {}; + + int ret = 0; + in.bit_size = 8; + + in.modulus.data = &modulus; + in.modulus.length = sizeof(modulus); + + in.exponent.data = &exponent; + in.exponent.length = sizeof(exponent); + + + ret = sdb_pub_key_to_hdb_key_trust_val(&in, &out); + + assert_int_equal(0, ret); + assert_int_equal(sizeof(expected_key), out.pub_key.length); + assert_memory_equal(expected_key, + out.pub_key.data, + sizeof(expected_key)); + + free(out.pub_key.data); +} +/* + * Test that modulus and exponent with the leading bit not set, + * are handled correctly + */ +static void test_no_leading_bit(void **state) +{ + uint8_t expected_key[] = { + 0x30, 0x1c, /* Sequence 281 bytes, 2 elements */ + 0x30, 0x0d, /* Sequence 13 bytes, 2 elements */ + 0x06, 0x09, /* OID 9 bytes, 1.2.840.113549.1.1.1 */ + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, + 0x05, 0x00, /* Null */ + 0x03, 0x0b, 0x00, /* Bit string 11 bytes, zero unused bits */ + 0x30, 0x08, /* Sequence 8 bytes, 2 elements */ + 0x02, 0x02, 0x78, 0x9a, /* Integer 2 byte, value 0, Modulus */ + 0x02, 0x02, 0x65, 0x43, /* Integer 2 byte, value 0, Exponent */ + }; + uint8_t modulus[] = {0x78, 0x9a}; + uint8_t exponent[] = {0x65, 0x43}; + struct sdb_pub_key in = {}; + struct HDB_Ext_KeyTrust_val out = {}; + + int ret = 0; + in.bit_size = 8; + + in.modulus.data = &modulus; + in.modulus.length = sizeof(modulus); + + in.exponent.data = &exponent; + in.exponent.length = sizeof(exponent); + + + ret = sdb_pub_key_to_hdb_key_trust_val(&in, &out); + + assert_int_equal(0, ret); + assert_int_equal(sizeof(expected_key), out.pub_key.length); + assert_memory_equal(expected_key, + out.pub_key.data, + sizeof(expected_key)); + + free(out.pub_key.data); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(empty_key), + cmocka_unit_test(test_leading_bit_handling), + cmocka_unit_test(test_no_leading_bit), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/kdc/wscript_build b/source4/kdc/wscript_build index f3b3ae79dfa..3017070e6ee 100644 --- a/source4/kdc/wscript_build +++ b/source4/kdc/wscript_build @@ -37,6 +37,17 @@ if bld.CONFIG_SET('SAMBA4_USES_HEIMDAL'): for_selftest=True, ) + bld.SAMBA_BINARY('test_sdb_to_hdb', + source='tests/sdb_to_hdb_test.c', + deps=''' + sdb_hdb + HDB_SAMBA4 + cmocka + talloc + ''', + for_selftest=True, + ) + if bld.CONFIG_GET('SAMBA_USES_MITKDC'): bld.SAMBA_MODULE('service_kdc', source='kdc-service-mit.c', -- 2.47.3