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 <[email protected]>
Date: Fri Aug 1 16:25:13 2025 +1200
pytest:samba-tool group: remove unused imports
Signed-off-by: Douglas Bagnall <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
Autobuild-User(master): Douglas Bagnall <[email protected]>
Autobuild-Date(master): Wed Aug 20 05:35:03 UTC 2025 on atb-devel-224
commit 624a8c2261cfcb84e0080b19c2a6bb48f8c40750
Author: Douglas Bagnall <[email protected]>
Date: Sun Aug 17 09:59:07 2025 +0000
pytest: run user_keytrust tests as computer keytrust tests
Signed-off-by: Douglas Bagnall <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 5030dd33176cf1770637814120f69699f87ab03c
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 8ed39fa33f9dd917aae291ce6aad222d95654ec1
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 16d670f0a52ae8d78188af0c741b79175d7169ff
Author: Douglas Bagnall <[email protected]>
Date: Sun Aug 17 20:39:38 2025 +1200
samba-tool computer: remove unused imports
Signed-off-by: Douglas Bagnall <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 2681fe5df87db07b080e12fa7aeaaea0a0518546
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 625cabf65140ee2c79b0a89c483edd071d58a4f4
Author: Douglas Bagnall <[email protected]>
Date: Wed Aug 13 16:21:14 2025 +1200
samba-tool: Command.message() can print multiple lines
Signed-off-by: Douglas Bagnall <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 3ca754d8f25c0753d02859a97d7f2664a8b46462
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit df0cf2556f39ff5062e052ae020fb031c76fd222
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 87caac906e733e15ba3268db99f101bd1a93d9a1
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 7c08990a45540a83695e7759af0c72b77a90c2d5
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 93391259df86b156b429c95f0d8748dfb0862d44
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 3682667439a2c81b8bd08678387d9ee43ec54e18
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 439146c7a0ff362e2247feb94f2228edccea36be
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 2797c013e34953739e761ed56ea30b1f3f6c817d
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 0ff4d9e881cd0698247de99082981e8d0202157d
Author: Douglas Bagnall <[email protected]>
Date: Mon Aug 18 21:02:57 2025 +1200
man samba-tool: computer keytrust
Signed-off-by: Douglas Bagnall <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit 9322a71a4fdc781a79292158f770cadfedc60abb
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
commit ab9988b80d79743babcaf7afe3b9d4283005f312
Author: Douglas Bagnall <[email protected]>
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 <[email protected]>
Reviewed-by: Gary Lockyer <[email protected]>
-----------------------------------------------------------------------
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