[SCM] Samba Shared Repository - branch master updated

Andrew Bartlett abartlet at samba.org
Fri Mar 1 01:28:01 UTC 2024


The branch, master has been updated
       via  d6bfd26049b pytests: samba-tool domain kds root_key
       via  d0234391a8a samba-tool: add `samba-tool domain kds root_key delete`
       via  710093dc279 samba-tool: add `samba-tool domain kds root_key create`
       via  ee1e9f1fb22 samba-tool: add `samba-tool domain kds root_key view`
       via  a92699cda06 samba-tool: add `samba-tool domain kds root_key list`
       via  884d40ca165 samba-tool: don't error if there are no sub-commands
       via  79342a8411d provision: add a default root key
       via  53bf56c62b1 pytest:dsdb: check that there is a gkdi root key
       via  c6208a3b0ec pytest:gkdi: shift create_root_key into a function
       via  e1ab10b1fc1 pytest:samba-tool: add a flag to print more in runcmd
       via  ae0f38c319c samba-tool user delete: use account type constant
       via  e5efa217467 samba-tool domain: add LDB Result to json encoders
       via  bbd9249a9c2 ldb:pyldb exposes Result type
       via  17dbaf4d330 python:samdb: wrapper for _dsdb_create_gkdi_root_key()
       via  a7c955dc7f9 s4:pydsdb: python bindings for gkdi_new_root_key()
       via  214ac139d86 samba-tool domain kds root_key
       via  327f5dc4e58 samba-tool domain kds: add root key sub-command
       via  fbd9740272e samba-tool domain: add kds sub-branch
       via  d46daab2aed s4:dsdb: Add functions for GKDI root key creation
       via  e7a96915e82 lib:crypto: Check for overflow in GKDI rollover interval calculation
       via  2be2dca44a6 lib:crypto: Correct GKDI interval start time calculation
       via  924eb6bac50 lib:crypto: Add error checking to GKDI key start time calculation
       via  02f18a88dad selftest: Ignore msKds-DomainID in ldapcmp_restoredc.sh and samba.tests.domain_backup_offline
      from  667265b6851 ctdb-tests: Limit red-black tree test to 5s of random inserts

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


- Log -----------------------------------------------------------------
commit d6bfd26049b954ff976a528818e1019c4414f8e6
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Fri Feb 16 16:36:06 2024 +1300

    pytests: samba-tool domain kds root_key
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>
    
    Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
    Autobuild-Date(master): Fri Mar  1 01:27:30 UTC 2024 on atb-devel-224

