2024-02-13 03:04:06 +03:00
/*
Unix SMB / CIFS implementation .
Group Managed Service Account functions
Copyright ( C ) 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 < https : //www.gnu.org/licenses/>.
*/
# include "includes.h"
# include <gnutls/gnutls.h>
# include "lib/crypto/gnutls_helpers.h"
# include "lib/crypto/gkdi.h"
# include "lib/crypto/gmsa.h"
# include "librpc/gen_ndr/ndr_security.h"
static const uint8_t gmsa_security_descriptor [ ] = {
/* O:SYD:(A;;FRFW;;;S-1-5-9) */
0x01 , 0x00 , 0x04 , 0x80 , 0x30 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ,
0x00 , 0x00 , 0x00 , 0x00 , 0x14 , 0x00 , 0x00 , 0x00 , 0x02 , 0x00 , 0x1c , 0x00 ,
0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x14 , 0x00 , 0x9f , 0x01 , 0x12 , 0x00 ,
0x01 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x05 , 0x09 , 0x00 , 0x00 , 0x00 ,
0x01 , 0x01 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x05 , 0x12 , 0x00 , 0x00 , 0x00 } ;
static const uint8_t gmsa_password_label [ ] = {
/* GMSA PASSWORD as a NULL‐ terminated UTF‐ 16LE string. */
' G ' , 0 , ' M ' , 0 , ' S ' , 0 , ' A ' , 0 , ' ' , 0 , ' P ' , 0 , ' A ' , 0 ,
' S ' , 0 , ' S ' , 0 , ' W ' , 0 , ' O ' , 0 , ' R ' , 0 , ' D ' , 0 , 0 , 0 ,
} ;
static NTSTATUS generate_gmsa_password (
const uint8_t key [ static const GKDI_KEY_LEN ] ,
const struct dom_sid * const account_sid ,
const struct KdfAlgorithm kdf_algorithm ,
uint8_t password [ static const GMSA_PASSWORD_LEN ] )
{
NTSTATUS status = NT_STATUS_OK ;
gnutls_mac_algorithm_t algorithm ;
algorithm = get_sp800_108_mac_algorithm ( kdf_algorithm ) ;
if ( algorithm = = GNUTLS_MAC_UNKNOWN ) {
status = NT_STATUS_NOT_SUPPORTED ;
goto out ;
}
if ( account_sid = = NULL ) {
status = NT_STATUS_INVALID_PARAMETER ;
goto out ;
}
{
uint8_t encoded_sid [ ndr_size_dom_sid ( account_sid , 0 ) ] ;
{
struct ndr_push ndr = {
. data = encoded_sid ,
. alloc_size = sizeof encoded_sid ,
. fixed_buf_size = true ,
} ;
enum ndr_err_code ndr_err ;
ndr_err = ndr_push_dom_sid ( & ndr ,
NDR_SCALARS | NDR_BUFFERS ,
account_sid ) ;
if ( ! NDR_ERR_CODE_IS_SUCCESS ( ndr_err ) ) {
status = ndr_map_error2ntstatus ( ndr_err ) ;
goto out ;
}
}
status = samba_gnutls_sp800_108_derive_key (
key ,
GKDI_KEY_LEN ,
NULL ,
0 ,
gmsa_password_label ,
sizeof gmsa_password_label ,
encoded_sid ,
sizeof encoded_sid ,
algorithm ,
password ,
GMSA_PASSWORD_LEN ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
goto out ;
}
}
out :
return status ;
}
static void gmsa_post_process_password_buffer (
uint8_t password [ static const GMSA_PASSWORD_NULL_TERMINATED_LEN ] )
{
size_t n ;
for ( n = 0 ; n < GMSA_PASSWORD_LEN ; n + = 2 ) {
const uint8_t a = password [ n ] ;
const uint8_t b = password [ n + 1 ] ;
if ( ! a & & ! b ) {
/*
* There is a 0.2 % chance that the generated password
* will contain an embedded null terminator , which will
* need to be converted into U + 0001.
*/
password [ n ] = 1 ;
}
}
/* Null‐ terminate the password. */
password [ GMSA_PASSWORD_LEN ] = 0 ;
password [ GMSA_PASSWORD_LEN + 1 ] = 0 ;
}
NTSTATUS gmsa_password_based_on_key_id (
TALLOC_CTX * mem_ctx ,
const struct Gkid gkid ,
const NTTIME current_time ,
const struct ProvRootKey * const root_key ,
const struct dom_sid * const account_sid ,
uint8_t password [ static const GMSA_PASSWORD_NULL_TERMINATED_LEN ] )
{
NTSTATUS status = NT_STATUS_OK ;
/* Ensure that a specific seed key is being requested. */
if ( ! gkid_is_valid ( gkid ) ) {
status = NT_STATUS_INVALID_PARAMETER ;
goto out ;
}
if ( gkid_key_type ( gkid ) ! = GKID_L2_SEED_KEY ) {
status = NT_STATUS_INVALID_PARAMETER ;
goto out ;
}
/* Require the root key ID for the moment. */
if ( root_key = = NULL ) {
status = NT_STATUS_INVALID_PARAMETER ;
goto out ;
}
/* Assert that the root key may be used at this time. */
if ( current_time < root_key - > use_start_time ) {
status = NT_STATUS_INVALID_PARAMETER ;
goto out ;
}
{
/*
* The key being requested must not be from the future . That
* said , we allow for a little bit of clock skew so that samdb
* can compute the next managed password prior to the expiration
* of the current one .
*/
const struct Gkid current_gkid = gkdi_get_interval_id (
current_time + gkdi_max_clock_skew ) ;
if ( ! gkid_less_than_or_equal_to ( gkid , current_gkid ) ) {
status = NT_STATUS_INVALID_PARAMETER ;
goto out ;
}
}
/*
* Windows ’ GetKey ( ) might return not the specified L2 seed key , but an
* earlier L2 seed key , or an L1 seed key , leaving the client to perform
* the rest of the derivation . We are able to simplify things by always
* deriving the specified L2 seed key , but if we implement a
* client ‐ accessible GetKey ( ) , we must take care that it match the
* Windows implementation .
*/
/*
* Depending on the GKID that was requested , Windows ’ GetKey ( ) might
* return a different L1 or L2 seed key , leaving the client with some
* further derivation to do . Our simpler implementation will return
* either the exact key the caller requested , or an error code if the
* client is not suitably authorized .
*/
{
uint8_t key [ GKDI_KEY_LEN ] ;
status = compute_seed_key (
mem_ctx ,
data_blob_const ( gmsa_security_descriptor ,
sizeof gmsa_security_descriptor ) ,
root_key ,
gkid ,
key ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
goto out ;
}
status = generate_gmsa_password ( key ,
account_sid ,
root_key - > kdf_algorithm ,
password ) ;
ZERO_ARRAY ( key ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
goto out ;
}
}
gmsa_post_process_password_buffer ( password ) ;
out :
return status ;
}
NTSTATUS gmsa_talloc_password_based_on_key_id (
TALLOC_CTX * mem_ctx ,
const struct Gkid gkid ,
const NTTIME current_time ,
const struct ProvRootKey * const root_key ,
const struct dom_sid * const account_sid ,
struct gmsa_null_terminated_password * * password_out )
{
struct gmsa_null_terminated_password * password = NULL ;
NTSTATUS status = NT_STATUS_OK ;
if ( password_out = = NULL ) {
return NT_STATUS_INVALID_PARAMETER ;
}
password = talloc ( mem_ctx , struct gmsa_null_terminated_password ) ;
if ( password = = NULL ) {
return NT_STATUS_NO_MEMORY ;
}
2024-04-02 00:33:27 +03:00
status = gmsa_password_based_on_key_id ( mem_ctx ,
gkid ,
current_time ,
root_key ,
account_sid ,
password - > buf ) ;
2024-02-13 03:04:06 +03:00
if ( ! NT_STATUS_IS_OK ( status ) ) {
talloc_free ( password ) ;
return status ;
}
* password_out = password ;
return status ;
}
bool gmsa_current_time ( NTTIME * current_time_out )
{
struct timespec current_timespec ;
int ret ;
ret = clock_gettime ( CLOCK_REALTIME , & current_timespec ) ;
if ( ret ) {
return false ;
}
* current_time_out = full_timespec_to_nt_time ( & current_timespec ) ;
return true ;
}