The branch, master has been updated via 246ce57e52e pytest:samba-tool group: remove unused imports via 624a8c2261c pytest: run user_keytrust tests as computer keytrust tests via 5030dd33176 pytest: adapt user_keytrust tests to be objectclass agnostic via 8ed39fa33f9 samba-tool: copy user_keytrust to computer keytrust via 16d670f0a52 samba-tool computer: remove unused imports via 2681fe5df87 samba-tool: add user keytrust command via 625cabf6514 samba-tool: Command.message() can print multiple lines via 3ca754d8f25 py:key_credential_link: filter_kcl_list helper for samba-tool via df0cf2556f3 py:key_credential_list: add kcl_in_list function via 87caac906e7 py:key_credential_links: allow encoding=='auto' via 7c08990a455 samba-tool: add verbose flag to @exception_to_command_error via 93391259df8 py:tests: test key_credential_link module via 3682667439a python:key_credential_link: add descriptive methods via 439146c7a0f python:models: do not re-use mutable defaults via 2797c013e34 samba-tool: add decorator to catch exception types via 0ff4d9e881c man samba-tool: computer keytrust via 9322a71a4fd man samba-tool: user keytrust via ab9988b80d7 man samba-tool: don't suggest non-existent option in synopsis. from affb734a256 tdbtorture: Fix CID 1034816: proper calloc usage
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit 246ce57e52e76b3e4b190a6b93309b3a8b938dde Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Aug 1 16:25:13 2025 +1200 pytest:samba-tool group: remove unused imports Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> Autobuild-User(master): Douglas Bagnall <dbagn...@samba.org> Autobuild-Date(master): Wed Aug 20 05:35:03 UTC 2025 on atb-devel-224 commit 624a8c2261cfcb84e0080b19c2a6bb48f8c40750 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sun Aug 17 09:59:07 2025 +0000 pytest: run user_keytrust tests as computer keytrust tests Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 5030dd33176cf1770637814120f69699f87ab03c Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sun Aug 17 09:58:39 2025 +0000 pytest: adapt user_keytrust tests to be objectclass agnostic We will reuse the tests for the computer keytrust command. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 8ed39fa33f9dd917aae291ce6aad222d95654ec1 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sun Aug 17 09:57:55 2025 +0000 samba-tool: copy user_keytrust to computer keytrust This is exactly a copy of user/keytrust.py to computer_keytrust.py with a title-case-preserving `s/user/computer/`. It works. The Computer model differs from the User model in that it appends a '$' to the end of account names if it senses the lack, otherwise these commands are using the same code paths. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 16d670f0a52ae8d78188af0c741b79175d7169ff Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sun Aug 17 20:39:38 2025 +1200 samba-tool computer: remove unused imports Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 2681fe5df87db07b080e12fa7aeaaea0a0518546 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Aug 6 14:01:14 2025 +1200 samba-tool: add user keytrust command This allows manipulation of key credential links for users. See `man -l bin/default/docs-xml/manpages/samba-tool.8` for documentation. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 625cabf65140ee2c79b0a89c483edd071d58a4f4 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Aug 13 16:21:14 2025 +1200 samba-tool: Command.message() can print multiple lines Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 3ca754d8f25c0753d02859a97d7f2664a8b46462 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sun Aug 17 08:34:57 2025 +0000 py:key_credential_link: filter_kcl_list helper for samba-tool This will be used in `samba-tool user keytrust delete` and `samba-tool computer keytrust delete` and is mainly to deduplicate that code. Potentially it could also be used in `keytrust view`. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit df0cf2556f39ff5062e052ae020fb031c76fd222 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Aug 15 17:36:11 2025 +1200 py:key_credential_list: add kcl_in_list function This compares the key material and DN of a KeyCredentialLinkDn with a list of others, which is a different sense of equality than the default (which considers GUIDs and binary equality). This will be used by samba-tool to check whether a link is in fact a duplicate even if it seems not to be due to some insignificant field being non-identical. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 87caac906e733e15ba3268db99f101bd1a93d9a1 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Aug 15 17:35:33 2025 +1200 py:key_credential_links: allow encoding=='auto' 'auto' is the same as None. This is helpful to samba-tool. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 7c08990a45540a83695e7759af0c72b77a90c2d5 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Thu Aug 14 10:33:00 2025 +1200 samba-tool: add verbose flag to @exception_to_command_error Helpful in development. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 93391259df86b156b429c95f0d8748dfb0862d44 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Fri Aug 8 15:04:51 2025 +1200 py:tests: test key_credential_link module These tests use the samba.key_credential_link module and a real samdb. The existing key_credential_link tests address the IDL generated structures more directly. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 3682667439a2c81b8bd08678387d9ee43ec54e18 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Jun 11 16:16:54 2025 +1200 python:key_credential_link: add descriptive methods In samba-tool we are going to want a KeyCredentialLinkDn to be able to describe itself. We're adding the methods here because `samba-tool user` and `samba-tool computer` will both want to use them. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 439146c7a0ff362e2247feb94f2228edccea36be Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Aug 13 17:19:16 2025 +1200 python:models: do not re-use mutable defaults This ensures that model.save works when a field has the many flag set, but the object has no attribute of that name, and the caller appends to the attribute list, like this: user.key_credential_link.append(link) When we get to save, and are doing this: value = getattr(self, attr) old_value = getattr(existing_obj, attr) if value != old_value: # commit the change the .append() will have added the item to both value and old_value because they are the same list. But not any more. This was a problem because the Field instance is attached to the model class, not the model instance. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 2797c013e34953739e761ed56ea30b1f3f6c817d Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Sat Aug 9 16:27:42 2025 +1200 samba-tool: add decorator to catch exception types Often we [think we] know that all exceptions of a certain type should be formatted as CommandErrors (i.e., the traceback is suppressed, and the message is assumed intelligible). Rather than riddling .run() with try...except blocks to do this, we can @exception_to_command_error(ModelError) def run(...) which makes any ModelError into a CommandError in that samba-tool command. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 0ff4d9e881cd0698247de99082981e8d0202157d Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Mon Aug 18 21:02:57 2025 +1200 man samba-tool: computer keytrust Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit 9322a71a4fdc781a79292158f770cadfedc60abb Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Wed Jun 11 14:20:51 2025 +1200 man samba-tool: user keytrust This documentation anticipates changes that will occur over the next ~20 commits. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> commit ab9988b80d79743babcaf7afe3b9d4283005f312 Author: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Date: Mon Aug 18 20:56:04 2025 +1200 man samba-tool: don't suggest non-existent option in synopsis. Signed-off-by: Douglas Bagnall <douglas.bagn...@catalyst.net.nz> Reviewed-by: Gary Lockyer <g...@catalyst.net.nz> ----------------------------------------------------------------------- Summary of changes: docs-xml/manpages/samba-tool.8.xml | 184 +++++++++- python/samba/domain/models/fields.py | 2 +- python/samba/key_credential_link.py | 171 ++++++++- python/samba/netcmd/__init__.py | 41 ++- python/samba/netcmd/computer.py | 8 +- python/samba/netcmd/computer_keytrust.py | 223 ++++++++++++ python/samba/netcmd/user/__init__.py | 2 + python/samba/netcmd/user/keytrust.py | 223 ++++++++++++ python/samba/tests/key_credential_link_samdb.py | 307 +++++++++++++++++ python/samba/tests/samba_tool/group.py | 5 - python/samba/tests/samba_tool/user_keytrust.py | 382 +++++++++++++++++++++ source4/selftest/tests.py | 2 + .../keytrust/ca-cert-ecdsa-p256.pem | 0 .../keytrust/ca-cert-rsa-2048.pem | 0 .../keytrust/ca-cert-rsa-4096.pem | 0 .../keytrust/cert-rsa-1024.pem | 0 .../keytrust/cert-rsa-2048.pem | 0 .../keytrust/cert-rsa-2048b.pem | 0 .../public-key-from-cert-rsa-2048-pkcs1.pem | 8 + testdata/keytrust/rsa2048-pkcs1.der | Bin 0 -> 270 bytes testdata/keytrust/rsa2048b-spki.pem | 9 + 21 files changed, 1550 insertions(+), 17 deletions(-) create mode 100644 python/samba/netcmd/computer_keytrust.py create mode 100644 python/samba/netcmd/user/keytrust.py create mode 100755 python/samba/tests/key_credential_link_samdb.py create mode 100644 python/samba/tests/samba_tool/user_keytrust.py copy third_party/heimdal/lib/hx509/data/secp256r1TestCA.cert.pem => testdata/keytrust/ca-cert-ecdsa-p256.pem (100%) copy third_party/heimdal/lib/hx509/data/j.pem => testdata/keytrust/ca-cert-rsa-2048.pem (100%) copy third_party/heimdal/lib/hx509/data/ca.crt => testdata/keytrust/ca-cert-rsa-4096.pem (100%) copy third_party/heimdal/lib/hx509/data/yutaka-pad-ok-cert.pem => testdata/keytrust/cert-rsa-1024.pem (100%) copy third_party/heimdal/lib/hx509/data/tcg-ek-cp.pem => testdata/keytrust/cert-rsa-2048.pem (100%) copy third_party/heimdal/lib/hx509/data/tcg-devid.pem => testdata/keytrust/cert-rsa-2048b.pem (100%) create mode 100644 testdata/keytrust/public-key-from-cert-rsa-2048-pkcs1.pem create mode 100644 testdata/keytrust/rsa2048-pkcs1.der create mode 100644 testdata/keytrust/rsa2048b-spki.pem Changeset truncated at 500 lines: diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml index d0bbc30c9e6..b27b168f471 100644 --- a/docs-xml/manpages/samba-tool.8.xml +++ b/docs-xml/manpages/samba-tool.8.xml @@ -24,7 +24,7 @@ <arg choice="opt">-W myworkgroup</arg> <arg choice="opt">-U user</arg> <arg choice="opt">-d debuglevel</arg> - <arg choice="opt">--v</arg> + <arg choice="opt">-V</arg> </cmdsynopsis> </refsynopsisdiv> @@ -295,6 +295,96 @@ </variablelist> </refsect3> +<refsect2> + <title>computer keytrust</title> + <para>Manage Key Credential Links for a computer.</para> + <para>This can populate, describe or delete msDS-KeyCredentialLink attributes.</para> +</refsect2> + + +<refsect3> +<title>computer keytrust add <replaceable>computername</replaceable> <replaceable>public-key-or-certificate</replaceable>[options]</title> +<para>Add a key-credential-link, which is a linked attribute that holds a public key in a binary field. +</para> +<para> + The second argument is a filename that should refer to a 2048 bit RSA key (or a certificate containing that key) in PEM or DER format. By default the encoding format will be detected automatically, but you can attempt to override this with <constant>--encoding</constant> option. Other types of public key are not supported, though the <constant>--force</constant> option can be used to add a non-2048 bit key. +</para> + +<variablelist> +<!--Options--> + <varlistentry> + <term>--link-target=DN</term> + <listitem><para>link to this DN (default: the computer's DN)</para></listitem> + </varlistentry> + <varlistentry> + <term>--encoding=ENCODING</term> + <listitem><para>Key format, either <constant>pem</constant>, <constant>der</constant>, or <constant>auto</constant>. The default is <constant>auto</constant>, which is likely to detect the correct format in all circumstances.</para></listitem> + </varlistentry> + <varlistentry> + <term>--force</term> + <listitem><para>proceed with operations that seems ill-fated</para></listitem> + </varlistentry> +</variablelist> +</refsect3> + +<refsect3> + +<title>computer keytrust delete <replaceable>computername</replaceable> [options]</title> +<para>Delete a key-credential-link. +</para> +<para>The link to be deleted can be selected in a number of ways. <constant>--all</constant> will delete all key credential links for the computer (often there will only be one). The <constant>--link-target</constant> option selects a key credential link based on the DN targeted by the link. The <constant>--fingerprint</constant> option selects a link to delete based on the key fingerprint. This is the SHA256 of the DER-encoded key material, expressed as hex-pairs separated by colons. See <constant>computer keytrust view</constant> to get a list of links and their fingerprints. +</para> + +<para>If more than one of <constant>--link-target</constant>, <constant>--fingerprint</constant>, and <constant>--all</constant> are used, links matched by any of them will be deleted. +</para> + +<para>The <constant>--dry-run</constant> option will prevent links from being deleted, and instead indicate what would happen if it was omitted. +</para> + +<variablelist> +<!--Options--> + <varlistentry> + <term>--link-target=DN</term> + <listitem><para>Delete this key credential link (a DN)</para></listitem> + </varlistentry> + <varlistentry> + <term>--fingerprint=HH:HH:..</term> + <listitem><para>Delete the key credential link with this key fingerprint</para></listitem> + </varlistentry> + <varlistentry> + <term>--all</term> + <listitem><para>Delete all key credential links</para></listitem> + </varlistentry> + <varlistentry> + <term>-n, --dry-run</term> + <listitem><para>Do nothing but print what would happen</para></listitem> + </varlistentry> +</variablelist> +</refsect3> + +<refsect3> + +<title>computer keytrust view <replaceable>computername</replaceable> [options]</title> + +<para>View a computer's key credential links. This can be used to find a link's fingerprint and target DN for <title>computer keytrust delete</title>. + +The <constant>--verbose</constant> includes more, probably useless, information. +</para> + +<variablelist> +<!--Options--> + <varlistentry> + <term>-h, --help</term> + <listitem><para>show this help message and exit</para></listitem> + </varlistentry> + <varlistentry> + <term>-v, --verbose</term> + <listitem><para>Be verbose</para></listitem> + </varlistentry> +</variablelist> +</refsect3> + + <refsect2> <title>contact</title> <para>Manage contacts.</para> @@ -621,7 +711,7 @@ return code will not indicate error. </para></listitem> </varlistentry> </variablelist> - + </refsect3> @@ -4303,6 +4393,96 @@ in use.</para> <para>View the assigned authentication silo for user.</para> </refsect3> +<refsect2> + <title>user keytrust</title> + <para>Manage Key Credential Links for a user.</para> + <para>This can populate, describe or delete msDS-KeyCredentialLink attributes.</para> +</refsect2> + + +<refsect3> +<title>user keytrust add <replaceable>username</replaceable> <replaceable>public-key-or-certificate</replaceable>[options]</title> +<para>Add a key-credential-link, which is a linked attribute that holds a public key in a binary field. +</para> +<para> + The second argument is a filename that should refer to a 2048 bit RSA key (or a certificate containing that key) in PEM or DER format. By default the encoding format will be detected automatically, but you can attempt to override this with <constant>--encoding</constant> option. Other types of public key are not supported, though the <constant>--force</constant> option can be used to add a non-2048 bit key. +</para> + +<variablelist> +<!--Options--> + <varlistentry> + <term>--link-target=DN</term> + <listitem><para>link to this DN (default: the user's DN)</para></listitem> + </varlistentry> + <varlistentry> + <term>--encoding=ENCODING</term> + <listitem><para>Key format, either <constant>pem</constant>, <constant>der</constant>, or <constant>auto</constant>. The default is <constant>auto</constant>, which is likely to detect the correct format in all circumstances.</para></listitem> + </varlistentry> + <varlistentry> + <term>--force</term> + <listitem><para>proceed with operations that seems ill-fated</para></listitem> + </varlistentry> +</variablelist> +</refsect3> + +<refsect3> + +<title>user keytrust delete <replaceable>username</replaceable> [options]</title> +<para>Delete a key-credential-link. +</para> +<para>The link to be deleted can be selected in a number of ways. <constant>--all</constant> will delete all key credential links for the user (often there will only be one). The <constant>--link-target</constant> option selects a key credential link based on the DN targeted by the link. The <constant>--fingerprint</constant> option selects a link to delete based on the key fingerprint. This is the SHA256 of the DER-encoded key material, expressed as hex-pairs separated by colons. See <constant>user keytrust view</constant> to get a list of links and their fingerprints. +</para> + +<para>If more than one of <constant>--link-target</constant>, <constant>--fingerprint</constant>, and <constant>--all</constant> are used, links matched by any of them will be deleted. +</para> + +<para>The <constant>--dry-run</constant> option will prevent links from being deleted, and instead indicate what would happen if it was omitted. +</para> + +<variablelist> +<!--Options--> + <varlistentry> + <term>--link-target=DN</term> + <listitem><para>Delete this key credential link (a DN)</para></listitem> + </varlistentry> + <varlistentry> + <term>--fingerprint=HH:HH:..</term> + <listitem><para>Delete the key credential link with this key fingerprint</para></listitem> + </varlistentry> + <varlistentry> + <term>--all</term> + <listitem><para>Delete all key credential links</para></listitem> + </varlistentry> + <varlistentry> + <term>-n, --dry-run</term> + <listitem><para>Do nothing but print what would happen</para></listitem> + </varlistentry> +</variablelist> + +</refsect3> +<refsect3> + +<title>user keytrust view <replaceable>username</replaceable> [options]</title> + +<para>View a user's key credential links. This can be used to find a link's fingerprint and target DN for <title>user keytrust delete</title>. + +The <constant>--verbose</constant> includes more, probably useless, information. +</para> + +<variablelist> +<!--Options--> + <varlistentry> + <term>-h, --help</term> + <listitem><para>show this help message and exit</para></listitem> + </varlistentry> + <varlistentry> + <term>-v, --verbose</term> + <listitem><para>Be verbose</para></listitem> + </varlistentry> +</variablelist> +</refsect3> + + <refsect2> <title>vampire [options] <replaceable>domain</replaceable></title> <para>Join and synchronise a remote AD domain to the local server. diff --git a/python/samba/domain/models/fields.py b/python/samba/domain/models/fields.py index cff11661e73..959f80faf5c 100644 --- a/python/samba/domain/models/fields.py +++ b/python/samba/domain/models/fields.py @@ -64,7 +64,7 @@ class Field(metaclass=ABCMeta): # This ensures that fields with many=True are always lists. # If this is inconsistent anywhere, it isn't so great to use. if self.many and default is None: - self.default = [] + self.default = lambda x: list() else: self.default = default diff --git a/python/samba/key_credential_link.py b/python/samba/key_credential_link.py index 2ff17da44da..f8c82e1a82b 100644 --- a/python/samba/key_credential_link.py +++ b/python/samba/key_credential_link.py @@ -18,9 +18,11 @@ """Functions for processing key_credential_link""" +import base64 from hashlib import sha256 import struct -from typing import Optional, Union +import time +from typing import Optional, Union, Iterable from cryptography.hazmat.primitives.serialization import ( load_der_public_key, @@ -35,10 +37,31 @@ from cryptography.x509 import ( load_der_x509_certificate) +from samba import nttime2unix from samba.samdb import SamDB, BinaryDn from samba.ndr import ndr_unpack, ndr_pack from ldb import Dn -from samba.dcerpc import keycredlink +from samba.dcerpc import keycredlink, misc + + +class KeyCredLinkError(Exception): + """The key credential link is inconsistent.""" + # For bad values handed in, we use ValueError. For internal bad + # values, we use this. + + +def key_usage_string(i): + # there must be a better way. + for s in ('KEY_USAGE_NGC', 'KEY_USAGE_FIDO', 'KEY_USAGE_FEK',): + if i == getattr(keycredlink, s): + return s + return "unknown" + + +def nttime_as_date(nt): + secs = nttime2unix(nt) + ts = time.gmtime(secs) + return time.strftime('%Y-%m-%d %H:%M:%S', ts) class KeyCredentialLinkDn(BinaryDn): @@ -72,8 +95,107 @@ class KeyCredentialLinkDn(BinaryDn): raise ValueError("Could not parse value as KEYCREDENTIALLINK_BLOB " f" (internal error: {e})") + def get_entry(self, entry_id): + if self.blob is None: + raise KeyCredLinkError("no key material") + + for entry in self.blob.entries: + if entry.identifier == entry_id: + return entry.value + + raise KeyCredLinkError(f"Key information entry {entry_id} not found") + + def fingerprint(self) -> str: + """The SHA256 of the key material in DER encoding, formatted + as hex pairs separated by colons ("hh:hh:...")""" + # A competing format is '2048 SHA256:<base64bytes>' (ssh style). + + # This sha256 value should also be stored in the KeyID field. + data = self.get_entry(keycredlink.KeyMaterial) + hash = sha256(data).digest() + # Python 3.8+ will do this with hash.hex(':') + return ':'.join(f'{_:02X}' for _ in hash) + + def description(self, verbosity=2) -> str: + """Text describing key credential link characteristics. + + verbosity is adjustable between 1 and 3. + """ + out = [] + + def write(msg, verbose_level=0): + if verbosity > verbose_level: + out.append(msg) + + write(f'Link target: {self.dn}', 1) + write(f'Binary Dn: {self}', 2) + write(f'Key Credential Link Blob version: {self.blob.version}', 2) + write(f'Number of key entries: {self.blob.count}', 1) + + write('Key entries:') + entries = [] + longest = 0 + for description, verbose_level, fn, attr in [ + ("key material fingerprint", 0, + lambda x: ':'.join(f"{_:02X}" for _ in x), + 'KeyID'), + ("key parameters fingerprint", 2, + lambda x: ':'.join(f"{_:02X}" for _ in x), + 'KeyHash'), + ("key usage", 1, key_usage_string, 'KeyUsage'), + ("Device GUID", 1, misc.GUID, 'DeviceId'), + ("last logon", 0, nttime_as_date, + 'KeyApproximateLastLogonTimeStamp'), + ("creation time", 0, nttime_as_date, 'KeyCreationTime'), + # for now we are ignoring KeySource and CustomKeyInformation + # KeyMaterial is decoded separately + ]: + + if verbosity > 1: + description = f"{description} ({attr})" + + i = getattr(keycredlink, attr) + + try: + entry = self.get_entry(i) + value = fn(entry) + except KeyCredLinkError: + value = "not found" + + if verbosity > verbose_level: + entries.append((description, value)) + longest = max(longest, len(description)) + + for desc, val in entries: + write(f" {desc + ':':{longest + 1}} {val}") + + data = self.get_entry(keycredlink.KeyMaterial) + key = get_public_key(data, 'der') -def get_public_key(data:bytes, encoding:str): + write("RSA public key properties:", 1) + write(f" key size: {key.key_size}", 1) + write(f" fingerprint: {self.fingerprint()}", 1) + + return '\n'.join(out) + + def key_material(self) -> bytes: + return self.get_entry(keycredlink.KeyMaterial) + + def as_pem(self) -> str: + """Get the key out of the keycredlink blob, and return it in + PEM format as a string. + + PEM is the ASCII format that starts '-----BEGIN PUBLIC KEY-----'. + """ + # The key is in DER format in an entry in the blob. + data = self.key_material() + key = get_public_key(data, 'der') + pem = key.public_bytes(Encoding.PEM, + PublicFormat.SubjectPublicKeyInfo) + return pem.decode() + + +def get_public_key(data:bytes, encoding:Optional[str] = None) -> RSAPublicKey: """decode a key in PEM or DER format. If it turns out to be a certificate or something, we try to get @@ -146,6 +268,9 @@ def create_key_credential_link(samdb: SamDB, if len(res) == 0: raise ValueError(f"link target {target} does not exist") + if encoding == 'auto': + encoding = None + key = get_public_key(data, encoding) if key.key_size != 2048: @@ -214,3 +339,43 @@ def create_key_credential_link(samdb: SamDB, k = KeyCredentialLinkDn.from_bytes_and_dn(samdb, kcl_bytes, target) return k + +def kcl_in_list(kcl: KeyCredentialLinkDn, others: Iterable[KeyCredentialLinkDn]): + """True if kcl is in the list, otherwise False, disregarding + everything except key material and DN for the comparison. + """ + # this helps us avoid duplicate key credential links, which are + # otherwise disallowed only if all fields are identical, but which + # are generally useless. + km = kcl.key_material() + for other in others: + if km == other.key_material() and kcl.dn == other.dn: + return True + return False + + +def filter_kcl_list(samdb: SamDB, + keycredlinks: Iterable[KeyCredentialLinkDn], + link_target: Optional[str] = None, + fingerprint: Optional[str] = None) -> list: + """Select only the input links that match at least one of the + criteria. + """ + # used in samba-tool X keytrust delete + selected = [] + filters = [] + if link_target is not None: + target_dn = Dn(samdb, link_target) + filters.append(lambda x: x.dn == target_dn) + + if fingerprint is not None: + fingerprint = fingerprint.upper() + filters.append(lambda x: x.fingerprint() == fingerprint) + + for x in keycredlinks: + for fn in filters: + if fn(x): + selected.append(x) + break + + return selected diff --git a/python/samba/netcmd/__init__.py b/python/samba/netcmd/__init__.py index 9aa4664418a..fc8bf96f19c 100644 --- a/python/samba/netcmd/__init__.py +++ b/python/samba/netcmd/__init__.py @@ -300,8 +300,9 @@ class Command(object): return parser, optiongroups - def message(self, text): - self.outf.write(text + "\n") + def message(self, *text): + for t in text: + print(t, file=self.outf) def _resolve(self, path, *argv, outf=None, errf=None): """This is a leaf node, the command that will actually run.""" @@ -500,3 +501,39 @@ class CommandError(Exception): def __repr__(self): return "CommandError(%s)" % self.message + + +def exception_to_command_error(*exceptions, verbose=False): + """If you think all instances of a particular exceptions can be + turning to a CommandError, do this: + + @exception_to_command_error(ValueError, LdbError): + def run(self, username, ...): + # continue as normal + + Add the verbose=True flag during development if it is doing your + head in. + """ + def wrap2(f): + def wrap(*args, **kwargs): + try: + return f(*args, **kwargs) + except exceptions as e: + if verbose: + print(colour.c_DARK_RED("↓" * 20 + "DEBUG" + "↓" * 20), + file=sys.stderr) + print(f"converting «e» raised in {f} " + "to CommandError\n", + file=sys.stderr) + traceback.print_exc() + print("\nThis message is here because " + "exception_to_command_error() has the verbose=True" + " debugging option set.", + file=sys.stderr) + print("Below this is what you would normally see:", + file=sys.stderr) + print(colour.c_DARK_RED("↑" * 20 + "DEBUG" + "↑" * 20), + file=sys.stderr) + raise CommandError(e) + return wrap + return wrap2 diff --git a/python/samba/netcmd/computer.py b/python/samba/netcmd/computer.py index 1413803cf8a..cd5389cf8ec 100644 -- Samba Shared Repository