commit d0234391a8a47f6f39f7965c03fbda8f61815251
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:55:54 2024 +1300

    samba-tool: add `samba-tool domain kds root_key delete`
    
    For deleting root keys.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 710093dc27922c0e28a8950120821df6f853b3ee
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:55:16 2024 +1300

    samba-tool: add `samba-tool domain kds root_key create`
    
    For making new root keys.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit ee1e9f1fb220fb3c2c3cf0c87b92900acb8e8909
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:54:24 2024 +1300

    samba-tool: add `samba-tool domain kds root_key view`
    
    This is for looking at one root key. There isn't much to know.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit a92699cda06bf278d91c1351685613ccaa91cd9d
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:34:25 2024 +1300

    samba-tool: add `samba-tool domain kds root_key list`
    
    This lists root keys, in descending chronological order according to the
    use_start_toime attribute. That's becuase you usually only care about
    the newest one.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 884d40ca16549d5a69119a2a2470ae4e45ee816a
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Feb 29 16:29:30 2024 +1300

    samba-tool: don't error if there are no sub-commands
    
    This is useful when you commit samba-tool tests before you commit the
    samba-tool code, and you want the tests to fail rather than error.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 79342a8411d6e1534e03ce43be0506007959c115
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 15:28:22 2024 +1300

    provision: add a default root key
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 53bf56c62b18da1bfd85099454ebc654ab738785
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 15:32:41 2024 +1300

    pytest:dsdb: check that there is a gkdi root key
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit c6208a3b0ec1d8a6c76755d66846d28deb274123
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Feb 22 16:17:37 2024 +1300

    pytest:gkdi: shift create_root_key into a function
    
    This is so the samba-tool domain kds root_key tests can use it as a
    function.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit e1ab10b1fc19ac35ea1dcaf0161d59d394fc363c
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Fri Feb 23 16:24:11 2024 +1300

    pytest:samba-tool: add a flag to print more in runcmd
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit ae0f38c319c95817b8f51ce3b427821c53d9cb86
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Fri Feb 16 15:35:06 2024 +1300

    samba-tool user delete: use account type constant
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit e5efa217467b5b9e582c62830a94712da7c0e840
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Feb 22 16:16:17 2024 +1300

    samba-tool domain: add LDB Result to json encoders
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit bbd9249a9c2eb574289ccc7a940646acb2035aaa
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Feb 15 04:07:34 2024 +0000

    ldb:pyldb exposes Result type
    
    You perhaps never want to manually create results (as in `x = Result()`)
    -- except maybe in tests -- and that would be why we never added it in
    the first place (or rather, we never noticed that it ws missing).
    
    But we do want to sometimes go `isinstance(x, ldb.Result)`, and that
    is how we noticed it was missing now.
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 17dbaf4d330dd4d1940c37598d798b228c74149b
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:15:44 2024 +1300

    python:samdb: wrapper for _dsdb_create_gkdi_root_key()
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit a7c955dc7f9c378a55443ff54c85bdcf79502ee1
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:15:09 2024 +1300

    s4:pydsdb: python bindings for gkdi_new_root_key()
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 214ac139d86a5ab334c22216ab316e3ac9635dad
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Feb 22 16:51:42 2024 +1300

    samba-tool domain kds root_key
    
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 327f5dc4e58a250049d993a25e076c2563311aea
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Wed Feb 28 17:29:40 2024 +1300

    samba-tool domain kds: add root key sub-command
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit fbd9740272eeabdc278e49c3af30862146a48168
Author: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
Date:   Thu Feb 22 16:51:56 2024 +1300

    samba-tool domain: add kds sub-branch
    
    Signed-off-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit d46daab2aed0d66c909285f3e0524ac1142d2ab2
Author: Jo Sutton <josutton at catalyst.net.nz>
Date:   Tue Feb 13 16:09:57 2024 +1300

    s4:dsdb: Add functions for GKDI root key creation
    
    Signed-off-by: Jo Sutton <josutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit e7a96915e8207f562000f60869e449ddf8bc915f
Author: Jo Sutton <josutton at catalyst.net.nz>
Date:   Mon Feb 19 10:34:02 2024 +1300

    lib:crypto: Check for overflow in GKDI rollover interval calculation
    
    Signed-off-by: Jo Sutton <josutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 2be2dca44a6d602d41ae623d96ed3d12b7b43d4f
Author: Jo Sutton <josutton at catalyst.net.nz>
Date:   Mon Feb 19 10:33:41 2024 +1300

    lib:crypto: Correct GKDI interval start time calculation
    
    Signed-off-by: Jo Sutton <josutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 924eb6bac50885a4d90cba227de569087185a06d
Author: Jo Sutton <josutton at catalyst.net.nz>
Date:   Tue Feb 13 13:04:48 2024 +1300

    lib:crypto: Add error checking to GKDI key start time calculation
    
    Signed-off-by: Jo Sutton <josutton at catalyst.net.nz>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit 02f18a88dad8d8cc6b15fc8f2ebde2560741e265
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Fri Mar 1 12:14:58 2024 +1300

    selftest: Ignore msKds-DomainID in ldapcmp_restoredc.sh and samba.tests.domain_backup_offline
    
    Like serverReferenceBL etc, this will point to a DC that created the object, and
    as part of the backup and restore, this DC will be deleted.  It is just for
    tracking the object creation, so this is fine.
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Douglas Bagnall <douglas.bagnall at catalyst.net.nz>

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

