LCOV - code coverage report
Current view: top level - lib/crypto - gmsa.c (source / functions) Hit Total Coverage
Test: coverage report for master 98b443d9 Lines: 61 85 71.8 %
Date: 2024-05-31 13:13:24 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /*
       2             :    Unix SMB/CIFS implementation.
       3             :    Group Managed Service Account functions
       4             : 
       5             :    Copyright (C) Catalyst.Net Ltd 2024
       6             : 
       7             :    This program is free software: you can redistribute it and/or modify
       8             :    it under the terms of the GNU General Public License as published by
       9             :    the Free Software Foundation, either version 3 of the License, or
      10             :    (at your option) any later version.
      11             : 
      12             :    This program is distributed in the hope that it will be useful,
      13             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             :    GNU General Public License for more details.
      16             : 
      17             :    You should have received a copy of the GNU General Public License
      18             :    along with this program.  If not, see <https://www.gnu.org/licenses/>.
      19             : */
      20             : 
      21             : #include "includes.h"
      22             : #include <gnutls/gnutls.h>
      23             : #include "lib/crypto/gnutls_helpers.h"
      24             : #include "lib/crypto/gkdi.h"
      25             : #include "lib/crypto/gmsa.h"
      26             : #include "librpc/gen_ndr/ndr_security.h"
      27             : 
      28             : static const uint8_t gmsa_security_descriptor[] = {
      29             :         /* O:SYD:(A;;FRFW;;;S-1-5-9) */
      30             :         0x01, 0x00, 0x04, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
      31             :         0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1c, 0x00,
      32             :         0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x9f, 0x01, 0x12, 0x00,
      33             :         0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x09, 0x00, 0x00, 0x00,
      34             :         0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x12, 0x00, 0x00, 0x00};
      35             : 
      36             : static const uint8_t gmsa_password_label[] = {
      37             :         /* GMSA PASSWORD as a NULL‐terminated UTF‐16LE string. */
      38             :         'G', 0, 'M', 0, 'S', 0, 'A', 0, ' ', 0, 'P', 0, 'A', 0,
      39             :         'S', 0, 'S', 0, 'W', 0, 'O', 0, 'R', 0, 'D', 0, 0,   0,
      40             : };
      41             : 
      42         191 : static NTSTATUS generate_gmsa_password(
      43             :         const uint8_t key[static const GKDI_KEY_LEN],
      44             :         const struct dom_sid *const account_sid,
      45             :         const struct KdfAlgorithm kdf_algorithm,
      46             :         uint8_t password[static const GMSA_PASSWORD_LEN])
      47             : {
      48         191 :         NTSTATUS status = NT_STATUS_OK;
      49           1 :         gnutls_mac_algorithm_t algorithm;
      50             : 
      51         191 :         algorithm = get_sp800_108_mac_algorithm(kdf_algorithm);
      52         191 :         if (algorithm == GNUTLS_MAC_UNKNOWN) {
      53           0 :                 status = NT_STATUS_NOT_SUPPORTED;
      54           0 :                 goto out;
      55             :         }
      56             : 
      57         191 :         if (account_sid == NULL) {
      58           0 :                 status = NT_STATUS_INVALID_PARAMETER;
      59           0 :                 goto out;
      60             :         }
      61             : 
      62         191 :         {
      63         191 :                 uint8_t encoded_sid[ndr_size_dom_sid(account_sid, 0)];
      64             :                 {
      65         191 :                         struct ndr_push ndr = {
      66             :                                 .data = encoded_sid,
      67             :                                 .alloc_size = sizeof encoded_sid,
      68             :                                 .fixed_buf_size = true,
      69             :                         };
      70           1 :                         enum ndr_err_code ndr_err;
      71             : 
      72         191 :                         ndr_err = ndr_push_dom_sid(&ndr,
      73             :                                                    NDR_SCALARS | NDR_BUFFERS,
      74             :                                                    account_sid);
      75         191 :                         if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
      76           0 :                                 status = ndr_map_error2ntstatus(ndr_err);
      77           0 :                                 goto out;
      78             :                         }
      79             :                 }
      80             : 
      81         191 :                 status = samba_gnutls_sp800_108_derive_key(
      82             :                         key,
      83             :                         GKDI_KEY_LEN,
      84             :                         NULL,
      85             :                         0,
      86             :                         gmsa_password_label,
      87             :                         sizeof gmsa_password_label,
      88             :                         encoded_sid,
      89             :                         sizeof encoded_sid,
      90             :                         algorithm,
      91             :                         password,
      92             :                         GMSA_PASSWORD_LEN);
      93         191 :                 if (!NT_STATUS_IS_OK(status)) {
      94           0 :                         goto out;
      95             :                 }
      96             :         }
      97             : 
      98         191 : out:
      99         191 :         return status;
     100             : }
     101             : 
     102         191 : static void gmsa_post_process_password_buffer(
     103             :         uint8_t password[static const GMSA_PASSWORD_NULL_TERMINATED_LEN])
     104             : {
     105           1 :         size_t n;
     106             : 
     107       24639 :         for (n = 0; n < GMSA_PASSWORD_LEN; n += 2) {
     108       24448 :                 const uint8_t a = password[n];
     109       24448 :                 const uint8_t b = password[n + 1];
     110       24448 :                 if (!a && !b) {
     111             :                         /*
     112             :                          * There is a 0.2% chance that the generated password
     113             :                          * will contain an embedded null terminator, which will
     114             :                          * need to be converted into U+0001.
     115             :                          */
     116           1 :                         password[n] = 1;
     117             :                 }
     118             :         }
     119             : 
     120             :         /* Null‐terminate the password. */
     121         191 :         password[GMSA_PASSWORD_LEN] = 0;
     122         191 :         password[GMSA_PASSWORD_LEN + 1] = 0;
     123         191 : }
     124             : 
     125         191 : NTSTATUS gmsa_password_based_on_key_id(
     126             :         TALLOC_CTX *mem_ctx,
     127             :         const struct Gkid gkid,
     128             :         const NTTIME current_time,
     129             :         const struct ProvRootKey *const root_key,
     130             :         const struct dom_sid *const account_sid,
     131             :         uint8_t password[static const GMSA_PASSWORD_NULL_TERMINATED_LEN])
     132             : {
     133         191 :         NTSTATUS status = NT_STATUS_OK;
     134             : 
     135             :         /* Ensure that a specific seed key is being requested. */
     136             : 
     137         191 :         if (!gkid_is_valid(gkid)) {
     138           0 :                 status = NT_STATUS_INVALID_PARAMETER;
     139           0 :                 goto out;
     140             :         }
     141             : 
     142         191 :         if (gkid_key_type(gkid) != GKID_L2_SEED_KEY) {
     143           0 :                 status = NT_STATUS_INVALID_PARAMETER;
     144           0 :                 goto out;
     145             :         }
     146             : 
     147             :         /* Require the root key ID for the moment. */
     148         191 :         if (root_key == NULL) {
     149           0 :                 status = NT_STATUS_INVALID_PARAMETER;
     150           0 :                 goto out;
     151             :         }
     152             : 
     153             :         /* Assert that the root key may be used at this time. */
     154         191 :         if (current_time < root_key->use_start_time) {
     155           0 :                 status = NT_STATUS_INVALID_PARAMETER;
     156           0 :                 goto out;
     157             :         }
     158             : 
     159             :         {
     160             :                 /*
     161             :                  * The key being requested must not be from the future. That
     162             :                  * said, we allow for a little bit of clock skew so that samdb
     163             :                  * can compute the next managed password prior to the expiration
     164             :                  * of the current one.
     165             :                  */
     166         191 :                 const struct Gkid current_gkid = gkdi_get_interval_id(
     167             :                         current_time + gkdi_max_clock_skew);
     168         191 :                 if (!gkid_less_than_or_equal_to(gkid, current_gkid)) {
     169           0 :                         status = NT_STATUS_INVALID_PARAMETER;
     170           0 :                         goto out;
     171             :                 }
     172             :         }
     173             : 
     174             :         /*
     175             :          * Windows’ GetKey() might return not the specified L2 seed key, but an
     176             :          * earlier L2 seed key, or an L1 seed key, leaving the client to perform
     177             :          * the rest of the derivation. We are able to simplify things by always
     178             :          * deriving the specified L2 seed key, but if we implement a
     179             :          * client‐accessible GetKey(), we must take care that it match the
     180             :          * Windows implementation.
     181             :          */
     182             : 
     183             :         /*
     184             :          * Depending on the GKID that was requested, Windows’ GetKey() might
     185             :          * return a different L1 or L2 seed key, leaving the client with some
     186             :          * further derivation to do. Our simpler implementation will return
     187             :          * either the exact key the caller requested, or an error code if the
     188             :          * client is not suitably authorized.
     189             :          */
     190             : 
     191             :         {
     192           1 :                 uint8_t key[GKDI_KEY_LEN];
     193             : 
     194         191 :                 status = compute_seed_key(
     195             :                         mem_ctx,
     196             :                         data_blob_const(gmsa_security_descriptor,
     197             :                                         sizeof gmsa_security_descriptor),
     198             :                         root_key,
     199             :                         gkid,
     200             :                         key);
     201         191 :                 if (!NT_STATUS_IS_OK(status)) {
     202           0 :                         goto out;
     203             :                 }
     204             : 
     205         191 :                 status = generate_gmsa_password(key,
     206             :                                                 account_sid,
     207             :                                                 root_key->kdf_algorithm,
     208             :                                                 password);
     209         191 :                 ZERO_ARRAY(key);
     210         191 :                 if (!NT_STATUS_IS_OK(status)) {
     211           0 :                         goto out;
     212             :                 }
     213             :         }
     214             : 
     215         191 :         gmsa_post_process_password_buffer(password);
     216             : 
     217         191 : out:
     218         191 :         return status;
     219             : }
     220             : 
     221         190 : NTSTATUS gmsa_talloc_password_based_on_key_id(
     222             :         TALLOC_CTX *mem_ctx,
     223             :         const struct Gkid gkid,
     224             :         const NTTIME current_time,
     225             :         const struct ProvRootKey *const root_key,
     226             :         const struct dom_sid *const account_sid,
     227             :         struct gmsa_null_terminated_password **password_out)
     228             : {
     229         190 :         struct gmsa_null_terminated_password *password = NULL;
     230         190 :         NTSTATUS status = NT_STATUS_OK;
     231             : 
     232         190 :         if (password_out == NULL) {
     233           0 :                 return NT_STATUS_INVALID_PARAMETER;
     234             :         }
     235             : 
     236         190 :         password = talloc(mem_ctx, struct gmsa_null_terminated_password);
     237         190 :         if (password == NULL) {
     238           0 :                 return NT_STATUS_NO_MEMORY;
     239             :         }
     240             : 
     241         190 :         status = gmsa_password_based_on_key_id(mem_ctx,
     242             :                                                gkid,
     243             :                                                current_time,
     244             :                                                root_key,
     245             :                                                account_sid,
     246         190 :                                                password->buf);
     247         190 :         if (!NT_STATUS_IS_OK(status)) {
     248           0 :                 talloc_free(password);
     249           0 :                 return status;
     250             :         }
     251             : 
     252         190 :         *password_out = password;
     253         190 :         return status;
     254             : }
     255             : 
     256      364498 : bool gmsa_current_time(NTTIME *current_time_out)
     257             : {
     258       12409 :         struct timespec current_timespec;
     259       12409 :         int ret;
     260             : 
     261      364498 :         ret = clock_gettime(CLOCK_REALTIME, &current_timespec);
     262      364498 :         if (ret) {
     263           0 :                 return false;
     264             :         }
     265             : 
     266      364498 :         *current_time_out = full_timespec_to_nt_time(&current_timespec);
     267      364498 :         return true;
     268             : }

Generated by: LCOV version 1.14