Summary of changes:
 lib/crypto/gkdi.c                                  |  57 +-
 lib/crypto/gkdi.h                                  |   2 +-
 lib/ldb/pyldb.c                                    |   1 +
 python/samba/netcmd/__init__.py                    |   2 +-
 python/samba/netcmd/domain/__init__.py             |   2 +
 .../dcerpc => netcmd/domain/kds}/__init__.py       |  20 +-
 python/samba/netcmd/domain/kds/root_key.py         | 440 +++++++++++++
 python/samba/netcmd/encoders.py                    |   6 +-
 python/samba/netcmd/user/delete.py                 |   5 +-
 python/samba/provision/__init__.py                 |   4 +
 python/samba/samdb.py                              |   6 +
 python/samba/tests/domain_backup_offline.py        |   2 +-
 python/samba/tests/dsdb_quiet_provision_tests.py   |  59 ++
 python/samba/tests/gkdi.py                         | 291 +++++----
 python/samba/tests/samba_tool/base.py              |  22 +-
 .../samba/tests/samba_tool/domain_kds_root_key.py  | 717 +++++++++++++++++++++
 source4/dsdb/gmsa/gkdi.c                           | 356 ++++++++++
 source4/dsdb/gmsa/gkdi.h                           |  46 ++
 source4/dsdb/pydsdb.c                              |  84 +++
 source4/dsdb/samdb/samdb.h                         |   6 +
 source4/dsdb/wscript_build                         |   4 +-
 source4/selftest/tests.py                          |   5 +
 testprogs/blackbox/ldapcmp_restoredc.sh            |   6 +
 23 files changed, 1991 insertions(+), 152 deletions(-)
 copy python/samba/{tests/dcerpc => netcmd/domain/kds}/__init__.py (66%)
 create mode 100644 python/samba/netcmd/domain/kds/root_key.py
 create mode 100644 python/samba/tests/dsdb_quiet_provision_tests.py
 create mode 100644 python/samba/tests/samba_tool/domain_kds_root_key.py
 create mode 100644 source4/dsdb/gmsa/gkdi.c
 create mode 100644 source4/dsdb/gmsa/gkdi.h


Changeset truncated at 500 lines:

diff --git a/lib/crypto/gkdi.c b/lib/crypto/gkdi.c
index 92348f286ac..af00ea4217e 100644
--- a/lib/crypto/gkdi.c
+++ b/lib/crypto/gkdi.c
@@ -175,11 +175,45 @@ struct Gkid gkdi_get_interval_id(const NTTIME time)
 		    time / gkdi_key_cycle_duration % gkdi_l2_key_iteration);
 }
 
-NTTIME gkdi_get_key_start_time(const struct Gkid gkid)
+bool gkdi_get_key_start_time(const struct Gkid gkid, NTTIME *start_time_out)
 {
-	return (gkid.l0_idx * gkdi_l1_key_iteration * gkdi_l2_key_iteration +
-		gkid.l1_idx * gkdi_l2_key_iteration + gkid.l2_idx) *
-	       gkdi_key_cycle_duration;
+	if (!gkid_is_valid(gkid)) {
+		return false;
+	}
+
+	{
+		enum GkidType key_type = gkid_key_type(gkid);
+		if (key_type != GKID_L2_SEED_KEY) {
+			return false;
+		}
+	}
+
+	{
+		/*
+		 * Make sure that the GKID is not so large its start time can’t
+		 * be represented in NTTIME.
+		 */
+		static const struct Gkid max_gkid = {
+			UINT64_MAX /
+				(gkdi_l1_key_iteration * gkdi_l2_key_iteration *
+				 gkdi_key_cycle_duration),
+			UINT64_MAX /
+				(gkdi_l2_key_iteration *
+				 gkdi_key_cycle_duration) %
+				gkdi_l1_key_iteration,
+			UINT64_MAX / gkdi_key_cycle_duration %
+				gkdi_l2_key_iteration};
+		if (!gkid_less_than_or_equal_to(gkid, max_gkid)) {
+			return false;
+		}
+	}
+
+	*start_time_out = ((uint64_t)gkid.l0_idx * gkdi_l1_key_iteration *
+				   gkdi_l2_key_iteration +
+			   (uint64_t)gkid.l1_idx * gkdi_l2_key_iteration +
+			   (uint64_t)gkid.l2_idx) *
+			  gkdi_key_cycle_duration;
+	return true;
 }
 
 /*
@@ -188,7 +222,7 @@ NTTIME gkdi_get_key_start_time(const struct Gkid gkid)
  */
 NTTIME gkdi_get_interval_start_time(const NTTIME time)
 {
-	return time % gkdi_key_cycle_duration;
+	return time / gkdi_key_cycle_duration * gkdi_key_cycle_duration;
 }
 
 bool gkid_less_than_or_equal_to(const struct Gkid g1, const struct Gkid g2)
@@ -207,7 +241,18 @@ bool gkid_less_than_or_equal_to(const struct Gkid g1, const struct Gkid g2)
 bool gkdi_rollover_interval(const int64_t managed_password_interval,
 			    NTTIME *result)
 {
-	if (managed_password_interval < 0) {
+	/*
+	 * This is actually a conservative reckoning. The interval could be one
+	 * higher than this maximum and not overflow. But there’s no reason to
+	 * support intervals that high (and Windows will start producing strange
+	 * results for intervals beyond that).
+	 */
+	const int64_t maximum_interval = UINT64_MAX / gkdi_key_cycle_duration *
+					 10 / 24;
+
+	if (managed_password_interval < 0 ||
+	    managed_password_interval > maximum_interval)
+	{
 		return false;
 	}
 
diff --git a/lib/crypto/gkdi.h b/lib/crypto/gkdi.h
index 0786d228a19..244563d18b6 100644
--- a/lib/crypto/gkdi.h
+++ b/lib/crypto/gkdi.h
@@ -133,7 +133,7 @@ static const int64_t gkdi_max_clock_skew = 3000000000;	     /* five minutes */
 
 struct Gkid gkdi_get_interval_id(const NTTIME time);
 
-NTTIME gkdi_get_key_start_time(const struct Gkid gkid);
+bool gkdi_get_key_start_time(const struct Gkid gkid, NTTIME *start_time_out);
 
 NTTIME gkdi_get_interval_start_time(const NTTIME time);
 
diff --git a/lib/ldb/pyldb.c b/lib/ldb/pyldb.c
index 23637a6b2a2..20b3c26f958 100644
--- a/lib/ldb/pyldb.c
+++ b/lib/ldb/pyldb.c
@@ -5019,6 +5019,7 @@ static PyObject* module_init(void)
 	PyModule_AddObject(m, "MessageElement", (PyObject *)&PyLdbMessageElement);
 	PyModule_AddObject(m, "Module", (PyObject *)&PyLdbModule);
 	PyModule_AddObject(m, "Tree", (PyObject *)&PyLdbTree);
+	PyModule_AddObject(m, "Result", (PyObject *)&PyLdbResult);
 	PyModule_AddObject(m, "Control", (PyObject *)&PyLdbControl);
 
 	PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION);
diff --git a/python/samba/netcmd/__init__.py b/python/samba/netcmd/__init__.py
index 01497055d68..e83c8390c55 100644
--- a/python/samba/netcmd/__init__.py
+++ b/python/samba/netcmd/__init__.py
@@ -428,7 +428,7 @@ class SuperCommand(Command):
         epilog = "\nAvailable subcommands:\n"
 
         subcmds = sorted(self.subcommands.keys())
-        max_length = max([len(c) for c in subcmds])
+        max_length = max([len(c) for c in subcmds], default=0)
         for cmd_name in subcmds:
             cmd = self.subcommands[cmd_name]
             if cmd.hidden:
diff --git a/python/samba/netcmd/domain/__init__.py b/python/samba/netcmd/domain/__init__.py
index 1c527f1baec..b9b31a5ae8e 100644
--- a/python/samba/netcmd/domain/__init__.py
+++ b/python/samba/netcmd/domain/__init__.py
@@ -36,6 +36,7 @@ from .demote import cmd_domain_demote
 from .functional_prep import cmd_domain_functional_prep
 from .info import cmd_domain_info
 from .join import cmd_domain_join
+from .kds import cmd_domain_kds
 from .keytab import cmd_domain_export_keytab
 from .leave import cmd_domain_leave
 from .level import cmd_domain_level
@@ -62,6 +63,7 @@ class cmd_domain(SuperCommand):
         subcommands["demote"] = cmd_domain_demote()
         subcommands["provision"] = cmd_domain_provision()
         subcommands["dcpromo"] = cmd_domain_dcpromo()
+        subcommands["kds"] = cmd_domain_kds()
         subcommands["level"] = cmd_domain_level()
         subcommands["passwordsettings"] = cmd_domain_passwordsettings()
         subcommands["classicupgrade"] = cmd_domain_classicupgrade()
diff --git a/python/samba/tests/dcerpc/__init__.py b/python/samba/netcmd/domain/kds/__init__.py
similarity index 66%
copy from python/samba/tests/dcerpc/__init__.py
copy to python/samba/netcmd/domain/kds/__init__.py
index b8df5a2c31f..3725725b2ba 100644
--- a/python/samba/tests/dcerpc/__init__.py
+++ b/python/samba/netcmd/domain/kds/__init__.py
@@ -1,7 +1,8 @@
-# -*- coding: utf-8 -*-
-#
 # Unix SMB/CIFS implementation.
-# Copyright © Jelmer Vernooij <jelmer at samba.org> 2008
+#
+# samba-tool commands for Key Distribution Services
+#
+# Copyright © Catalyst.Net Ltd. 2024
 #
 # 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
@@ -15,5 +16,16 @@
 #
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+from samba.netcmd import SuperCommand
+
+from .root_key import cmd_domain_kds_root_key
+
+
+class cmd_domain_kds(SuperCommand):
+    """Key Distribution Service management."""
 
-"""Tests for the DCE/RPC Python bindings."""
+    subcommands = {
+        "root-key": cmd_domain_kds_root_key(),
+    }
diff --git a/python/samba/netcmd/domain/kds/root_key.py b/python/samba/netcmd/domain/kds/root_key.py
new file mode 100644
index 00000000000..dcbdec27399
--- /dev/null
+++ b/python/samba/netcmd/domain/kds/root_key.py
@@ -0,0 +1,440 @@
+# Unix SMB/CIFS implementation.
+#
+# samba-tool commands for Key Distribution Services
+#
+# Copyright © Catalyst.Net Ltd. 2024
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+
+import samba.getopt as options
+from ldb import SCOPE_SUBTREE
+from samba.netcmd import Command, CommandError, Option, SuperCommand
+from samba.dcerpc import misc
+from ldb import MessageElement, LdbError
+from samba import string_is_guid
+
+
+from samba.nt_time import (string_from_nt_time,
+                           nt_time_from_string,
+                           nt_now,
+                           timedelta_from_nt_time_delta)
+
+
+def root_key_base_dn(ldb):
+    base_dn = ldb.get_config_basedn()
+    base_dn.add_child(
+        "CN=Master Root Keys,CN=Group Key Distribution Service,CN=Services")
+    return base_dn
+
+
+def get_root_key_by_name_or_dn(ldb, name, attrs=None):
+    if string_is_guid(str(name)):
+        key = 'name'
+    else:
+        key = 'dn'
+
+    if attrs is None:
+        attrs = ['*']
+
+    base_dn = root_key_base_dn(ldb)
+
+    expression = ("(&(objectClass = msKds-ProvRootKey)"
+                  f"({key} = {name}))")
+
+    res = ldb.search(base_dn,
+                     scope=SCOPE_SUBTREE,
+                     expression=expression,
+                     attrs=attrs)
+
+    if len(res) == 0:
+        raise CommandError(f"no such root key: {name}")
+    if len(res) != 1:
+        # the database is in a sorry state
+        raise CommandError(f"duplicate root keys matching {name}")
+
+    return res[0]
+
+
+def get_sorted_root_keys(ldb, attrs=None, n=None):
+    if attrs is None:
+        attrs = ['*']
+
+    base_dn = root_key_base_dn(ldb)
+
+    res = ldb.search(base_dn,
+                     scope=SCOPE_SUBTREE,
+                     expression="(objectClass = msKds-ProvRootKey)",
+                     attrs=attrs,
+                     controls=["server_sort:1:1:msKds-UseStartTime"])
+
+    return res
+
+
+def delta_string(d):
+    """Turn a datetime.timedelta into an approximate string."""
+    td = timedelta_from_nt_time_delta(d)
+    secs = td.total_seconds()
+    absolute = abs(secs)
+    if absolute < 2:
+        return 'about now'
+    s = 'about '
+    if absolute < 120:
+        s += f'{int(absolute)} seconds'
+    elif absolute < 7200:
+        s += f'{int(absolute / 60)} minutes'
+    elif absolute < 48 * 3600:
+        s += f'{int(absolute / 3600)} hours'
+    else:
+        s += f'{int(absolute / (24 * 3600))} days'
+
+    if secs <= 0:
+        s += ' ago'
+    else:
+        s += ' in the FUTURE'
+
+    return s
+
+
+# These next ridiculously simple looking functions are for the
+# ENCODERS mapping below.
+
+def guid_to_string(v):
+    return str(misc.GUID(v))
+
+
+def string_from_nt_time_string(nt_time):
+    nt_time = int(nt_time)
+    return string_from_nt_time(nt_time)
+
+
+# ENCODERS is a mapping of attribute names to encoding functions for
+# the corresponding values. Anything not mentioned will go through
+# str(), which for MessageElements is the same as bytes.decode().
+ENCODERS = {
+    "msKds-UseStartTime": string_from_nt_time_string,
+    "msKds-CreateTime": string_from_nt_time_string,
+    "msKds-RootKeyData": bytes.hex,
+    "msKds-SecretAgreementParam": bytes.hex,
+    "objectGUID": guid_to_string,
+    "msKds-KDFParam": bytes.hex,
+    "msKds-PublicKeyLength": int,
+    "msKds-PrivateKeyLength": int,
+    "msKds-Version": int,
+}
+
+
+def encode_by_key(k, v):
+    """Convert an attribute into a printable form, using the the attribute
+    name to guess the best format."""
+    fn = ENCODERS.get(k, lambda x: str(x))
+
+    if not isinstance(v, MessageElement):  # probably Dn
+        return fn(v)
+
+    if len(v) == 1:
+        return fn(v[0])
+
+    return [fn(x) for x in v]
+
+
+# these attributes we normally wany to show. 'name' is a GUID string
+# (and has the same value as cn, the rdn).
+BASE_ATTRS = ["name",
+              "msKds-UseStartTime",
+              "msKds-CreateTime",
+              ]
+
+# these attributes are secret, and also pretty opaque and useless to
+# look at (unless you want to steal the secret).
+SECRET_ATTRS = ["msKds-RootKeyData",
+                "msKds-SecretAgreementParam"]
+
+# these are things you might want to look at, but  generally don't.
+VERBOSE_ATTRS = ["whenCreated",
+                 "whenChanged",
+                 "objectGUID",
+                 "msKds-KDFAlgorithmID",
+                 "msKds-KDFParam",
+                 "msKds-SecretAgreementAlgorithmID",
+                 "msKds-PublicKeyLength",
+                 "msKds-PrivateKeyLength",
+                 "msKds-Version",
+                 "msKds-DomainID",
+                 "cn",
+                 ]
+
+
+class RootKeyCommand(Command):
+    """Base class with a common method for presenting root key data."""
+    def show_root_key_message(self, msg,
+                              output_format=None,
+                              show_secrets=False,
+                              preamble=None,
+                              now=None):
+        if output_format == 'json':
+            out = {}
+            if preamble is not None:
+                out['message'] = preamble
+            for k, v in msg.items():
+                if not show_secrets and k in SECRET_ATTRS:
+                    continue
+                out[k] = encode_by_key(k, v)
+            self.print_json(out)
+            return
+
+        if now is None:
+            now = nt_now()
+        create_time = int(msg['msKds-createTime'][0])
+        start_time = int(msg['msKds-UseStartTime'][0])
+        create_delta_string = delta_string(create_time - now)
+        start_delta_string = delta_string(start_time - now)
+
+        if preamble is not None:
+            self.message(preamble)
+
+        self.message(f"name {msg['name']}")
+        self.message(f"   created        {string_from_nt_time(create_time)} ({create_delta_string})")
+        self.message(f"   usable from    {string_from_nt_time(start_time)} ({start_delta_string})")
+
+        if show_secrets:
+            for k in SECRET_ATTRS:
+                v = msg[k][0].hex()
+                self.message(f"   {k:14} {v}")
+
+        remaining_keys = [k for k in msg if k not in BASE_ATTRS + SECRET_ATTRS]
+
+        for k in remaining_keys:
+            v = encode_by_key(k, msg[k])
+            self.message(f"   {k:14} {v}")
+
+        self.message('')
+
+
+class cmd_domain_kds_root_key_create(RootKeyCommand):
+    """Create a KDS root key object."""
+
+    synopsis = "%prog [-H <URL>] [options]"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "hostopts": options.HostOptions,
+    }
+
+    takes_options = [
+        Option("--json", help="Output results in JSON format.",
+               dest="output_format", action="store_const", const="json"),
+        Option("--use-start-time", help="Use of the key begins at this time."),
+        Option("-v", "--verbose", help="Be verbose", action="store_true"),
+    ]
+
+    def run(self, hostopts=None, sambaopts=None, credopts=None,
+            output_format=None, use_start_time=None, verbose=None):
+        kwargs = {}
+        if use_start_time is not None:
+            try:
+                nt_use = nt_time_from_string(use_start_time)
+                kwargs['use_start_time'] = nt_use
+            except ValueError as e:
+                raise CommandError(e) from None
+
+        ldb = self.ldb_connect(hostopts, sambaopts, credopts)
+        dn = ldb.new_gkdi_root_key(**kwargs)
+        guid = dn.get_rdn_value()
+
+        attrs = BASE_ATTRS[:]
+        if verbose:
+            attrs += VERBOSE_ATTRS
+
+        msg = get_root_key_by_name_or_dn(ldb, guid, attrs=attrs)
+        start_time = int(msg['msKds-UseStartTime'][0])
+        used_from_string = (f"usable from {string_from_nt_time(start_time)} "
+                            f"({delta_string(start_time - nt_now())})")
+
+        message = f"created root key {guid}, {used_from_string}"
+
+        if verbose:
+            self.show_root_key_message(msg,
+                                       output_format,
+                                       preamble=f"{message}\n")
+
+        elif output_format == 'json':
+            kwargs = {k: msg[k] for k in attrs}
+            self.print_json_status(message=message, dn=str(dn), **kwargs)
+        else:
+            self.message(message)
+
+
+class cmd_domain_kds_root_key_delete(RootKeyCommand):
+    """Delete a KDS root key."""
+
+    synopsis = "%prog [-H <URL>] [options]"
+
+    takes_optiongroups = {
+        "sambaopts": options.SambaOptions,
+        "credopts": options.CredentialsOptions,
+        "hostopts": options.HostOptions,
+    }
+
+    takes_options = [
+        Option("--name", help="The key to delete"),
+        Option("--json", help="Output results in JSON format.",
+               dest="output_format", action="store_const", const="json"),
+    ]
+
+    def run(self, hostopts=None, sambaopts=None, credopts=None, name=None, output_format=None):
+        ldb = self.ldb_connect(hostopts, sambaopts, credopts)
+        try:
+            root_key = get_root_key_by_name_or_dn(ldb, name)
+        except LdbError as e:
+            raise CommandError(e)
+
+        ldb.delete(root_key.dn)
+
+        guid = root_key.dn.get_rdn_value()
+        message = f"deleted root key {guid}"
+
+        if output_format == 'json':
+            self.print_json_status(message)
+        else:
+            self.message(message)
+
+
+class cmd_domain_kds_root_key_list(RootKeyCommand):
+    """List KDS root keys."""


-- 
Samba Shared Repository



More information about the samba-cvs mailing list