You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4130 lines
118 KiB
4130 lines
118 KiB
/*++
|
|
|
|
Copyright (c) 1991 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dbsecret.c
|
|
|
|
Abstract:
|
|
|
|
LSA - Database - Secret Object Private API Workers
|
|
|
|
NOTE: This module should remain as portable code that is independent
|
|
of the implementation of the LSA Database. As such, it is
|
|
permitted to use only the exported LSA Database interfaces
|
|
contained in db.h and NOT the private implementation
|
|
dependent functions in dbp.h.
|
|
|
|
Author:
|
|
|
|
Scott Birrell (ScottBi) December 12, 1991
|
|
|
|
Environment:
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
#include <lsapch2.h>
|
|
#include "dbp.h"
|
|
#include <lmcons.h> // CRYPT_TXT_LEN
|
|
#include <crypt.h> // required for logonmsv.h
|
|
#include <logonmsv.h> // SSI_SECRET_NAME
|
|
#include <kerberos.h> // KERB_CHANGE_MACH_PWD_REQUEST
|
|
#include <ntddnfs.h> // DD_NFS_DEVICE_NAME_U
|
|
#include <remboot.h> // LMMR_RI_XXX
|
|
#include <lsawmi.h>
|
|
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
//
|
|
// This BOOLEAN tracks whether we have received the first LsarQuerySecret()
|
|
// call for the the machine account secret. On the first call, we check
|
|
// with the remote boot code to see if the password has changed while
|
|
// the machine was off.
|
|
//
|
|
|
|
static BOOLEAN FirstMachineAccountQueryDone = FALSE;
|
|
#endif // defined(REMOTE_BOOT)
|
|
|
|
//
|
|
// The name of the machine account password secret.
|
|
//
|
|
|
|
static UNICODE_STRING LsapMachineSecret = { sizeof(SSI_SECRET_NAME)-sizeof(WCHAR), sizeof(SSI_SECRET_NAME), SSI_SECRET_NAME};
|
|
|
|
VOID
|
|
LsaIFree_LSAI_SECRET_ENUM_BUFFER (
|
|
IN PVOID Buffer,
|
|
IN ULONG Count
|
|
);
|
|
|
|
//
|
|
// The real function that LsarSetSecret() calls.
|
|
//
|
|
|
|
NTSTATUS
|
|
LsapDbSetSecret(
|
|
IN LSAPR_HANDLE SecretHandle,
|
|
IN OPTIONAL PLSAPR_CR_CIPHER_VALUE CipherCurrentValue,
|
|
IN OPTIONAL PLARGE_INTEGER CurrentTime,
|
|
IN OPTIONAL PLSAPR_CR_CIPHER_VALUE CipherOldValue,
|
|
IN OPTIONAL PLARGE_INTEGER OldTime
|
|
#if defined(REMOTE_BOOT)
|
|
,
|
|
IN BOOLEAN RemoteBootMachinePasswordChange
|
|
#endif // defined(REMOTE_BOOT)
|
|
);
|
|
|
|
BOOLEAN
|
|
LsapDbSecretIsMachineAcc(
|
|
IN LSAPR_HANDLE SecretHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function determines if the given handle refers to the local machine account
|
|
|
|
Arguments:
|
|
|
|
SecretHandle - Handle from an LsaOpenSecret call.
|
|
|
|
Return Values:
|
|
|
|
TRUE -- The handle refers to the machine account
|
|
|
|
FALSE -- It doesn't
|
|
|
|
--*/
|
|
{
|
|
BOOLEAN IsMachAcc = FALSE;
|
|
UNICODE_STRING CheckSecret;
|
|
LSAP_DB_HANDLE Handle = ( LSAP_DB_HANDLE )SecretHandle;
|
|
|
|
RtlInitUnicodeString( &CheckSecret, L"$MACHINE.ACC" );
|
|
|
|
if ( RtlEqualUnicodeString( &CheckSecret, &Handle->LogicalNameU, TRUE ) ) {
|
|
|
|
IsMachAcc = TRUE;
|
|
}
|
|
|
|
return( IsMachAcc );
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
LsarCreateSecret(
|
|
IN LSAPR_HANDLE PolicyHandle,
|
|
IN PLSAPR_UNICODE_STRING SecretName,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
OUT PLSAPR_HANDLE SecretHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is the LSA server RPC worker routine for the
|
|
LsaCreateSecret API.
|
|
|
|
The LsaCreateSecret API creates a named Secret object in the
|
|
Lsa Database. Each Secret Object can have two values assigned,
|
|
called the Current Value and the Old Value. The meaning of these
|
|
values is known to the Secret object creator. The caller must have
|
|
LSA_CREATE_SECRET access to the LsaDatabase object.
|
|
|
|
Arguments:
|
|
|
|
PolicyHandle - Handle from an LsaOpenPolicy call.
|
|
|
|
SecretName - Pointer to Unicode String specifying the name of the
|
|
secret.
|
|
|
|
DesiredAccess - Specifies the accesses to be granted to the newly
|
|
created and opened secret.
|
|
|
|
SecretHandle - Receives a handle to the newly created and opened
|
|
Secret object. This handle is used on subsequent accesses to
|
|
the object until closed.
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate access
|
|
to complete the operation.
|
|
|
|
STATUS_OBJECT_NAME_COLLISION - A Secret object having the given name
|
|
already exists.
|
|
|
|
STATUS_TOO_MANY_SECRETS - The maximum number of Secret objects in the
|
|
system has been reached.
|
|
|
|
STATUS_NAME_TOO_LONG - The name of the secret is too long to be stored
|
|
in the LSA database.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status, SecondaryStatus;
|
|
LSAP_DB_OBJECT_INFORMATION ObjectInformation;
|
|
BOOLEAN ContainerReferenced = FALSE;
|
|
ULONG CriticalValue = 0;
|
|
LSAP_DB_ATTRIBUTE Attributes[3];
|
|
ULONG TypeSpecificAttributeCount;
|
|
LARGE_INTEGER CreationTime;
|
|
ULONG Index;
|
|
ULONG CreateOptions = 0;
|
|
ULONG CreateDisp = 0;
|
|
ULONG ReferenceOptions = LSAP_DB_LOCK |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION |
|
|
LSAP_DB_READ_ONLY_TRANSACTION;
|
|
ULONG DereferenceOptions = LSAP_DB_LOCK |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION |
|
|
LSAP_DB_READ_ONLY_TRANSACTION;
|
|
BOOLEAN GlobalSecret = FALSE;
|
|
BOOLEAN DsTrustedDomainSecret = FALSE;
|
|
ULONG SecretType;
|
|
LSAP_DB_HANDLE InternalHandle;
|
|
|
|
LsarpReturnCheckSetup();
|
|
LsapEnterFunc( "LsarCreateSecret" );
|
|
|
|
|
|
//
|
|
// Check to see if the lenght of the name is within the limits of the
|
|
// LSA database.
|
|
//
|
|
|
|
if ( SecretName->Length > LSAP_DB_LOGICAL_NAME_MAX_LENGTH ) {
|
|
|
|
LsapExitFunc( "LsarCreateSecret", STATUS_NAME_TOO_LONG );
|
|
return(STATUS_NAME_TOO_LONG);
|
|
}
|
|
|
|
//
|
|
// Validate the input buffer
|
|
//
|
|
if ( !LsapValidateLsaUnicodeString( SecretName ) ) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto CreateSecretError;
|
|
}
|
|
|
|
|
|
//
|
|
// Check for Local Secret creation request. If the Secret name does
|
|
// not begin with the Global Secret Prefix, the Secret is local. In
|
|
// this case, creation of the secret is allowed on BDC's as well as
|
|
// PDC's and Workstations. Creation of Global Secrets is not
|
|
// allowed on BDC's except for trusted callers such as a Replicator.
|
|
//
|
|
SecretType = LsapDbGetSecretType( SecretName );
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_GLOBAL ) ) {
|
|
|
|
GlobalSecret = TRUE;
|
|
}
|
|
|
|
if (!GlobalSecret) {
|
|
|
|
CreateOptions |= LSAP_DB_OMIT_REPLICATOR_NOTIFICATION;
|
|
ReferenceOptions |= LSAP_DB_OMIT_REPLICATOR_NOTIFICATION;
|
|
|
|
} else {
|
|
|
|
CreateOptions |= LSAP_DB_OBJECT_SCOPE_DS;
|
|
}
|
|
|
|
//
|
|
// It isn't legal to create a TrustedDomain secret of the form G$$<DomainName>.
|
|
//
|
|
// There is a race condition between creating such secrets and converting the
|
|
// TDO between inbound and outbound. It is pragmatic to simply not allow
|
|
// creation of G$$ secrets and then not worry about morphing such objects into TDOs.
|
|
//
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_TRUSTED_DOMAIN ) ) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto CreateSecretError;
|
|
}
|
|
|
|
|
|
//
|
|
// Acquire the Lsa Database lock. Verify that the connection handle
|
|
// (container object handle) is valid, is of the expected type and has
|
|
// all of the desired accesses granted. Reference the container
|
|
// object handle.
|
|
//
|
|
|
|
Status = LsapDbReferenceObject(
|
|
PolicyHandle,
|
|
POLICY_CREATE_SECRET,
|
|
PolicyObject,
|
|
SecretObject,
|
|
ReferenceOptions
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto CreateSecretError;
|
|
}
|
|
|
|
ContainerReferenced = TRUE;
|
|
|
|
//
|
|
// Fill in the ObjectInformation structure. Initialize the
|
|
// embedded Object Attributes with the PolicyHandle as the
|
|
// Root Directory (Container Object) handle and the Logical Name
|
|
// of the account. Store the types of the object and its container.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectInformation.ObjectAttributes,
|
|
(PUNICODE_STRING)SecretName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
PolicyHandle,
|
|
NULL
|
|
);
|
|
|
|
ObjectInformation.ObjectTypeId = SecretObject;
|
|
ObjectInformation.ContainerTypeId = PolicyObject;
|
|
ObjectInformation.Sid = NULL;
|
|
ObjectInformation.ObjectAttributeNameOnly = FALSE;
|
|
ObjectInformation.DesiredObjectAccess = DesiredAccess;
|
|
|
|
//
|
|
// Set up the Creation Time as a Type Specific Attribute.
|
|
//
|
|
|
|
GetSystemTimeAsFileTime( (LPFILETIME) &CreationTime );
|
|
|
|
Index = 0;
|
|
|
|
LsapDbInitializeAttributeDs( &Attributes[Index],
|
|
CupdTime,
|
|
&CreationTime,
|
|
sizeof( LARGE_INTEGER ),
|
|
FALSE );
|
|
Index++;
|
|
LsapDbInitializeAttributeDs( &Attributes[Index],
|
|
OupdTime,
|
|
&CreationTime,
|
|
sizeof( LARGE_INTEGER ),
|
|
FALSE );
|
|
Index++;
|
|
|
|
|
|
TypeSpecificAttributeCount = Index;
|
|
|
|
//
|
|
// We'll have to see whether we have a global secret for a trusted domain in the
|
|
// DS or just a global secret. If it's the first case, we'll need to locate the
|
|
// TrustedDomain object and then we'll set the attributes on that.
|
|
//
|
|
if ( GlobalSecret ) {
|
|
|
|
Status = LsapDsIsSecretDsTrustedDomain( (PUNICODE_STRING)SecretName,
|
|
&ObjectInformation,
|
|
CreateOptions,
|
|
DesiredAccess,
|
|
SecretHandle,
|
|
&DsTrustedDomainSecret );
|
|
|
|
//
|
|
// If it's a Ds trusted domain, we don't do anything with it. If it's a global secret,
|
|
// then we'll go ahead and do our normal proceesing
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
if ( DsTrustedDomainSecret ) {
|
|
|
|
goto CreateSecretFinish;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
if ( LsaDsStateInfo.UseDs ) {
|
|
|
|
CreateDisp = LSAP_DB_CREATE_OBJECT_IN_DS;
|
|
|
|
//
|
|
// We are doing secret creation. This means that this an object that we'll need to
|
|
// mark for initial system replication when setting up a new replica
|
|
//
|
|
CriticalValue = 1;
|
|
LsapDbInitializeAttributeDs( &Attributes[Index],
|
|
PseudoSystemCritical,
|
|
&CriticalValue,
|
|
sizeof( ULONG ),
|
|
FALSE );
|
|
Index++;
|
|
TypeSpecificAttributeCount++;
|
|
|
|
}
|
|
}
|
|
|
|
ASSERT( sizeof( Attributes ) / sizeof( LSAP_DB_ATTRIBUTE ) >= TypeSpecificAttributeCount );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
//
|
|
// Create the Secret Object. We fail if the object already exists.
|
|
// Note that the object create routine performs a Database transaction.
|
|
//
|
|
|
|
Status = LsapDbCreateObject(
|
|
&ObjectInformation,
|
|
DesiredAccess,
|
|
LSAP_DB_OBJECT_CREATE | CreateDisp,
|
|
CreateOptions,
|
|
Attributes,
|
|
&TypeSpecificAttributeCount,
|
|
RTL_NUMBER_OF(Attributes),
|
|
SecretHandle
|
|
);
|
|
|
|
//
|
|
// If we are creating a secret in the ds, set the flag in the handle to know that
|
|
// we just created the secret. We'll use this later on to urgently replicate the change
|
|
//
|
|
if ( NT_SUCCESS( Status ) && GlobalSecret ) {
|
|
|
|
((LSAP_DB_HANDLE)( *SecretHandle ) )->Options |= LSAP_DB_HANDLE_CREATED_SECRET;
|
|
}
|
|
|
|
//
|
|
// Set the internal secret flag if necessary
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
InternalHandle = ( LSAP_DB_HANDLE )( *SecretHandle );
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_LOCAL ) ) {
|
|
|
|
InternalHandle->ObjectOptions |= LSAP_DB_OBJECT_SECRET_LOCAL;
|
|
|
|
}
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_SYSTEM ) ) {
|
|
|
|
InternalHandle->ObjectOptions |= LSAP_DB_OBJECT_SECRET_INTERNAL;
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto CreateSecretError;
|
|
}
|
|
|
|
CreateSecretFinish:
|
|
|
|
//
|
|
// If necessary, release the LSA Database lock.
|
|
//
|
|
|
|
if (ContainerReferenced) {
|
|
|
|
LsapDbApplyTransaction( PolicyHandle,
|
|
LSAP_DB_NO_DS_OP_TRANSACTION |
|
|
LSAP_DB_READ_ONLY_TRANSACTION,
|
|
(SECURITY_DB_DELTA_TYPE) 0 );
|
|
|
|
LsapDbReleaseLockEx( SecretObject,
|
|
LSAP_DB_READ_ONLY_TRANSACTION);
|
|
}
|
|
|
|
#ifdef TRACK_HANDLE_CLOSE
|
|
if (*SecretHandle == LsapDbHandle)
|
|
{
|
|
DbgPrint("Closing global policy handle!!!\n");
|
|
DbgBreakPoint();
|
|
}
|
|
#endif
|
|
LsarpReturnPrologue();
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarCreateSecret: 0x%lx\n", Status ));
|
|
|
|
return( Status );
|
|
|
|
CreateSecretError:
|
|
|
|
//
|
|
// If necessary, dereference the Container Object.
|
|
//
|
|
|
|
if (ContainerReferenced) {
|
|
|
|
SecondaryStatus = LsapDbDereferenceObject(
|
|
&PolicyHandle,
|
|
PolicyObject,
|
|
SecretObject,
|
|
DereferenceOptions,
|
|
(SECURITY_DB_DELTA_TYPE) 0,
|
|
Status
|
|
);
|
|
LsapDbSetStatusFromSecondary( Status, SecondaryStatus );
|
|
|
|
ContainerReferenced = FALSE;
|
|
}
|
|
|
|
goto CreateSecretFinish;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsarOpenSecret(
|
|
IN LSAPR_HANDLE ConnectHandle,
|
|
IN PLSAPR_UNICODE_STRING SecretName,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
OUT PLSAPR_HANDLE SecretHandle
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is the LSA server RPC worker routine for the LsaOpenSecret
|
|
API.
|
|
|
|
The LsaOpenSecret API opens a Secret Object within the LSA Database.
|
|
A handle is returned which must be used to perform operations on the
|
|
secret object.
|
|
|
|
Arguments:
|
|
|
|
ConnectHandle - Handle from an LsaOpenLsa call.
|
|
|
|
DesiredAccess - This is an access mask indicating accesses being
|
|
requested for the secret object being opened. These access types
|
|
are reconciled with the Discretionary Access Control List of the
|
|
target secret object to determine whether the accesses will be
|
|
granted or denied.
|
|
|
|
SecretName - Pointer to a Unicode String structure that references the
|
|
name of the Secret object to be opened.
|
|
|
|
SecretHandle - Pointer to location that will receive a handle to the
|
|
newly opened Secret object.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate access
|
|
to complete the operation.
|
|
|
|
STATUS_OBJECT_NAME_NOT_FOUND - There is no Secret object in the
|
|
target system's LSA Database having the specified SecretName.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status, SecondaryStatus;
|
|
LSAP_DB_OBJECT_INFORMATION ObjectInformation;
|
|
BOOLEAN ContainerReferenced = FALSE;
|
|
BOOLEAN AcquiredLock = FALSE;
|
|
BOOLEAN GlobalSecret = FALSE;
|
|
ULONG OpenOptions = 0;
|
|
ULONG ReferenceOptions = LSAP_DB_LOCK |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION |
|
|
LSAP_DB_READ_ONLY_TRANSACTION;
|
|
BOOLEAN DsTrustedDomainSecret = FALSE;
|
|
ULONG SecretType;
|
|
LSAP_DB_HANDLE InternalHandle;
|
|
|
|
LsarpReturnCheckSetup();
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarOpenSecret\n" ));
|
|
|
|
|
|
//
|
|
// Validate the input buffer
|
|
//
|
|
if ( !LsapValidateLsaUnicodeString( SecretName ) ) {
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto OpenSecretError;
|
|
}
|
|
|
|
//
|
|
// Check for Local Secret open request. If the Secret name does
|
|
// not begin with the Global Secret Prefix, the Secret is local. In
|
|
// this case, update/deletion of the secret is allowed on BDC's as well as
|
|
// PDC's and Workstations. Update/deletion of Global Secrets is not
|
|
// allowed on BDC's except for trusted callers such as a Replicator.
|
|
// To facilitate validation of the open request on BDC's we record
|
|
// that the BDC check should be omitted on the container reference, and
|
|
// that the replicator notification should be omitted on a commit.
|
|
//
|
|
|
|
SecretType = LsapDbGetSecretType( SecretName );
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_GLOBAL ) ) {
|
|
|
|
GlobalSecret = TRUE;
|
|
}
|
|
|
|
if (!GlobalSecret) {
|
|
|
|
OpenOptions |= LSAP_DB_OMIT_REPLICATOR_NOTIFICATION;
|
|
}
|
|
|
|
//
|
|
// Acquire the Lsa Database lock. Verify that the connection handle
|
|
// (container object handle) is valid, and is of the expected type.
|
|
// Reference the container object handle. This reference remains in
|
|
// effect until the child object handle is closed.
|
|
//
|
|
|
|
Status = LsapDbReferenceObject(
|
|
ConnectHandle,
|
|
0,
|
|
PolicyObject,
|
|
SecretObject,
|
|
ReferenceOptions
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto OpenSecretError;
|
|
}
|
|
|
|
AcquiredLock = TRUE;
|
|
ContainerReferenced =TRUE;
|
|
|
|
//
|
|
// Setup Object Information prior to calling the LSA Database Object
|
|
// Open routine. The Object Type, Container Object Type and
|
|
// Logical Name (derived from the Sid) need to be filled in.
|
|
//
|
|
|
|
ObjectInformation.ObjectTypeId = SecretObject;
|
|
ObjectInformation.ContainerTypeId = PolicyObject;
|
|
ObjectInformation.Sid = NULL;
|
|
ObjectInformation.ObjectAttributeNameOnly = FALSE;
|
|
ObjectInformation.DesiredObjectAccess = DesiredAccess;
|
|
|
|
//
|
|
// Initialize the Object Attributes. The Container Object Handle and
|
|
// Logical Name (Internal Name) of the object must be set up.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectInformation.ObjectAttributes,
|
|
(PUNICODE_STRING)SecretName,
|
|
0,
|
|
ConnectHandle,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Open the specific Secret object. Note that the account object
|
|
// handle returned is an RPC Context Handle.
|
|
//
|
|
|
|
if ( GlobalSecret ) {
|
|
|
|
Status = LsapDsIsSecretDsTrustedDomain( (PUNICODE_STRING)SecretName,
|
|
&ObjectInformation,
|
|
((LSAP_DB_HANDLE)ConnectHandle)->Options,
|
|
DesiredAccess,
|
|
SecretHandle,
|
|
&DsTrustedDomainSecret );
|
|
//
|
|
// If it's a Ds trusted domain, we don't do anything with it. If it's a global secret,
|
|
// then we'll go ahead and do our normal proceesing
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
if( DsTrustedDomainSecret ) {
|
|
|
|
InternalHandle = ( LSAP_DB_HANDLE )( *SecretHandle );
|
|
InternalHandle->ObjectOptions |= LSAP_DB_OBJECT_SECRET_INTERNAL;
|
|
goto OpenSecretFinish;
|
|
|
|
} else {
|
|
|
|
OpenOptions |= LSAP_DB_OBJECT_SCOPE_DS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsapDbOpenObject( &ObjectInformation,
|
|
DesiredAccess,
|
|
OpenOptions,
|
|
SecretHandle );
|
|
|
|
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND && GlobalSecret ) {
|
|
|
|
//
|
|
// It's possible that we have a manually created global secret... This
|
|
// would not have the SECRET postfix on the name...
|
|
//
|
|
ObjectInformation.ObjectAttributeNameOnly = TRUE;
|
|
Status = LsapDbOpenObject( &ObjectInformation,
|
|
DesiredAccess,
|
|
OpenOptions,
|
|
SecretHandle );
|
|
}
|
|
|
|
//
|
|
// Set the internal secret flag if necessary
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
InternalHandle = ( LSAP_DB_HANDLE )( *SecretHandle );
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_LOCAL ) ) {
|
|
|
|
InternalHandle->ObjectOptions |= LSAP_DB_OBJECT_SECRET_LOCAL;
|
|
|
|
}
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_SYSTEM ) ) {
|
|
|
|
InternalHandle->ObjectOptions |= LSAP_DB_OBJECT_SECRET_INTERNAL;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the open failed, dereference the container object.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto OpenSecretError;
|
|
}
|
|
|
|
OpenSecretFinish:
|
|
|
|
//
|
|
// If necessary, release the LSA Database lock.
|
|
//
|
|
|
|
if (AcquiredLock) {
|
|
|
|
LsapDbApplyTransaction( ConnectHandle,
|
|
LSAP_DB_NO_DS_OP_TRANSACTION |
|
|
LSAP_DB_READ_ONLY_TRANSACTION,
|
|
(SECURITY_DB_DELTA_TYPE) 0 );
|
|
|
|
LsapDbReleaseLockEx( SecretObject,
|
|
LSAP_DB_READ_ONLY_TRANSACTION );
|
|
}
|
|
|
|
#ifdef TRACK_HANDLE_CLOSE
|
|
if (*SecretHandle == LsapDbHandle)
|
|
{
|
|
DbgPrint("Closing global policy handle!!!\n");
|
|
DbgBreakPoint();
|
|
}
|
|
#endif
|
|
|
|
LsarpReturnPrologue();
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarOpenSecret:0x%lx\n", Status ));
|
|
|
|
return(Status);
|
|
|
|
OpenSecretError:
|
|
|
|
//
|
|
// If necessary, dereference the Container Object handle. Note that
|
|
// this is only done in the error case. In the non-error case, the
|
|
// Container handle stays referenced until the Account object is
|
|
// closed.
|
|
//
|
|
|
|
if (ContainerReferenced) {
|
|
|
|
*SecretHandle = NULL;
|
|
|
|
SecondaryStatus = LsapDbDereferenceObject(
|
|
&ConnectHandle,
|
|
PolicyObject,
|
|
SecretObject,
|
|
LSAP_DB_NO_DS_OP_TRANSACTION |
|
|
LSAP_DB_READ_ONLY_TRANSACTION,
|
|
(SECURITY_DB_DELTA_TYPE) 0,
|
|
Status
|
|
);
|
|
LsapDbSetStatusFromSecondary( Status, SecondaryStatus );
|
|
}
|
|
|
|
goto OpenSecretFinish;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapNotifySecurityPackagesOfPasswordChange(
|
|
IN PLSAP_CR_CLEAR_VALUE CurrentValue,
|
|
IN OPTIONAL PLSAP_CR_CLEAR_VALUE OldValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call the Kerberos package to let it know that the machine's
|
|
password has changed. This should only be called after it has
|
|
been updated successfully on the server.
|
|
|
|
Arguments:
|
|
|
|
NewPassword - The new NT OWF for the machine.
|
|
|
|
Return Value:
|
|
|
|
none
|
|
|
|
--*/
|
|
{
|
|
|
|
SECPKG_PRIMARY_CRED PrimaryCred;
|
|
ULONG_PTR OldPackage;
|
|
PLSAP_SECURITY_PACKAGE pAuxPackage;
|
|
NTSTATUS scRet = STATUS_SUCCESS;
|
|
|
|
|
|
//
|
|
// If we are being called during initialization
|
|
// then return success.
|
|
//
|
|
|
|
if (!LsapDbState.DbServerInitialized)
|
|
{
|
|
return( STATUS_SUCCESS);
|
|
}
|
|
|
|
if ( CurrentValue == NULL ) {
|
|
|
|
return( STATUS_SUCCESS );
|
|
}
|
|
|
|
|
|
DebugLog((DEB_TRACE, "Updating machine credentials.\n"));
|
|
|
|
|
|
|
|
RtlZeroMemory(
|
|
&PrimaryCred,
|
|
sizeof(PrimaryCred)
|
|
);
|
|
|
|
|
|
PrimaryCred.Password.Buffer = (LPWSTR) CurrentValue->Buffer;
|
|
PrimaryCred.Password.Length = (USHORT) CurrentValue->Length;
|
|
PrimaryCred.Password.MaximumLength = (USHORT) CurrentValue->MaximumLength;
|
|
PrimaryCred.Flags = PRIMARY_CRED_CLEAR_PASSWORD | PRIMARY_CRED_UPDATE;
|
|
|
|
if (OldValue != NULL)
|
|
{
|
|
PrimaryCred.OldPassword.Buffer = (LPWSTR) OldValue->Buffer;
|
|
PrimaryCred.OldPassword.Length = (USHORT) OldValue->Length;
|
|
PrimaryCred.OldPassword.MaximumLength = (USHORT) OldValue->MaximumLength;
|
|
}
|
|
|
|
|
|
OldPackage = GetCurrentPackageId();
|
|
|
|
|
|
pAuxPackage = SpmpIteratePackagesByRequest( NULL, SP_ORDINAL_ACCEPTCREDS );
|
|
|
|
while (pAuxPackage)
|
|
{
|
|
ULONG_PTR iPackage;
|
|
LUID NetworkServiceLogonId = NETWORKSERVICE_LUID;
|
|
|
|
iPackage = pAuxPackage->dwPackageID;
|
|
|
|
DebugLog((DEB_TRACE_INIT, "Updating package %ws with %x:%xn",
|
|
pAuxPackage->Name.Buffer,
|
|
SystemLogonId.HighPart, SystemLogonId.LowPart
|
|
));
|
|
|
|
SetCurrentPackageId(iPackage);
|
|
|
|
//
|
|
// call each package twice:
|
|
// once for the SYSTEM_LUID
|
|
// once for the NETWORKSERVICE_LUID
|
|
//
|
|
// Note: if an exception occurs, we don't fail the logon, we just
|
|
// do the magic on the package that blew. If the package blows,
|
|
// the other packages may succeed, and so the user may not be able
|
|
// to use that provider.
|
|
|
|
PrimaryCred.LogonId = SystemLogonId;
|
|
|
|
__try
|
|
{
|
|
|
|
scRet = pAuxPackage->FunctionTable.AcceptCredentials(
|
|
Interactive,
|
|
NULL,
|
|
&PrimaryCred,
|
|
NULL // no supplemental credentials
|
|
);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
scRet = (NTSTATUS) GetExceptionCode();
|
|
scRet = SPException(scRet, iPackage);
|
|
}
|
|
|
|
PrimaryCred.LogonId = NetworkServiceLogonId;
|
|
|
|
__try
|
|
{
|
|
|
|
scRet = pAuxPackage->FunctionTable.AcceptCredentials(
|
|
Interactive,
|
|
NULL,
|
|
&PrimaryCred,
|
|
NULL // no supplemental credentials
|
|
);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
scRet = (NTSTATUS) GetExceptionCode();
|
|
scRet = SPException(scRet, iPackage);
|
|
}
|
|
|
|
|
|
pAuxPackage = SpmpIteratePackagesByRequest( pAuxPackage,
|
|
SP_ORDINAL_ACCEPTCREDS );
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// Finally, set this thread back.
|
|
//
|
|
|
|
SetCurrentPackageId( OldPackage );
|
|
|
|
|
|
return scRet;
|
|
|
|
}
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
NTSTATUS
|
|
LsapNotifyRemoteBootOfPasswordChange(
|
|
IN PLSAP_CR_CLEAR_VALUE CurrentValue,
|
|
IN OPTIONAL PLSAP_CR_CLEAR_VALUE OldValue
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call the remote boot package to let it know that the machine's
|
|
password has changed.
|
|
|
|
Arguments:
|
|
|
|
CurrentValue - The cleartext new password.
|
|
|
|
OldValue - The cleartext old password.
|
|
|
|
Return Value:
|
|
|
|
Result of the operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status ;
|
|
HANDLE RdrDevice ;
|
|
UNICODE_STRING String ;
|
|
OBJECT_ATTRIBUTES ObjA ;
|
|
ULONG SetPasswordLength ;
|
|
PLMMR_RI_SET_NEW_PASSWORD SetPassword ;
|
|
IO_STATUS_BLOCK IoStatus ;
|
|
|
|
//
|
|
// Open the redirector device.
|
|
//
|
|
|
|
RtlInitUnicodeString( &String, DD_NFS_DEVICE_NAME_U );
|
|
|
|
InitializeObjectAttributes( &ObjA,
|
|
&String,
|
|
0,
|
|
0,
|
|
0);
|
|
|
|
Status = NtOpenFile( &RdrDevice,
|
|
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
|
|
&ObjA,
|
|
&IoStatus,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
0 );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog(( DEB_ERROR, "FAILED to open %ws, status %x\n",
|
|
String.Buffer, Status ));
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Construct the buffer that has the passwords in it.
|
|
//
|
|
|
|
SetPasswordLength = FIELD_OFFSET(LMMR_RI_SET_NEW_PASSWORD, Data[0]) +
|
|
CurrentValue->Length;
|
|
if (ARGUMENT_PRESENT(OldValue)) {
|
|
SetPasswordLength += OldValue->Length;
|
|
}
|
|
|
|
SetPassword = (PLMMR_RI_SET_NEW_PASSWORD) LsapAllocateLsaHeap( SetPasswordLength );
|
|
if (SetPassword == NULL) {
|
|
NtClose(RdrDevice);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
SetPassword->Length1 = CurrentValue->Length;
|
|
if (ARGUMENT_PRESENT(OldValue)) {
|
|
SetPassword->Length2 = OldValue->Length;
|
|
} else {
|
|
SetPassword->Length2 = 0;
|
|
}
|
|
|
|
RtlCopyMemory(
|
|
SetPassword->Data,
|
|
CurrentValue->Buffer,
|
|
CurrentValue->Length);
|
|
|
|
if (ARGUMENT_PRESENT(OldValue)) {
|
|
RtlCopyMemory(
|
|
SetPassword->Data + CurrentValue->Length,
|
|
OldValue->Buffer,
|
|
OldValue->Length);
|
|
}
|
|
|
|
Status = NtFsControlFile(
|
|
RdrDevice,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatus,
|
|
FSCTL_LMMR_RI_SET_NEW_PASSWORD,
|
|
SetPassword,
|
|
SetPasswordLength,
|
|
NULL,
|
|
0 );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog(( DEB_ERROR, "FAILED to FSCTL_LMMR_RI_SET_NEW_PASSWORD %ws, status %x\n",
|
|
String.Buffer, Status ));
|
|
}
|
|
|
|
LsapFreeLsaHeap(SetPassword);
|
|
NtClose(RdrDevice);
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
LsapCheckRemoteBootForPasswordChange(
|
|
IN LSAP_DB_HANDLE InternalHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Check with the remote boot package to see if the machine account
|
|
secret has changed, and if so, set the new value.
|
|
NOTE: This routine is called with the database lock held.
|
|
|
|
Arguments:
|
|
|
|
InternalHandle - A handle to the machine account secret.
|
|
|
|
Return Value:
|
|
|
|
Result of the operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
UNICODE_STRING DeviceName;
|
|
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
HANDLE RedirHandle = NULL;
|
|
|
|
UCHAR PacketBuffer[sizeof(ULONG)+64];
|
|
PLMMR_RI_CHECK_FOR_NEW_PASSWORD RequestPacket = (PLMMR_RI_CHECK_FOR_NEW_PASSWORD)PacketBuffer;
|
|
|
|
LSAPR_HANDLE SecretHandle = NULL;
|
|
PLSAP_CR_CIPHER_VALUE CurrentValue = NULL;
|
|
LSAP_CR_CLEAR_VALUE NewValue;
|
|
|
|
//
|
|
// First see if the redirector has a new password to tell us. This
|
|
// will fail unless the password has changed while the machine is
|
|
// off.
|
|
//
|
|
|
|
RtlInitUnicodeString(&DeviceName, DD_NFS_DEVICE_NAME_U);
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&DeviceName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = NtOpenFile(
|
|
&RedirHandle,
|
|
SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = IoStatusBlock.Status;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
DebugLog(( DEB_ERROR, "FAILED to open %ws, status %x\n",
|
|
DeviceName.Buffer, Status ));
|
|
Status = STATUS_SUCCESS;
|
|
goto CheckRemoteBootFinish;
|
|
}
|
|
|
|
//
|
|
// Send the request to the redir.
|
|
//
|
|
|
|
Status = NtFsControlFile(
|
|
RedirHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_LMMR_RI_CHECK_FOR_NEW_PASSWORD,
|
|
NULL, // no input buffer
|
|
0,
|
|
PacketBuffer,
|
|
sizeof(PacketBuffer));
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = IoStatusBlock.Status;
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
goto CheckRemoteBootFinish;
|
|
}
|
|
|
|
//
|
|
// The redir thinks there is a new password, so query the current
|
|
// one, and set the new one as the current one and the current
|
|
// one as the old one.
|
|
//
|
|
|
|
Status = LsarOpenSecret( LsapPolicyHandle,
|
|
(PLSAPR_UNICODE_STRING)&LsapMachineSecret,
|
|
MAXIMUM_ALLOWED,
|
|
&SecretHandle );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
DebugLog(( DEB_ERROR, "FAILED to LsarOpenSecret for remote boot, status %x\n",
|
|
Status ));
|
|
goto CheckRemoteBootError;
|
|
}
|
|
|
|
//
|
|
// Query the Current Value attribute of the Secret Object.
|
|
//
|
|
|
|
Status = LsapDbQueryValueSecret(
|
|
SecretHandle,
|
|
CurrVal,
|
|
NULL,
|
|
&CurrentValue
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
DebugLog(( DEB_ERROR, "FAILED to LsarDbQueryValueSecret for remote boot, status %x\n",
|
|
Status ));
|
|
goto CheckRemoteBootError;
|
|
}
|
|
|
|
//
|
|
// Set the new value if it is different from the current one.
|
|
//
|
|
|
|
NewValue.Length = RequestPacket->Length;
|
|
NewValue.MaximumLength = RequestPacket->Length;
|
|
NewValue.Buffer = RequestPacket->Data;
|
|
|
|
if ((CurrentValue->Length != NewValue.Length) ||
|
|
!RtlEqualMemory(CurrentValue->Buffer, NewValue.Buffer, CurrentValue->Length)) {
|
|
|
|
Status = LsapDbSetSecret( SecretHandle,
|
|
( PLSAPR_CR_CIPHER_VALUE )&NewValue,
|
|
NULL,
|
|
( PLSAPR_CR_CIPHER_VALUE )CurrentValue,
|
|
NULL
|
|
#if defined(REMOTE_BOOT)
|
|
,
|
|
TRUE
|
|
#endif // defined(REMOTE_BOOT)
|
|
); // this is a remote boot machine password change
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
DebugLog(( DEB_ERROR, "FAILED to LsapDbSetSecret for remote boot, status %x\n",
|
|
Status ));
|
|
goto CheckRemoteBootError;
|
|
}
|
|
|
|
}
|
|
|
|
CheckRemoteBootFinish:
|
|
|
|
if (RedirHandle != NULL) {
|
|
NtClose(RedirHandle);
|
|
}
|
|
|
|
if (SecretHandle != NULL) {
|
|
LsapCloseHandle(&SecretHandle, Status);
|
|
}
|
|
|
|
if (CurrentValue != NULL) {
|
|
MIDL_user_free(CurrentValue);
|
|
}
|
|
|
|
return(Status);
|
|
|
|
CheckRemoteBootError:
|
|
|
|
goto CheckRemoteBootFinish;
|
|
|
|
}
|
|
#endif // defined(REMOTE_BOOT)
|
|
|
|
NTSTATUS
|
|
LsarSetSecret(
|
|
IN LSAPR_HANDLE SecretHandle,
|
|
IN OPTIONAL PLSAPR_CR_CIPHER_VALUE CipherCurrentValue,
|
|
IN OPTIONAL PLSAPR_CR_CIPHER_VALUE CipherOldValue
|
|
)
|
|
{
|
|
return LsapDbSetSecret( SecretHandle,
|
|
CipherCurrentValue,
|
|
NULL,
|
|
CipherOldValue,
|
|
NULL
|
|
#if defined(REMOTE_BOOT)
|
|
,
|
|
FALSE
|
|
#endif // defined(REMOTE_BOOT)
|
|
); // not a remote boot machine password change
|
|
}
|
|
|
|
NTSTATUS
|
|
LsapDbSetSecret(
|
|
IN LSAPR_HANDLE SecretHandle,
|
|
IN OPTIONAL PLSAPR_CR_CIPHER_VALUE CipherCurrentValue,
|
|
IN OPTIONAL PLARGE_INTEGER CurrentTime,
|
|
IN OPTIONAL PLSAPR_CR_CIPHER_VALUE CipherOldValue,
|
|
IN OPTIONAL PLARGE_INTEGER OldTime
|
|
#if defined(REMOTE_BOOT)
|
|
,
|
|
IN BOOLEAN RemoteBootMachinePasswordChange
|
|
#endif // defined(REMOTE_BOOT)
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is the LSA server RPC worker routine for the LsaSetSecret
|
|
API.
|
|
|
|
The LsaSetSecret API optionally sets one or both values associated with
|
|
a Secret object. These values are known as the Current Value and
|
|
Old Value of the Secret object and these values have a meaning known to
|
|
the creator of the object.
|
|
|
|
This worker routine receives the Secret values in encrypted form from
|
|
the client. A two-way encryption algorithm using the Session Key will
|
|
havge been applied. The values received will first be decrypted using
|
|
this same key and then two-way encrypted using the LSA Database Private
|
|
Encryption Key. The resulting re-encrypted values will then be stored
|
|
as attributes of the Secret object.
|
|
|
|
Arguments:
|
|
|
|
SecretHandle - Handle from an LsaOpenSecret or LsaCreateSecret call.
|
|
|
|
CipherCurrentValue - Optional pointer to an encrypted value structure
|
|
containing the Current Value (if any) to be set for the Secret
|
|
Object (if any). This value is two-way encrypted with the Session
|
|
Key. If NULL is specified, the existing Current Value will be left
|
|
assigned to the object will be left unchanged.
|
|
|
|
CipherOldValue - Optional pointer to an encrypted value structure
|
|
containing the "old value" (if any) to be set for the Secret
|
|
Object (if any). If NULL is specified, the existing Old Value will be
|
|
assigned to the object will be left unchanged.
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
RemoteBootMachinePasswordChange - Indicates that a write lock is already
|
|
held, that it is OK to let this proceed even if the remote boot state
|
|
is CANT_NOTIFY, and that we don't need to notify Kerberos about
|
|
the change (because it hasn't initialized yet).
|
|
#endif // defined(REMOTE_BOOT)
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate access
|
|
to complete the operation.
|
|
|
|
STATUS_INVALID_HANDLE - Handle is invalid.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
LSAP_DB_HANDLE Handle = (LSAP_DB_HANDLE) SecretHandle;
|
|
|
|
PLSAP_CR_CLEAR_VALUE ClearCurrentValue = NULL;
|
|
PLSAP_CR_CLEAR_VALUE ClearOldValue = NULL;
|
|
PLSAP_CR_CIPHER_VALUE DbCipherCurrentValue = NULL;
|
|
ULONG DbCipherCurrentValueLength;
|
|
PLSAP_CR_CIPHER_VALUE DbCipherOldValue = NULL;
|
|
ULONG DbCipherOldValueLength;
|
|
PLSAP_CR_CIPHER_KEY SessionKey = NULL;
|
|
LARGE_INTEGER UpdatedTime;
|
|
LARGE_INTEGER CurrentSecretTime;
|
|
BOOLEAN ObjectReferenced = FALSE, FreeOldCipher = FALSE, FreeCurrentCipher = FALSE;
|
|
LSAP_DB_HANDLE InternalHandle = (LSAP_DB_HANDLE) SecretHandle;
|
|
ULONG ReferenceOptions = LSAP_DB_LOCK | LSAP_DB_START_TRANSACTION | LSAP_DB_NO_DS_OP_TRANSACTION;
|
|
ULONG DereferenceOptions = LSAP_DB_LOCK | LSAP_DB_FINISH_TRANSACTION | LSAP_DB_NO_DS_OP_TRANSACTION;
|
|
BOOLEAN GlobalSecret= FALSE, DsTrustedDomainSecret = FALSE;
|
|
LSAP_DB_ATTRIBUTE Attributes[LSAP_DB_ATTRS_SECRET];
|
|
PLSAP_DB_ATTRIBUTE NextAttribute;
|
|
ULONG AttributeCount = 0;
|
|
BOOLEAN NotifyMachineChange = FALSE;
|
|
|
|
LsarpReturnCheckSetup();
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarSetSecret\n" ));
|
|
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
//
|
|
// If this is a remote boot machine and this request is to set the
|
|
// machine account password, check if we can do it right now. The
|
|
// exception is if the remote boot code is notifying LSA of a changed
|
|
// password, then allow this call through, since it isn't necessary
|
|
// to notify remote boot if it is the source of the change.
|
|
//
|
|
|
|
if ((LsapDbState.RemoteBootState == LSAP_DB_REMOTE_BOOT_CANT_NOTIFY) &&
|
|
(RtlEqualUnicodeString(
|
|
&LsapMachineSecret,
|
|
&InternalHandle->LogicalNameU,
|
|
TRUE)) && // case insensitive
|
|
!RemoteBootMachinePasswordChange) {
|
|
|
|
DebugLog(( DEB_ERROR, "FAILED LsarSetSecret for machine secret, remote boot can't be notified on this boot.\n" ));
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto SetSecretError;
|
|
}
|
|
#endif // defined(REMOTE_BOOT)
|
|
|
|
//
|
|
// Check for Local Secret set request. If the Secret name does
|
|
// not begin with the Global Secret Prefix, the Secret is local. In
|
|
// this case, update of the secret is allowed on BDC's as well as
|
|
// PDC's and Workstations. Creation of Global Secrets is not
|
|
// allowed on BDC's except for trusted callers such as a Replicator.
|
|
//
|
|
|
|
if ( FLAG_ON( InternalHandle->Options, LSAP_DB_DS_TRUSTED_DOMAIN_AS_SECRET ) ) {
|
|
GlobalSecret = TRUE;
|
|
} else {
|
|
ULONG SecretType;
|
|
|
|
SecretType = LsapDbGetSecretType( ( PLSAPR_UNICODE_STRING )&InternalHandle->LogicalNameU );
|
|
|
|
if ( FLAG_ON( SecretType, LSAP_DB_SECRET_GLOBAL ) ) {
|
|
GlobalSecret = TRUE;
|
|
}
|
|
}
|
|
|
|
if ( !GlobalSecret ) {
|
|
|
|
DereferenceOptions |= LSAP_DB_OMIT_REPLICATOR_NOTIFICATION;
|
|
}
|
|
|
|
//
|
|
// Acquire the Lsa Database lock. Verify that the Secret Object handle is
|
|
// valid, is of the expected type and has all of the desired accesses
|
|
// granted. Reference the handle and open a database transaction.
|
|
//
|
|
#if defined(REMOTE_BOOT)
|
|
// If this is a remote boot machine password change, the lock is
|
|
// already acquired.
|
|
//
|
|
|
|
if (!RemoteBootMachinePasswordChange)
|
|
#endif // defined(REMOTE_BOOT)
|
|
{
|
|
|
|
Status = LsapDbReferenceObject(
|
|
SecretHandle,
|
|
SECRET_SET_VALUE,
|
|
SecretObject,
|
|
SecretObject,
|
|
ReferenceOptions
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
|
|
ObjectReferenced = TRUE;
|
|
|
|
}
|
|
|
|
if( GlobalSecret ) {
|
|
|
|
Status = LsapDsIsHandleDsObjectTypeHandle( SecretHandle,
|
|
TrustedDomainObject,
|
|
&DsTrustedDomainSecret );
|
|
}
|
|
|
|
//
|
|
// If the client is non-trusted, obtain the Session Key used by the
|
|
// client to two-way encrypt the Current Value and/or Old Values.
|
|
//
|
|
|
|
if (!InternalHandle->Trusted) {
|
|
|
|
Status = LsapCrServerGetSessionKey( SecretHandle, &SessionKey);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If a Current Value is specified for the Secret Object, and the
|
|
// client is non-trusted, decrypt the value using the Session Key and
|
|
// encrypt it using the LSA Database System Key. Then (for all
|
|
// clients) encrypt the resulting value with the internal LSA Database
|
|
// encryption key and write resulting Value structure (header followed by
|
|
// buffer to the Policy Database as the Current Value attribute of the
|
|
// Secret object. If no Current Value is specified, or a NULL
|
|
// string is specified, the existing Current Value will be deleted.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(CipherCurrentValue)) {
|
|
|
|
if (!InternalHandle->Trusted) {
|
|
|
|
Status = LsapCrDecryptValue(
|
|
(PLSAP_CR_CIPHER_VALUE) CipherCurrentValue,
|
|
SessionKey,
|
|
&ClearCurrentValue
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
|
|
} else {
|
|
|
|
ClearCurrentValue = (PLSAP_CR_CLEAR_VALUE) CipherCurrentValue;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If an Old Value is specified for the Secret Object, and the
|
|
// client is non-trusted, decrypt the value using the Session Key and
|
|
// encrypt it using the LSA Database System Key. Then (for all
|
|
// clients) encrypt the resulting value with the internal LSA Database
|
|
// encryption key and write resulting Value structure (header followed by
|
|
// buffer to the Policy Database as the Old Value attribute of the
|
|
// Secret object. If no Old Value is specified, or a NULL
|
|
// string is specified, the existing Old Value will be deleted.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(CipherOldValue)) {
|
|
|
|
if (!InternalHandle->Trusted) {
|
|
|
|
Status = LsapCrDecryptValue(
|
|
(PLSAP_CR_CIPHER_VALUE) CipherOldValue,
|
|
SessionKey,
|
|
&ClearOldValue
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
|
|
} else {
|
|
|
|
ClearOldValue = (PLSAP_CR_CLEAR_VALUE) CipherOldValue;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Get the time at which the Current Secret value was last updated.
|
|
//
|
|
|
|
GetSystemTimeAsFileTime( (LPFILETIME) &UpdatedTime );
|
|
|
|
//
|
|
// If it's a trusted domain secret, write it out now...
|
|
//
|
|
if ( DsTrustedDomainSecret ) {
|
|
|
|
Status = LsapDsSetSecretOnTrustedDomainObject( SecretHandle,
|
|
TRUST_AUTH_TYPE_CLEAR,
|
|
ClearCurrentValue,
|
|
ClearOldValue,
|
|
&UpdatedTime );
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
|
|
} else {
|
|
|
|
goto SetSecretFinish;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// If the caller didn't specify an old value,
|
|
// get the current value off the object and use that.
|
|
//
|
|
|
|
if ( ClearOldValue == NULL ) {
|
|
BOOLEAN SavedTrusted;
|
|
|
|
//
|
|
// Grab the current value of the secret.
|
|
//
|
|
// We may not have access, so go as trusted.
|
|
// Password comes back in the clear.
|
|
//
|
|
|
|
SavedTrusted = Handle->Trusted;
|
|
Handle->Trusted = TRUE;
|
|
|
|
Status = LsarQuerySecret( SecretHandle,
|
|
&(PLSAPR_CR_CIPHER_VALUE)ClearOldValue,
|
|
&CurrentSecretTime,
|
|
NULL,
|
|
NULL );
|
|
|
|
Handle->Trusted = SavedTrusted;
|
|
|
|
if ( !NT_SUCCESS(Status)) {
|
|
goto SetSecretError;
|
|
}
|
|
|
|
OldTime = &CurrentSecretTime;
|
|
}
|
|
|
|
//
|
|
// Now, encrypt them both, if we are not writing to a DS secret
|
|
//
|
|
if ( ClearCurrentValue != NULL ) {
|
|
|
|
if ( !LsapDsIsWriteDs( SecretHandle ) ) {
|
|
|
|
Status = LsapCrEncryptValue( ClearCurrentValue,
|
|
LsapDbSecretCipherKeyWrite,
|
|
&DbCipherCurrentValue );
|
|
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
|
|
FreeCurrentCipher = TRUE;
|
|
|
|
DbCipherCurrentValueLength = DbCipherCurrentValue->Length
|
|
+ sizeof( LSAP_CR_CIPHER_VALUE );
|
|
|
|
DbCipherCurrentValue->MaximumLength |= LSAP_DB_SECRET_WIN2K_SYSKEY_ENCRYPTED;
|
|
|
|
} else {
|
|
|
|
DbCipherCurrentValue = ( PLSAP_CR_CIPHER_VALUE )ClearCurrentValue->Buffer;
|
|
DbCipherCurrentValueLength = ClearCurrentValue->Length;
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
DbCipherCurrentValue = NULL;
|
|
DbCipherCurrentValueLength = 0;
|
|
|
|
}
|
|
|
|
if ( ClearOldValue != NULL ) {
|
|
|
|
if ( !LsapDsIsWriteDs( SecretHandle ) ) {
|
|
|
|
Status = LsapCrEncryptValue( ClearOldValue,
|
|
LsapDbSecretCipherKeyWrite,
|
|
&DbCipherOldValue );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
|
|
FreeOldCipher = TRUE;
|
|
|
|
DbCipherOldValueLength = DbCipherOldValue->Length + sizeof( LSAP_CR_CIPHER_VALUE );
|
|
DbCipherOldValue->MaximumLength |= LSAP_DB_SECRET_WIN2K_SYSKEY_ENCRYPTED;
|
|
|
|
} else {
|
|
|
|
DbCipherOldValue = ( PLSAP_CR_CIPHER_VALUE )ClearOldValue->Buffer;
|
|
DbCipherOldValueLength = ClearOldValue->Length;
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
DbCipherOldValue = NULL;
|
|
DbCipherOldValueLength = 0;
|
|
}
|
|
|
|
//
|
|
// Build the list of attributes
|
|
//
|
|
NextAttribute = Attributes;
|
|
|
|
//
|
|
// Current value
|
|
//
|
|
LsapDbInitializeAttributeDs( NextAttribute,
|
|
CurrVal,
|
|
DbCipherCurrentValue,
|
|
DbCipherCurrentValueLength,
|
|
FALSE );
|
|
|
|
NextAttribute++;
|
|
AttributeCount++;
|
|
|
|
//
|
|
// Current time
|
|
//
|
|
LsapDbInitializeAttributeDs( NextAttribute,
|
|
CupdTime,
|
|
CurrentTime ? CurrentTime : &UpdatedTime,
|
|
sizeof( LARGE_INTEGER ),
|
|
FALSE );
|
|
NextAttribute++;
|
|
AttributeCount++;
|
|
|
|
if ( !( LsapDsIsWriteDs( SecretHandle ) && ClearOldValue == NULL ) ) {
|
|
|
|
//
|
|
// Previous value
|
|
//
|
|
LsapDbInitializeAttributeDs( NextAttribute,
|
|
OldVal,
|
|
DbCipherOldValue,
|
|
DbCipherOldValueLength,
|
|
FALSE );
|
|
|
|
LsapDbAttributeCanNotExist(NextAttribute);
|
|
|
|
NextAttribute++;
|
|
AttributeCount++;
|
|
|
|
|
|
//
|
|
// Previous time
|
|
//
|
|
LsapDbInitializeAttributeDs( NextAttribute,
|
|
OupdTime,
|
|
OldTime ? OldTime : &UpdatedTime,
|
|
sizeof( LARGE_INTEGER ),
|
|
FALSE );
|
|
|
|
LsapDbAttributeCanNotExist(NextAttribute);
|
|
|
|
NextAttribute++;
|
|
AttributeCount++;
|
|
|
|
}
|
|
|
|
|
|
Status = LsapDbWriteAttributesObject( SecretHandle,
|
|
Attributes,
|
|
AttributeCount );
|
|
|
|
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetSecretError;
|
|
}
|
|
|
|
//
|
|
// If this is the machine account, do notification
|
|
//
|
|
|
|
if( LsapDbSecretIsMachineAcc( Handle ) ) {
|
|
|
|
LsaINotifyChangeNotification( PolicyNotifyMachineAccountPasswordInformation );
|
|
}
|
|
|
|
NotifyMachineChange = TRUE;
|
|
|
|
SetSecretFinish:
|
|
|
|
|
|
//
|
|
// If necessary, dereference the Secret object, close the database
|
|
// transaction, notify the LSA Database Replicator of the change,
|
|
// release the LSA Database lock and return. If this is a
|
|
// RemoteBootMachinePasswordChange, ObjectReferenced will be FALSE.
|
|
//
|
|
|
|
if (ObjectReferenced) {
|
|
|
|
Status = LsapDbDereferenceObject(
|
|
&SecretHandle,
|
|
SecretObject,
|
|
SecretObject,
|
|
DereferenceOptions,
|
|
SecurityDbChange,
|
|
Status
|
|
);
|
|
}
|
|
|
|
//
|
|
// If we are changing our machine account password, notify
|
|
// security packages and the remote boot code.
|
|
//
|
|
|
|
if (NotifyMachineChange && RtlEqualUnicodeString(
|
|
&LsapMachineSecret,
|
|
&InternalHandle->LogicalNameU,
|
|
TRUE)) { // case insensitive
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
//
|
|
// If this is being called due to a remote boot machine password
|
|
// change, we don't need to notify Kerberos, because this will
|
|
// be inside the initial LsarQuerySecret() call that LSA makes,
|
|
// and it notifies Kerberos after that call returns.
|
|
//
|
|
|
|
if (!RemoteBootMachinePasswordChange)
|
|
#endif // defined(REMOTE_BOOT)
|
|
{
|
|
(VOID) LsapNotifySecurityPackagesOfPasswordChange(
|
|
ClearCurrentValue,
|
|
ClearOldValue
|
|
);
|
|
}
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
//
|
|
// Notify remote boot if it wants to be notified.
|
|
//
|
|
|
|
if (LsapDbState.RemoteBootState == LSAP_DB_REMOTE_BOOT_NOTIFY) {
|
|
(VOID) LsapNotifyRemoteBootOfPasswordChange(
|
|
ClearCurrentValue,
|
|
ClearOldValue
|
|
);
|
|
}
|
|
#endif // defined(REMOTE_BOOT)
|
|
|
|
}
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Session Key.
|
|
//
|
|
|
|
if (SessionKey != NULL) {
|
|
|
|
MIDL_user_free(SessionKey);
|
|
SessionKey = NULL;
|
|
}
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Current Value
|
|
// encrypted for storage in the LSA Database.
|
|
//
|
|
|
|
if ( FreeCurrentCipher ) {
|
|
|
|
LsapCrFreeMemoryValue( DbCipherCurrentValue );
|
|
DbCipherCurrentValue = NULL;
|
|
}
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Old Value
|
|
// encrypted for storage in the LSA Database.
|
|
//
|
|
|
|
if ( FreeOldCipher ) {
|
|
|
|
LsapCrFreeMemoryValue( DbCipherOldValue );
|
|
DbCipherOldValue = NULL;
|
|
}
|
|
|
|
//
|
|
// If necessary, free memory allocated for Decrypted Current Value.
|
|
// Note that for trusted clients, the decryption is the identity
|
|
// mapping, so do not do the free in this case.
|
|
//
|
|
|
|
if ((ClearCurrentValue != NULL) && !InternalHandle->Trusted) {
|
|
|
|
LsapCrFreeMemoryValue( ClearCurrentValue );
|
|
ClearCurrentValue = NULL;
|
|
}
|
|
|
|
//
|
|
// If necessary, free memory allocated for Decrypted Old Value.
|
|
// Note that for trusted clients, the decryption is the identity
|
|
// mapping, so do not do the free in this case.
|
|
//
|
|
|
|
if ((ClearOldValue != NULL) && !InternalHandle->Trusted) {
|
|
|
|
LsapCrFreeMemoryValue( ClearOldValue );
|
|
ClearOldValue = NULL;
|
|
}
|
|
|
|
|
|
LsarpReturnPrologue();
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarSetSecret: 0x%lx\n", Status ));
|
|
|
|
return(Status);
|
|
|
|
SetSecretError:
|
|
|
|
goto SetSecretFinish;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsarQuerySecret(
|
|
IN LSAPR_HANDLE SecretHandle,
|
|
IN OUT OPTIONAL PLSAPR_CR_CIPHER_VALUE *CipherCurrentValue,
|
|
OUT OPTIONAL PLARGE_INTEGER CurrentValueSetTime,
|
|
IN OUT OPTIONAL PLSAPR_CR_CIPHER_VALUE *CipherOldValue,
|
|
OUT OPTIONAL PLARGE_INTEGER OldValueSetTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is the LSA server RPC worker routine for the LsaQuerySecret
|
|
API.
|
|
|
|
The LsaQuerySecret API optionally returns one or both of the values
|
|
assigned to a Secret object. These values are known as the "current value"
|
|
and the "old value", and they have a meaning known to the creator of the
|
|
Secret object. The values are returned in their encrypted form.
|
|
The caller must have LSA_QUERY_SECRET access to the Secret object.
|
|
|
|
Arguments:
|
|
|
|
SecretHandle - Handle from an LsaOpenSecret or LsaCreateSecret call.
|
|
|
|
CipherCurrentValue - Optional pointer to location which will receive a
|
|
pointer to an encrypted Unicode String structure containing the
|
|
"current value" of the Secret Object (if any) in encrypted form.
|
|
If no "current value" is assigned to the Secret object, a NULL pointer
|
|
is returned.
|
|
|
|
CurrentValueSetTime - The date/time at which the current secret value
|
|
was established.
|
|
|
|
CipherOldValue - Optional pointer to location which will receive a
|
|
pointer to an encrypted Unicode String structure containing the
|
|
"old value" of the Secret Object (if any) in encrypted form.
|
|
If no "old value" is assigned to the Secret object, a NULL pointer
|
|
is returned.
|
|
|
|
OldValueSetTime - The date/time at which the old secret value
|
|
was established.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate access
|
|
to complete the operation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
LSAP_DB_HANDLE InternalHandle = (LSAP_DB_HANDLE) SecretHandle;
|
|
PLSAP_CR_CIPHER_VALUE OutputCipherCurrentValue = NULL;
|
|
PLSAP_CR_CIPHER_VALUE OutputCipherOldValue = NULL;
|
|
PLSAP_CR_CIPHER_KEY SessionKey = NULL;
|
|
BOOLEAN ObjectReferenced = FALSE, DsTrustedDomainSecret = FALSE;
|
|
ULONG ValueSetTimeLength;
|
|
|
|
LsarpReturnCheckSetup();
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarQuerySecret\n" ));
|
|
|
|
LsapTraceEvent(EVENT_TRACE_TYPE_START, LsaTraceEvent_QuerySecret);
|
|
|
|
//
|
|
// If the caller is from a network call and we are opening a local/system secret,
|
|
// return an error
|
|
//
|
|
if ( InternalHandle->NetworkClient && (!InternalHandle->Trusted) &&
|
|
FLAG_ON( InternalHandle->ObjectOptions, LSAP_DB_OBJECT_SECRET_LOCAL ) ) {
|
|
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto QuerySecretReturn;
|
|
}
|
|
|
|
//
|
|
// If the caller is not trusted and they are trying to read an internal secret, return
|
|
// an error
|
|
//
|
|
if ( !InternalHandle->Trusted &&
|
|
FLAG_ON( InternalHandle->ObjectOptions, LSAP_DB_OBJECT_SECRET_INTERNAL ) ) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto QuerySecretReturn;
|
|
}
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
//
|
|
// If this is a remote boot machine that might notify us of
|
|
// password changes that happened while the machine is off, and
|
|
// this seems to be the first time this routine has been called,
|
|
// then take a write lock in case we need to write the secret
|
|
// (after we take the lock we check to make sure this really is
|
|
// the first time through).
|
|
//
|
|
// It is OK to do the initial check of FirstMachineAccountQueryDone
|
|
// without the lock because it starts as FALSE and changes to TRUE
|
|
// exactly once.
|
|
//
|
|
|
|
if ((LsapDbState.RemoteBootState != LSAP_DB_REMOTE_BOOT_NO_NOTIFICATION) &&
|
|
!FirstMachineAccountQueryDone &&
|
|
RtlEqualUnicodeString(
|
|
&LsapMachineSecret,
|
|
&InternalHandle->LogicalNameU,
|
|
TRUE)) { // case insensitive
|
|
|
|
NTSTATUS CheckStatus = STATUS_SUCCESS;
|
|
HANDLE TempHandle = SecretHandle;
|
|
|
|
//
|
|
// Take the lock with the options that LsarSetSecret will choose
|
|
// to write the machine account secret.
|
|
//
|
|
|
|
Status = LsapDbReferenceObject(
|
|
TempHandle,
|
|
SECRET_SET_VALUE,
|
|
SecretObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK |
|
|
LSAP_DB_START_TRANSACTION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
|
|
//
|
|
// Now that we have acquired the lock, check again to see if we
|
|
// need to check for a remote boot secret. If FirstMachineAccountQueryDone
|
|
// is now also TRUE, it means another thread did the query at the same
|
|
// time, and since we now have the lock, that thread has finished the
|
|
// query and any resulting updating.
|
|
//
|
|
|
|
if (!FirstMachineAccountQueryDone) {
|
|
|
|
CheckStatus = LsapCheckRemoteBootForPasswordChange(TempHandle);
|
|
FirstMachineAccountQueryDone = TRUE;
|
|
}
|
|
|
|
//
|
|
// We verify CheckStatus after we have dereferenced, so that we
|
|
// always do the dereference.
|
|
//
|
|
|
|
Status = LsapDbDereferenceObject(
|
|
&TempHandle,
|
|
SecretObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK |
|
|
LSAP_DB_FINISH_TRANSACTION |
|
|
LSAP_DB_OMIT_REPLICATOR_NOTIFICATION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION,
|
|
(SECURITY_DB_DELTA_TYPE) 0,
|
|
Status
|
|
);
|
|
|
|
if (!NT_SUCCESS(CheckStatus)) {
|
|
|
|
Status = CheckStatus;
|
|
goto QuerySecretError;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
|
|
}
|
|
#endif // defined(REMOTE_BOOT)
|
|
|
|
//
|
|
// If the caller is non-trusted, obtain the Session Key used by the
|
|
// client to two-way encrypt the Current Value and/or Old Values.
|
|
// Trusted Clients do not use encryption since they are calling
|
|
// this server service directly and not via RPC.
|
|
//
|
|
|
|
if (!InternalHandle->Trusted) {
|
|
|
|
Status = LsapCrServerGetSessionKey( SecretHandle, &SessionKey );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Acquire the Lsa Database lock. Verify that the Secret Object handle is
|
|
// valid, is of the expected type and has all of the desired accesses
|
|
// granted. Reference the handle and open a database transaction.
|
|
//
|
|
|
|
Status = LsapDbReferenceObject(
|
|
SecretHandle,
|
|
SECRET_QUERY_VALUE,
|
|
SecretObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK |
|
|
LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
|
|
ObjectReferenced = TRUE;
|
|
|
|
//
|
|
// See if its a trusted domain secret
|
|
//
|
|
Status = LsapDsIsHandleDsObjectTypeHandle( SecretHandle,
|
|
TrustedDomainObject,
|
|
&DsTrustedDomainSecret );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
|
|
//
|
|
// If it's a Ds trusted domain, go ahead and read the data. Otherwise, pass it along
|
|
//
|
|
if ( DsTrustedDomainSecret ) {
|
|
|
|
Status = LsapDsGetSecretOnTrustedDomainObject( SecretHandle,
|
|
SessionKey,
|
|
ARGUMENT_PRESENT(CipherCurrentValue) ?
|
|
&OutputCipherCurrentValue : NULL,
|
|
ARGUMENT_PRESENT(CipherOldValue) ?
|
|
&OutputCipherOldValue : NULL,
|
|
CurrentValueSetTime,
|
|
OldValueSetTime );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
goto QuerySecretFinish;
|
|
|
|
} else {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If requested, query the Current Value attribute of the Secret Object.
|
|
// For non-trusted callers, the Current value will be returned in
|
|
// encrypted form embedded within a structure.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(CipherCurrentValue)) {
|
|
|
|
Status = LsapDbQueryValueSecret(
|
|
SecretHandle,
|
|
CurrVal,
|
|
SessionKey,
|
|
&OutputCipherCurrentValue
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If requested, query the Old Value attribute of the Secret Object.
|
|
// For non-trusted callers, the Old Value will be returned in
|
|
// encrypted form embedded within a structure.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(CipherOldValue)) {
|
|
|
|
Status = LsapDbQueryValueSecret(
|
|
SecretHandle,
|
|
OldVal,
|
|
SessionKey,
|
|
&OutputCipherOldValue
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
}
|
|
|
|
ValueSetTimeLength = sizeof (LARGE_INTEGER);
|
|
|
|
//
|
|
// If requested, Query the time at which the Current Value of the Secret
|
|
// was last established. If the Current Value has never been set, return
|
|
// the time at which the Secret was created.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(CurrentValueSetTime)) {
|
|
|
|
Status = LsapDbReadAttributeObjectEx(
|
|
SecretHandle,
|
|
CupdTime,
|
|
CurrentValueSetTime,
|
|
&ValueSetTimeLength,
|
|
TRUE
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If requested, Query the time at which the Old Value of the Secret
|
|
// was last established. If the Old Value has never been set, return
|
|
// the time at which the Secret was created.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(OldValueSetTime)) {
|
|
|
|
Status = LsapDbReadAttributeObjectEx(
|
|
SecretHandle,
|
|
OupdTime,
|
|
OldValueSetTime,
|
|
&ValueSetTimeLength,
|
|
TRUE
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QuerySecretError;
|
|
}
|
|
|
|
}
|
|
|
|
QuerySecretFinish:
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Session Key.
|
|
//
|
|
|
|
if (SessionKey != NULL) {
|
|
|
|
MIDL_user_free(SessionKey);
|
|
}
|
|
|
|
//
|
|
// Return Current and/or Old Values of Secret Object, or NULL to
|
|
// caller. In error cases, NULL will be returned.
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(CipherCurrentValue)) {
|
|
|
|
(PLSAP_CR_CIPHER_VALUE) *CipherCurrentValue = OutputCipherCurrentValue;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(CipherOldValue)) {
|
|
|
|
(PLSAP_CR_CIPHER_VALUE) *CipherOldValue = OutputCipherOldValue;
|
|
}
|
|
|
|
//
|
|
// If necessary, dereference the Secret object, close the database
|
|
// transaction, release the LSA Database lock and return.
|
|
//
|
|
|
|
if (ObjectReferenced) {
|
|
|
|
Status = LsapDbDereferenceObject(
|
|
&SecretHandle,
|
|
SecretObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK |
|
|
LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION,
|
|
(SECURITY_DB_DELTA_TYPE) 0,
|
|
Status
|
|
);
|
|
}
|
|
|
|
|
|
QuerySecretReturn:
|
|
|
|
(void) LsapAdtGenerateObjectOperationAuditEvent(
|
|
SecretHandle,
|
|
NT_SUCCESS(Status) ?
|
|
EVENTLOG_AUDIT_SUCCESS : EVENTLOG_AUDIT_FAILURE,
|
|
ObjectOperationQuery
|
|
);
|
|
|
|
LsapTraceEvent(EVENT_TRACE_TYPE_END, LsaTraceEvent_QuerySecret);
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsarQuerySecret: 0x%lx\n", Status ));
|
|
LsarpReturnPrologue();
|
|
|
|
return(Status);
|
|
|
|
QuerySecretError:
|
|
|
|
//
|
|
// Deallocate any allocated memory
|
|
//
|
|
if ( OutputCipherCurrentValue ) {
|
|
|
|
MIDL_user_free( OutputCipherCurrentValue );
|
|
OutputCipherCurrentValue = NULL;
|
|
}
|
|
|
|
if ( OutputCipherOldValue ) {
|
|
|
|
MIDL_user_free( OutputCipherOldValue );
|
|
OutputCipherOldValue = NULL;
|
|
}
|
|
|
|
goto QuerySecretFinish;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapDbQueryValueSecret(
|
|
IN LSAPR_HANDLE SecretHandle,
|
|
IN LSAP_DB_NAMES ValueIndex,
|
|
IN OPTIONAL PLSAP_CR_CIPHER_KEY SessionKey,
|
|
OUT PLSAP_CR_CIPHER_VALUE *CipherValue
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function queries the specified value of a Secret Object. If
|
|
the caller is non-trusted, the value returned will have been two-way
|
|
encrypted with the Session Key. If the caller is trusted, no
|
|
encryption is done since the caller is calling us directly.
|
|
|
|
Arguments:
|
|
|
|
SecretHandle - Handle to Secret Object.
|
|
|
|
ValueName - Unicode name of the Secret Value to be queried. This
|
|
name is either "Currval" (for the Current Value) or "OldVal"
|
|
(for the Old Value.
|
|
|
|
SessionKey - Pointer to Session Key to be used for two-way encryption
|
|
of the value to be returned. This pointer must be non-NULL
|
|
except for Trusted Clients, where it must be NULL.
|
|
|
|
CipherValue - Receives 32-bit counted string pointer to Secret Value
|
|
queried. For non-trusted clients, the value will be encrypted.
|
|
|
|
WARNING - Note that CipherValue is defined to RPC as
|
|
"allocate(all_nodes)". This means that it is returned
|
|
in one contiguous block of memory rather than two, as
|
|
it would appear by the structure definition.
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS - Standard Nt Result Code.
|
|
|
|
STATUS_SUCCESS - The call completed successfully.
|
|
|
|
STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources,
|
|
such as memory, to complete the call.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG DbCipherValueLength;
|
|
PLSAP_CR_CLEAR_VALUE ClearValue = NULL;
|
|
PLSAP_CR_CIPHER_VALUE DbCipherValue = NULL;
|
|
PLSAP_CR_CIPHER_VALUE OutputCipherValue = NULL;
|
|
LSAP_DB_HANDLE InternalHandle = (LSAP_DB_HANDLE) SecretHandle;
|
|
|
|
//
|
|
// Get length of the specified Value attribute of the Secret object.
|
|
//
|
|
|
|
DbCipherValueLength = 0;
|
|
|
|
Status = LsapDbReadAttributeObjectEx(
|
|
SecretHandle,
|
|
ValueIndex,
|
|
NULL,
|
|
&DbCipherValueLength,
|
|
TRUE
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {
|
|
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
*CipherValue = NULL;
|
|
return(Status);
|
|
}
|
|
|
|
//
|
|
// We successfully read the length of the stored Secret Object value
|
|
// plus header from the Policy Database. Verify that the Secret
|
|
// Object value is either at least as long as a Cipher Value
|
|
// structure header, or is of length 0.
|
|
//
|
|
|
|
if ( !LsapDsIsWriteDs( SecretHandle ) &&
|
|
DbCipherValueLength < sizeof (LSAP_CR_CIPHER_VALUE ) ) {
|
|
|
|
if (DbCipherValueLength == 0) {
|
|
|
|
goto QueryValueSecretFinish;
|
|
}
|
|
|
|
Status = STATUS_INTERNAL_DB_ERROR;
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
//
|
|
// Allocate memory for reading the specified Value of the Secret object.
|
|
// This value is stored in the Policy Database in the form of a
|
|
// Self-Relative Value structure. The Value Buffer part is encrypted.
|
|
//
|
|
|
|
|
|
DbCipherValue = MIDL_user_allocate(DbCipherValueLength);
|
|
|
|
if (DbCipherValue == NULL) {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
//
|
|
// Read the specified Policy-database-encrypted Value attribute.
|
|
//
|
|
|
|
Status = LsapDbReadAttributeObjectEx(
|
|
SecretHandle,
|
|
ValueIndex,
|
|
DbCipherValue,
|
|
&DbCipherValueLength,
|
|
TRUE
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
//
|
|
// If we are not reading from the DS, the data is encrypted
|
|
//
|
|
if ( !LsapDsIsWriteDs( SecretHandle ) ) {
|
|
|
|
PLSAP_CR_CIPHER_KEY KeyToUse = LsapDbCipherKey;
|
|
BOOLEAN SP4Encrypted = FALSE;
|
|
|
|
//
|
|
// Verify that Lengths in returned header are consistent
|
|
// and also match returned data length - header size.
|
|
//
|
|
|
|
if (DbCipherValue->Length > DbCipherValue->MaximumLength) {
|
|
|
|
Status = STATUS_INTERNAL_DB_ERROR;
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
if ((DbCipherValue->Length + (ULONG) sizeof(LSAP_CR_CIPHER_VALUE)) !=
|
|
DbCipherValueLength) {
|
|
|
|
Status = STATUS_INTERNAL_DB_ERROR;
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
//
|
|
// If the string length is 0, something is wrong.
|
|
//
|
|
|
|
if (DbCipherValue->Length == 0) {
|
|
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
//
|
|
// Store pointer to Value buffer in the Value structure. This pointer
|
|
// points just after the header. Then decrypt the Value using the
|
|
// LSA Database Cipher Key and encrypt the result using the Session Key.
|
|
//
|
|
|
|
DbCipherValue->Buffer = (PUCHAR)(DbCipherValue + 1);
|
|
|
|
|
|
|
|
if ( FLAG_ON( DbCipherValue->MaximumLength, LSAP_DB_SECRET_SP4_SYSKEY_ENCRYPTED) ) {
|
|
|
|
DbCipherValue->MaximumLength &= ~LSAP_DB_SECRET_SP4_SYSKEY_ENCRYPTED;
|
|
|
|
if ( LsapDbSP4SecretCipherKey ) {
|
|
|
|
KeyToUse = LsapDbSP4SecretCipherKey;
|
|
SP4Encrypted = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This was encrypted using SP4 syskey, but now we don't have it... We're in trouble
|
|
//
|
|
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto QueryValueSecretError;
|
|
|
|
}
|
|
}
|
|
|
|
if ( FLAG_ON( DbCipherValue->MaximumLength, LSAP_DB_SECRET_WIN2K_SYSKEY_ENCRYPTED) ) {
|
|
|
|
DbCipherValue->MaximumLength &= ~LSAP_DB_SECRET_WIN2K_SYSKEY_ENCRYPTED;
|
|
|
|
if ( LsapDbSecretCipherKeyRead ) {
|
|
|
|
KeyToUse = LsapDbSecretCipherKeyRead;
|
|
|
|
ASSERT(!SP4Encrypted);
|
|
|
|
} else {
|
|
|
|
//
|
|
// This was encrypted using syskey, but now we don't have it... We're in trouble
|
|
//
|
|
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto QueryValueSecretError;
|
|
}
|
|
}
|
|
|
|
Status = LsapCrDecryptValue(
|
|
DbCipherValue,
|
|
KeyToUse,
|
|
&ClearValue
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
} else {
|
|
|
|
ClearValue = MIDL_user_allocate( sizeof( LSAP_CR_CLEAR_VALUE ) + DbCipherValueLength );
|
|
|
|
if ( ClearValue == NULL ) {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto QueryValueSecretError;
|
|
|
|
} else {
|
|
|
|
ClearValue->Length = DbCipherValueLength;
|
|
ClearValue->MaximumLength = DbCipherValueLength;
|
|
ClearValue->Buffer = ( PUCHAR )(ClearValue+1);
|
|
|
|
RtlCopyMemory( ClearValue->Buffer,
|
|
DbCipherValue,
|
|
DbCipherValueLength );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// If the client is non-Trusted, encrypt the value with the Session
|
|
// Key, otherwise, leave it unchanged.
|
|
//
|
|
|
|
if (!InternalHandle->Trusted) {
|
|
|
|
Status = LsapCrEncryptValue(
|
|
ClearValue,
|
|
SessionKey,
|
|
&OutputCipherValue
|
|
);
|
|
|
|
//
|
|
// Bug 574002: don't leave the cleartext value lying around
|
|
//
|
|
|
|
RtlSecureZeroMemory( ClearValue->Buffer, ClearValue->Length );
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto QueryValueSecretError;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Trusted clients get a clear-text block back.
|
|
// The block contains both the header and the text.
|
|
//
|
|
|
|
OutputCipherValue = (PLSAP_CR_CIPHER_VALUE)(ClearValue);
|
|
}
|
|
|
|
QueryValueSecretFinish:
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Db-encrypted Secret
|
|
// object Value read from the Policy Database.
|
|
//
|
|
|
|
if (DbCipherValue != NULL) {
|
|
|
|
LsapCrFreeMemoryValue( DbCipherValue );
|
|
}
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Decrypted Value.
|
|
// Trusted client's get a pointer to ClearValue back, so don't
|
|
// free it in this case.
|
|
//
|
|
|
|
if ( !InternalHandle->Trusted && ClearValue != NULL ) {
|
|
|
|
LsapCrFreeMemoryValue( ClearValue );
|
|
}
|
|
|
|
//
|
|
// Return pointer to Cipher Value (Clear Value for trusted clients) or
|
|
// NULL.
|
|
//
|
|
|
|
*CipherValue = OutputCipherValue;
|
|
return(Status);
|
|
|
|
QueryValueSecretError:
|
|
|
|
//
|
|
// If necessary, free memory allocated for the Secret object value
|
|
// after re-encryption for return to the Client.
|
|
//
|
|
|
|
if (OutputCipherValue != NULL) {
|
|
|
|
LsapCrFreeMemoryValue( OutputCipherValue );
|
|
}
|
|
|
|
goto QueryValueSecretFinish;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsaIEnumerateSecrets(
|
|
IN LSAPR_HANDLE PolicyHandle,
|
|
IN OUT PLSA_ENUMERATION_HANDLE EnumerationContext,
|
|
OUT PVOID *Buffer,
|
|
IN ULONG PreferedMaximumLength,
|
|
OUT PULONG CountReturned
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This service returns information about Secret objects. Since there
|
|
may be more information than can be returned in a single call of the
|
|
routine, multiple calls can be made to get all of the information.
|
|
To support this feature, the caller is provided with a handle that
|
|
can be used across calls to the API. On the initial call,
|
|
EnumerationContext should point to a variable that has been
|
|
initialized to 0.
|
|
|
|
Arguments:
|
|
|
|
PolicyHandle - Trusted handle to an open Policy Object.
|
|
|
|
EnumerationContext - Zero-based index at which to start the enumeration.
|
|
|
|
Buffer - Receives a pointer to a buffer containing information for
|
|
one or more Secret objects. This information is an array of
|
|
structures of type UNICODE_STRING, with each entry providing the
|
|
name of a single Secret object. When this information is no
|
|
longer needed, it must be released using MIDL_user_free.
|
|
|
|
PreferedMaximumLength - Prefered maximum length of the returned
|
|
data (in 8-bit bytes). This is not a hard upper limit but
|
|
serves as a guide. Due to data conversion between systems
|
|
with different natural data sizes, the actual amount of data
|
|
returned may be greater than this value.
|
|
|
|
CountReturned - Numer of entries returned.
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS - Standard Nt Result Code.
|
|
|
|
STATUS_SUCCESS - The call completed successfully.
|
|
|
|
STATUS_NO_MORE_ENTRIES - No entries have been returned because
|
|
there are no more.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
LSAP_DB_NAME_ENUMERATION_BUFFER RegEnumerationBuffer, DsEnumerationBuffer;
|
|
LSAP_DB_NAME_ENUMERATION_BUFFER DomainEnumerationBuffer, *CurrentEnumerationBuffer = NULL;
|
|
PUNICODE_STRING SecretNames = NULL;
|
|
BOOLEAN Locked = FALSE;
|
|
ULONG MaxLength, Entries, Context, Relative = 0;
|
|
LSA_ENUMERATION_HANDLE LocalEnumerationContext;
|
|
|
|
|
|
//
|
|
// If no Enumeration Structure is provided, return an error.
|
|
//
|
|
|
|
|
|
if ( !ARGUMENT_PRESENT(Buffer) ||
|
|
!ARGUMENT_PRESENT(EnumerationContext) ) {
|
|
return(STATUS_INVALID_PARAMETER);
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsaIEnumerateSecrets\n" ));
|
|
|
|
|
|
//
|
|
// Initialize the internal Lsa Database Enumeration Buffers, and
|
|
// the provided Enumeration Buffer to NULL.
|
|
//
|
|
RegEnumerationBuffer.EntriesRead = 0;
|
|
RegEnumerationBuffer.Names = NULL;
|
|
DsEnumerationBuffer.EntriesRead = 0;
|
|
DsEnumerationBuffer.Names = NULL;
|
|
DomainEnumerationBuffer.EntriesRead = 0;
|
|
DomainEnumerationBuffer.Names = NULL;
|
|
*Buffer = NULL;
|
|
|
|
Context = *((PULONG)EnumerationContext);
|
|
//
|
|
// Acquire the Lsa Database lock. Verify that the connection handle is
|
|
// valid, is of the expected type and has all of the desired accesses
|
|
// granted. Reference the handle.
|
|
//
|
|
|
|
Status = LsapDbReferenceObject(
|
|
PolicyHandle,
|
|
POLICY_VIEW_LOCAL_INFORMATION,
|
|
PolicyObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK |
|
|
LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
Locked = TRUE;
|
|
|
|
//
|
|
// Limit the enumeration length except for trusted callers
|
|
//
|
|
|
|
if ( !((LSAP_DB_HANDLE) PolicyHandle)->Trusted &&
|
|
(PreferedMaximumLength > LSA_MAXIMUM_ENUMERATION_LENGTH)
|
|
) {
|
|
MaxLength = LSA_MAXIMUM_ENUMERATION_LENGTH;
|
|
} else {
|
|
MaxLength = PreferedMaximumLength;
|
|
}
|
|
|
|
//
|
|
// Call general enumeration routine. This will return an array
|
|
// of names of secrets.
|
|
//
|
|
|
|
//
|
|
// Start with the Ds
|
|
//
|
|
Status = LsapDsEnumerateSecrets( &DsEnumerationBuffer );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
goto EnumSecretCleanup;
|
|
}
|
|
|
|
if ( Context < DsEnumerationBuffer.EntriesRead ) {
|
|
|
|
CurrentEnumerationBuffer = &DsEnumerationBuffer;
|
|
|
|
} else {
|
|
|
|
Relative = DsEnumerationBuffer.EntriesRead;
|
|
|
|
//
|
|
// Try the trusted domain ones
|
|
//
|
|
Status = LsapDsEnumerateTrustedDomainsAsSecrets( &DomainEnumerationBuffer );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
goto EnumSecretCleanup;
|
|
}
|
|
|
|
if ( Context < Relative + DomainEnumerationBuffer.EntriesRead) {
|
|
|
|
CurrentEnumerationBuffer = &DomainEnumerationBuffer;
|
|
|
|
} else {
|
|
|
|
if ( !LsaDsStateInfo.UseDs ) {
|
|
|
|
Relative += DomainEnumerationBuffer.EntriesRead;
|
|
|
|
LocalEnumerationContext = Context - Relative;
|
|
|
|
Status = LsapDbEnumerateNames(
|
|
PolicyHandle,
|
|
SecretObject,
|
|
&LocalEnumerationContext,
|
|
&RegEnumerationBuffer,
|
|
MaxLength
|
|
);
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
goto EnumSecretCleanup;
|
|
}
|
|
|
|
CurrentEnumerationBuffer = &RegEnumerationBuffer;
|
|
|
|
} else {
|
|
|
|
*CountReturned = 0;
|
|
Status = STATUS_NO_MORE_ENTRIES;
|
|
|
|
goto EnumSecretCleanup;
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// At this point:
|
|
//
|
|
// SUCCESS -> Some names are being returned (may or
|
|
// may not be additional names to be retrieved
|
|
// in future calls).
|
|
//
|
|
// NO_MORE_ENTRIES -> There are NO names to return
|
|
// for this or any future call.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// Return the number of entries read. Note that the Enumeration Buffer
|
|
// returned from LsapDbEnumerateNames is expected to be non-null
|
|
// in all non-error cases.
|
|
//
|
|
|
|
ASSERT(CurrentEnumerationBuffer->EntriesRead != 0);
|
|
|
|
|
|
//
|
|
// Now copy the output array of Unicode Strings for the caller.
|
|
// Memory for the array and the Unicode Buffers is allocated via
|
|
// MIDL_user_allocate.
|
|
//
|
|
|
|
Status = LsapRpcCopyUnicodeStrings(
|
|
NULL,
|
|
CurrentEnumerationBuffer->EntriesRead,
|
|
&SecretNames,
|
|
CurrentEnumerationBuffer->Names
|
|
);
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
*(PULONG)EnumerationContext += CurrentEnumerationBuffer->EntriesRead;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Fill in returned Enumeration Structure, returning 0 or NULL for
|
|
// fields in the error case.
|
|
//
|
|
|
|
*Buffer = SecretNames;
|
|
if (NULL!=CurrentEnumerationBuffer)
|
|
{
|
|
*CountReturned = CurrentEnumerationBuffer->EntriesRead;
|
|
}
|
|
else
|
|
{
|
|
*CountReturned = 0;
|
|
}
|
|
|
|
|
|
EnumSecretCleanup:
|
|
|
|
|
|
//
|
|
// Dereference retains current status value unless
|
|
// error occurs.
|
|
//
|
|
|
|
if ( Locked ) {
|
|
|
|
LsapDbDereferenceObject(
|
|
&PolicyHandle,
|
|
PolicyObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK |
|
|
LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION,
|
|
(SECURITY_DB_DELTA_TYPE) 0,
|
|
Status
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// Free the allocated memory
|
|
//
|
|
|
|
LsapDbFreeEnumerationBuffer( &DomainEnumerationBuffer );
|
|
LsapDbFreeEnumerationBuffer( &DsEnumerationBuffer );
|
|
LsapDbFreeEnumerationBuffer( &RegEnumerationBuffer );
|
|
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsaIEnumerateSecrets: 0x%lx\n", Status ));
|
|
|
|
return(Status);
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsaISetTimesSecret(
|
|
IN LSAPR_HANDLE SecretHandle,
|
|
IN PLARGE_INTEGER CurrentValueSetTime,
|
|
IN PLARGE_INTEGER OldValueSetTime
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This service is used to set the times associated with a Secret object.
|
|
This allows the times of secrets to be set to what they are on the
|
|
Primary Domain Controller (PDC) involved in an LSA Database replication
|
|
rather than being set to the time at which the Secret object is
|
|
created on a Backup Domain Controller (BDC) being replicated to.
|
|
|
|
Arguments:
|
|
|
|
SecretHandle - Trusted Handle to an open secret object. This will
|
|
have been obtained via a call to LsaCreateSecret() or LsaOpenSecret()
|
|
on which a Trusted Policy Handle was specified.
|
|
|
|
CurrentValueSetTime - The date and time to set for the date and time
|
|
at which the Current Value of the Secret object was set.
|
|
|
|
OldValueSetTime - The date and time to set for the date and time
|
|
at which the Old Value of the Secret object was set.
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_SUCCESS - The call completed successfully.
|
|
|
|
STATUS_ACCESS_DENIED - The supplied SecretHandle is not Trusted.
|
|
|
|
STATUS_INVALID_HANDLE - The supplied SecretHandle is not
|
|
a valid habdle to a Secret Object.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
LSAP_DB_HANDLE Handle = (LSAP_DB_HANDLE) SecretHandle;
|
|
BOOLEAN ObjectReferenced = FALSE;
|
|
|
|
//
|
|
// Verify that both Times are specified.
|
|
//
|
|
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
|
|
if (!ARGUMENT_PRESENT( CurrentValueSetTime )) {
|
|
|
|
goto SetTimesSecretError;
|
|
}
|
|
|
|
if (!ARGUMENT_PRESENT( CurrentValueSetTime )) {
|
|
|
|
goto SetTimesSecretError;
|
|
}
|
|
|
|
//
|
|
// Acquire the Lsa Database lock. Verify that the Secret Object handle is
|
|
// valid, is of the expected type and has all of the desired accesses
|
|
// granted. Reference the handle and open a database transaction.
|
|
//
|
|
|
|
Status = LsapDbReferenceObject(
|
|
SecretHandle,
|
|
SECRET_SET_VALUE,
|
|
SecretObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK | LSAP_DB_START_TRANSACTION | LSAP_DB_TRUSTED |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetTimesSecretError;
|
|
}
|
|
|
|
ObjectReferenced = TRUE;
|
|
|
|
//
|
|
// See if it's a secret in the Ds, since Ds replication will do the right thing
|
|
// with the time stamps
|
|
//
|
|
if ( LsapDsIsWriteDs( SecretHandle ) ) {
|
|
|
|
goto SetTimesSecretFinish;
|
|
}
|
|
|
|
|
|
//
|
|
// Set the time at which the Current Secret value was last updated
|
|
// to the specified value.
|
|
//
|
|
|
|
Status = LsapDbWriteAttributeObject(
|
|
SecretHandle,
|
|
&LsapDbNames[CupdTime],
|
|
CurrentValueSetTime,
|
|
sizeof (LARGE_INTEGER)
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetTimesSecretError;
|
|
}
|
|
|
|
//
|
|
// Set the time at which the Old Secret value was last updated
|
|
// to the specified value.
|
|
//
|
|
Status = LsapDbWriteAttributeObject(
|
|
SecretHandle,
|
|
&LsapDbNames[OupdTime],
|
|
OldValueSetTime,
|
|
sizeof (LARGE_INTEGER)
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto SetTimesSecretError;
|
|
}
|
|
|
|
SetTimesSecretFinish:
|
|
|
|
//
|
|
// If necessary, dereference the Secret object, close the database
|
|
// transaction, notify the LSA Database Replicator of the change and
|
|
// release the LSA Database lock and return.
|
|
//
|
|
|
|
if (ObjectReferenced) {
|
|
|
|
Status = LsapDbDereferenceObject(
|
|
&SecretHandle,
|
|
SecretObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK | LSAP_DB_FINISH_TRANSACTION |
|
|
LSAP_DB_NO_DS_OP_TRANSACTION,
|
|
SecurityDbChange,
|
|
Status
|
|
);
|
|
}
|
|
|
|
return(Status);
|
|
|
|
SetTimesSecretError:
|
|
|
|
goto SetTimesSecretFinish;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapDbGetScopeSecret(
|
|
IN PLSAPR_UNICODE_STRING SecretName,
|
|
OUT PBOOLEAN GlobalSecret
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks the scope of a Secret name. Secrets have either
|
|
Global or Local Scope.
|
|
|
|
Global Secrets are Secrets that are normally present on all DC's for a
|
|
Domain. They are replicated from PDC's to BDC's. On BDC's, only a
|
|
Trusted Client such as a replicator can create, update or delete Global
|
|
Secrets. Global Secrets are identified as Secrets whose name begins
|
|
with a designated prefix.
|
|
|
|
Local Secrets are Secrets that are private to a specific machine. They
|
|
are not replicated. Normal non-trusted clients may create, update or
|
|
delete Local Secrets. Local Secrets are idientified as Secrets whose
|
|
name does not begin with a designated prefix.
|
|
|
|
Arguments:
|
|
|
|
SecretName - Pointer to Unicode String containing the name of the
|
|
Secret to be checked.
|
|
|
|
GlobalSecret - Receives a Boolean indicating
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_SUCCESS - The Secret name is valid
|
|
|
|
STATUS_INVALID_PARAMETER - The Secret Name is invalid in such a
|
|
way as to prevent scope determination.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
UNICODE_STRING GlobalPrefix;
|
|
BOOLEAN OutputGlobalSecret = FALSE;
|
|
|
|
//
|
|
// Initialize a Unicode String with the Global Secret name Prefix.
|
|
//
|
|
|
|
RtlInitUnicodeString( &GlobalPrefix, LSA_GLOBAL_SECRET_PREFIX );
|
|
|
|
//
|
|
// Now check if the given Name has the Global Prefix.
|
|
//
|
|
|
|
if (RtlPrefixUnicodeString( &GlobalPrefix, (PUNICODE_STRING) SecretName, TRUE)) {
|
|
|
|
OutputGlobalSecret = TRUE;
|
|
}
|
|
|
|
*GlobalSecret = OutputGlobalSecret;
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapDbBuildSecretCache(
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function builds a cache of Secret Objects. Currently, it is not
|
|
implemented
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
|
|
|
|
NTSTATUS
|
|
LsapDsEnumerateSecrets(
|
|
IN OUT PLSAP_DB_NAME_ENUMERATION_BUFFER EnumerationBuffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function enumerates all of the secret objects in the Ds
|
|
|
|
Arguments:
|
|
|
|
EnumerationBuffer - Enumeration buffer to fill
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Success
|
|
|
|
STATUS_INSUFFICIENT_RESOURCES - A memory allocation failed.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PDSNAME *DsNames = NULL;
|
|
ULONG Items, i, Len;
|
|
PBYTE Buffer;
|
|
PUNICODE_STRING Names = NULL;
|
|
WCHAR RdnStart[ MAX_RDN_SIZE + 1 ];
|
|
ATTRTYP AttrType;
|
|
BOOLEAN ResetStates = FALSE;
|
|
|
|
//
|
|
// Just return if the Ds isn't running
|
|
//
|
|
if (!LsapDsWriteDs ) {
|
|
|
|
RtlZeroMemory( EnumerationBuffer, sizeof( LSAP_DB_NAME_ENUMERATION_BUFFER ) );
|
|
return( Status );
|
|
}
|
|
|
|
Status = LsapDsInitAllocAsNeededEx(
|
|
LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_NO_LOCK |
|
|
LSAP_DB_DS_OP_TRANSACTION,
|
|
SecretObject,
|
|
&ResetStates);
|
|
|
|
if ( !NT_SUCCESS( Status )) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// First, enumerate all of the secrets
|
|
//
|
|
Status = LsapDsGetListOfSystemContainerItems( CLASS_SECRET,
|
|
&Items,
|
|
&DsNames );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Names = MIDL_user_allocate( Items * sizeof( UNICODE_STRING ) );
|
|
|
|
if( Names == NULL ) {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
} else if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
|
|
|
|
Items = 0;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Now, we'll start building the appropriate names for each object
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
for ( i = 0; i < Items; i++ ) {
|
|
|
|
Status = LsapDsMapDsReturnToStatus( GetRDNInfoExternal(
|
|
DsNames[ i ],
|
|
RdnStart,
|
|
&Len,
|
|
&AttrType ) );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
PBYTE Buffer;
|
|
|
|
//
|
|
// Allocate a buffer to hold the name
|
|
//
|
|
|
|
Buffer = MIDL_user_allocate( Len * sizeof( WCHAR ) +
|
|
sizeof( LSA_GLOBAL_SECRET_PREFIX ) );
|
|
|
|
if ( Buffer == NULL ) {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If the LSA created the global secret, we appended a postfix... Remove
|
|
// that here.
|
|
//
|
|
RdnStart[ Len ] = UNICODE_NULL;
|
|
if ( Len > LSAP_DS_SECRET_POSTFIX_LEN &&
|
|
_wcsicmp( &RdnStart[Len-LSAP_DS_SECRET_POSTFIX_LEN],
|
|
LSAP_DS_SECRET_POSTFIX ) == 0 ) {
|
|
|
|
Len -= LSAP_DS_SECRET_POSTFIX_LEN;
|
|
RdnStart[ Len ] = UNICODE_NULL;
|
|
UNICODE_NULL;
|
|
}
|
|
|
|
|
|
RtlCopyMemory( Buffer,
|
|
LSA_GLOBAL_SECRET_PREFIX,
|
|
sizeof( LSA_GLOBAL_SECRET_PREFIX ) );
|
|
RtlCopyMemory( Buffer + sizeof( LSA_GLOBAL_SECRET_PREFIX ) - sizeof(WCHAR),
|
|
RdnStart,
|
|
( Len + 1 ) * sizeof( WCHAR ) );
|
|
|
|
RtlInitUnicodeString( &Names[ i ], (PWSTR)Buffer );
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free any allocated memory we no longer need
|
|
//
|
|
if ( DsNames != NULL ) {
|
|
|
|
LsapFreeLsaHeap( DsNames );
|
|
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
for ( i = 0 ; i < Items ; i++ ) {
|
|
|
|
MIDL_user_free( Names[i].Buffer );
|
|
}
|
|
|
|
MIDL_user_free( Names );
|
|
|
|
} else {
|
|
|
|
EnumerationBuffer->Names = Names;
|
|
EnumerationBuffer->EntriesRead = Items;
|
|
}
|
|
|
|
LsapDsDeleteAllocAsNeededEx(
|
|
LSAP_DB_READ_ONLY_TRANSACTION |
|
|
LSAP_DB_NO_LOCK |
|
|
LSAP_DB_DS_OP_TRANSACTION,
|
|
SecretObject,
|
|
ResetStates
|
|
);
|
|
|
|
return( Status );
|
|
}
|
|
|
|
NTSTATUS
|
|
LsapDsIsSecretDsTrustedDomainForUpgrade(
|
|
IN PUNICODE_STRING SecretName,
|
|
OUT PLSAPR_HANDLE TDObjHandle,
|
|
OUT BOOLEAN *IsTrustedDomainSecret
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will determine if the indicated secret is the global secret for a trust object.
|
|
|
|
Arguments:
|
|
|
|
SecretName - Name of secret to check
|
|
|
|
ObjectInformation - LsaDb information on the object
|
|
|
|
Options - Options to use for the access
|
|
|
|
DesiredAccess - Access to open the object with
|
|
|
|
TDObjHandle - Where the object handle is returned
|
|
|
|
IsTrustedDomainSecret - A TRUE is returned here if this secret is indeed a trusted domain
|
|
secret.
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS - Success
|
|
|
|
STATUS_INSUFFICIENT_RESOURCES - A memory allocation failed
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PWSTR pwszSecretName;
|
|
ATTR SearchAttr[2];
|
|
ATTRVAL AttrVal[2];
|
|
ULONG ObjId = CLASS_TRUSTED_DOMAIN;
|
|
PDSNAME FoundName = NULL;
|
|
UNICODE_STRING HandleName;
|
|
LSAP_DB_HANDLE TDHandle;
|
|
LSAP_DB_OBJECT_INFORMATION NewObjInfo;
|
|
ULONG TDONameLength = 0;
|
|
|
|
LsapEnterFunc( "LsapDsIsSecretDsTrustedDomain" );
|
|
|
|
*IsTrustedDomainSecret = FALSE;
|
|
*TDObjHandle = NULL;
|
|
|
|
LsapDsReturnSuccessIfNoDs
|
|
|
|
if ( LsaDsStateInfo.DsInitializedAndRunning == FALSE ) {
|
|
|
|
LsapDsDebugOut((DEB_ERROR,
|
|
"LsapDsIsSecretDsTrustedDomain: Object %wZ, Ds is not started\n ",
|
|
SecretName ));
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
//
|
|
// Convert the secret name to a TDO name.
|
|
//
|
|
if ( SecretName->Length <= (LSAP_DS_TRUSTED_DOMAIN_SECRET_PREFIX_LENGTH * sizeof(WCHAR)) ) {
|
|
return Status;
|
|
}
|
|
|
|
pwszSecretName = SecretName->Buffer + LSAP_DS_TRUSTED_DOMAIN_SECRET_PREFIX_LENGTH;
|
|
|
|
LsapDsDebugOut((DEB_TRACE, "Matching secret %ws to trusted domain\n ", pwszSecretName ));
|
|
|
|
|
|
AttrVal[0].valLen = sizeof( ULONG );
|
|
AttrVal[0].pVal = (PVOID)&ObjId;
|
|
|
|
AttrVal[1].valLen = TDONameLength = wcslen(pwszSecretName) * sizeof(WCHAR );
|
|
AttrVal[1].pVal = (PVOID)pwszSecretName;
|
|
|
|
SearchAttr[ 0 ].attrTyp = LsapDbDsAttInfo[TrDmName].AttributeId;
|
|
SearchAttr[ 0 ].AttrVal.valCount = 1;
|
|
SearchAttr[ 0 ].AttrVal.pAVal = &AttrVal[1];
|
|
SearchAttr[ 1 ].attrTyp = ATT_OBJECT_CLASS;
|
|
SearchAttr[ 1 ].AttrVal.valCount = 1;
|
|
SearchAttr[ 1 ].AttrVal.pAVal = &AttrVal[0];
|
|
|
|
Status = LsapDsSearchUnique( LSAPDS_OP_NO_TRANS,
|
|
LsaDsStateInfo.DsSystemContainer,
|
|
SearchAttr,
|
|
sizeof(SearchAttr) / sizeof(ATTR),
|
|
&FoundName );
|
|
|
|
|
|
|
|
if ( Status == STATUS_SUCCESS ) {
|
|
|
|
UNICODE_STRING TDOName;
|
|
|
|
//
|
|
// This is a trusted domain, try opening the trusted domain
|
|
//
|
|
|
|
TDOName.Buffer = pwszSecretName;
|
|
TDOName.Length = TDOName.MaximumLength = (USHORT) TDONameLength;
|
|
|
|
|
|
Status = LsapDbOpenTrustedDomainByName(
|
|
NULL, // use global policy handle
|
|
&TDOName,
|
|
TDObjHandle,
|
|
MAXIMUM_ALLOWED,
|
|
LSAP_DB_START_TRANSACTION,
|
|
TRUE ); // Trusted
|
|
|
|
|
|
*IsTrustedDomainSecret = TRUE;
|
|
LsapFreeLsaHeap( FoundName );
|
|
|
|
}
|
|
else if (STATUS_OBJECT_NAME_NOT_FOUND==Status)
|
|
{
|
|
//
|
|
// O.K to not find the object. That means the secret does not
|
|
// correspond to a TDO.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapDsSecretUpgradeRegistryToDs(
|
|
IN BOOLEAN DeleteOnly
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will move the remaining registry based secrets into the Ds
|
|
|
|
NOTE: It is assumed that the database is locked before calling this routine
|
|
|
|
Arguments:
|
|
|
|
DeleteOnly -- If TRUE, the registry values are deleted following the upgade.
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_ENUMERATION_HANDLE EnumContext = 0;
|
|
PBYTE EnumBuffer;
|
|
ULONG Items, i;
|
|
BOOLEAN GlobalSecret;
|
|
LSAPR_HANDLE Secret = NULL , DsSecret;
|
|
PLSAPR_CR_CIPHER_VALUE Current = NULL, Old = NULL ;
|
|
PLSAPR_UNICODE_STRING SecretName;
|
|
BOOLEAN DbLocked = FALSE;
|
|
LSAP_DB_NAME_ENUMERATION_BUFFER NameEnum;
|
|
BOOLEAN IsTrustedDomainSecret = FALSE;
|
|
LSAPR_HANDLE TDObjHandle = NULL;
|
|
BOOLEAN UseDsOld = LsaDsStateInfo.UseDs;
|
|
|
|
if ( !LsapDsWriteDs
|
|
&& !DeleteOnly ) {
|
|
|
|
return( STATUS_SUCCESS );
|
|
}
|
|
|
|
( ( LSAP_DB_HANDLE )LsapPolicyHandle )->Options |= LSAP_DB_HANDLE_UPGRADE;
|
|
|
|
//
|
|
// First, enumerate all of the registry based trusted domains
|
|
//
|
|
while ( NT_SUCCESS( Status ) ) {
|
|
|
|
LsaDsStateInfo.UseDs = FALSE;
|
|
|
|
Status = LsaIEnumerateSecrets( LsapPolicyHandle,
|
|
&EnumContext,
|
|
&EnumBuffer,
|
|
TENMEG,
|
|
&Items );
|
|
|
|
LsaDsStateInfo.UseDs = UseDsOld;
|
|
|
|
if ( Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) {
|
|
|
|
for ( i = 0; i < Items; i++ ) {
|
|
|
|
Secret = NULL;
|
|
SecretName = &( ( PLSAPR_UNICODE_STRING )EnumBuffer )[ i ];
|
|
Status = LsapDbGetScopeSecret( SecretName,
|
|
&GlobalSecret );
|
|
|
|
if ( !NT_SUCCESS( Status ) || !GlobalSecret ) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Get the information from the registry for this secret...
|
|
//
|
|
LsaDsStateInfo.UseDs = FALSE;
|
|
|
|
Status = LsarOpenSecret( LsapPolicyHandle,
|
|
SecretName,
|
|
MAXIMUM_ALLOWED,
|
|
&Secret );
|
|
|
|
if ( DeleteOnly ) {
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsarDeleteObject( &Secret );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
LsapDsDebugOut(( DEB_UPGRADE,
|
|
"Failed to delete secret (0x%x)\n",
|
|
Status ));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
} else {
|
|
|
|
LsapDsDebugOut(( DEB_UPGRADE,
|
|
"Failed to open secret to delete it (0x%x)\n",
|
|
Status ));
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
LsaDsStateInfo.UseDs = UseDsOld;
|
|
|
|
} else {
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsarQuerySecret( Secret,
|
|
&Current,
|
|
NULL,
|
|
&Old,
|
|
NULL );
|
|
}
|
|
|
|
|
|
LsaDsStateInfo.UseDs = UseDsOld;
|
|
|
|
//
|
|
// Check if the secret is a global secret corresponding
|
|
// to a trusted domain. Note at this point, due to the
|
|
// sequence of the upgrade, we have trusted domain objects
|
|
// in the DS, corresponding to the outbound trusts only
|
|
//
|
|
|
|
|
|
|
|
Status = LsapDsIsSecretDsTrustedDomainForUpgrade(
|
|
(PUNICODE_STRING)SecretName,
|
|
&TDObjHandle,
|
|
&IsTrustedDomainSecret
|
|
);
|
|
|
|
//
|
|
// Now, if that worked, write it out to the Ds
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
if ( IsTrustedDomainSecret) {
|
|
|
|
TRUSTED_DOMAIN_AUTH_INFORMATION AuthInfo;
|
|
LSA_AUTH_INFORMATION CurAuth,OldAuth;
|
|
|
|
//
|
|
// Build the auth information to be written out
|
|
//
|
|
|
|
ASSERT(NULL!=Current);
|
|
NtQuerySystemTime(&CurAuth.LastUpdateTime);
|
|
CurAuth.AuthType = TRUST_AUTH_TYPE_CLEAR;
|
|
CurAuth.AuthInfoLength = Current->Length;
|
|
CurAuth.AuthInfo = Current->Buffer;
|
|
|
|
|
|
|
|
AuthInfo.IncomingAuthInfos = 0;
|
|
AuthInfo.IncomingAuthenticationInformation = NULL;
|
|
AuthInfo.IncomingPreviousAuthenticationInformation = NULL;
|
|
AuthInfo.OutgoingAuthInfos = 1;
|
|
AuthInfo.OutgoingAuthenticationInformation = &CurAuth;
|
|
|
|
if (NULL!=Old) {
|
|
|
|
NtQuerySystemTime(&OldAuth.LastUpdateTime);
|
|
OldAuth.AuthType = TRUST_AUTH_TYPE_CLEAR;
|
|
OldAuth.AuthInfoLength = Old->Length;
|
|
OldAuth.AuthInfo = Old->Buffer;
|
|
AuthInfo.OutgoingPreviousAuthenticationInformation = &OldAuth;
|
|
|
|
} else {
|
|
|
|
AuthInfo.OutgoingPreviousAuthenticationInformation =NULL;
|
|
}
|
|
|
|
Status = LsarSetInformationTrustedDomain(
|
|
TDObjHandle,
|
|
TrustedDomainAuthInformation,
|
|
( PLSAPR_TRUSTED_DOMAIN_INFO ) &AuthInfo );
|
|
|
|
LsapCloseHandle(&TDObjHandle, Status);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Case of a normal secret
|
|
//
|
|
|
|
Status = LsarCreateSecret(
|
|
LsapPolicyHandle,
|
|
SecretName,
|
|
MAXIMUM_ALLOWED,
|
|
&DsSecret );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsarSetSecret( DsSecret,
|
|
Current,
|
|
Old );
|
|
|
|
LsapCloseHandle( &DsSecret, Status );
|
|
|
|
#if DBG
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
LsapDsDebugOut(( DEB_UPGRADE,
|
|
"Moved Secret %wZ to Ds\n",
|
|
SecretName ));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if ( Status == STATUS_OBJECT_NAME_COLLISION ) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (!LsapDsIsNtStatusResourceError(Status)) {
|
|
|
|
//
|
|
// Log an event log message indicating the failure
|
|
//
|
|
|
|
SpmpReportEventU(
|
|
EVENTLOG_ERROR_TYPE,
|
|
LSA_SECRET_UPGRADE_ERROR,
|
|
0,
|
|
sizeof( ULONG ),
|
|
&Status,
|
|
1,
|
|
SecretName
|
|
);
|
|
|
|
//
|
|
// Continue on all errors excepting resource errors
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Break out of the loop and terminate the upgrade
|
|
//
|
|
|
|
if ( Secret ) {
|
|
|
|
LsapCloseHandle( &Secret, Status );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( Secret ) {
|
|
|
|
LsapCloseHandle( &Secret, Status );
|
|
}
|
|
}
|
|
}
|
|
|
|
LsaIFree_LSAI_SECRET_ENUM_BUFFER ( EnumBuffer, Items );
|
|
}
|
|
}
|
|
|
|
if ( Status == STATUS_NO_MORE_ENTRIES ) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
( ( LSAP_DB_HANDLE )LsapPolicyHandle )->Options &= ~LSAP_DB_HANDLE_UPGRADE;
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
ULONG
|
|
LsapDbGetSecretType(
|
|
IN PLSAPR_UNICODE_STRING SecretName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function checks the type (scope) of a Secret name. Secrets have
|
|
Global, Local, System, or Client Scope.
|
|
|
|
Global Secrets are Secrets that are normally present on all DC's for a
|
|
Domain. They are replicated from PDC's to BDC's. On BDC's, only a
|
|
Trusted Client such as a replicator can create, update or delete Global
|
|
Secrets. Global Secrets are identified as Secrets whose name begins
|
|
with a designated prefix.
|
|
|
|
Local Secrets are Secrets that cannot be opened/read/set by anyone
|
|
attempting the operation from across the network.
|
|
|
|
System Secrets are thos Secrets that never leave the LSA process.
|
|
Examples are netlogon secrets and service controller secrets
|
|
|
|
Client Secrets are Secrets that are private to a specific machine. They
|
|
are not replicated. Normal non-trusted clients may create, update or
|
|
delete Local Secrets. Client Secrets are idientified as Secrets whose
|
|
name does not begin with a designated prefix. These were referred to as
|
|
Local Secrets in the NT3.x-4.x timeframe.
|
|
|
|
Arguments:
|
|
|
|
SecretName - Pointer to Unicode String containing the name of the
|
|
Secret to be checked.
|
|
|
|
Return Values:
|
|
|
|
SecretType - Mask of flags describing the type of secret
|
|
|
|
--*/
|
|
|
|
{
|
|
UNICODE_STRING Prefix;
|
|
ULONG i, SecretType = 0;
|
|
LSAP_DB_SECRET_TYPE_LOOKUP SecretTypePrefixLookupTable[ ] = {
|
|
{ LSAP_DS_TRUSTED_DOMAIN_SECRET_PREFIX, LSAP_DB_SECRET_GLOBAL|LSAP_DB_SECRET_TRUSTED_DOMAIN },
|
|
{ LSA_GLOBAL_SECRET_PREFIX, LSAP_DB_SECRET_GLOBAL },
|
|
{ LSA_LOCAL_SECRET_PREFIX, LSAP_DB_SECRET_LOCAL },
|
|
{ LSA_MACHINE_SECRET_PREFIX, LSAP_DB_SECRET_SYSTEM },
|
|
{ L"_sc_", LSAP_DB_SECRET_SYSTEM }, // Service Controller passwords
|
|
{ L"NL$", LSAP_DB_SECRET_SYSTEM }, // Netlogon secrets
|
|
{ L"RasDialParams", LSAP_DB_SECRET_LOCAL },
|
|
{ L"RasCredentials", LSAP_DB_SECRET_LOCAL }
|
|
};
|
|
|
|
LSAP_DB_SECRET_TYPE_LOOKUP SecretTypeNameLookupTable[ ] = {
|
|
{ L"$MACHINE.ACC", LSAP_DB_SECRET_LOCAL },
|
|
{ L"SAC", LSAP_DB_SECRET_LOCAL },
|
|
{ L"SAI", LSAP_DB_SECRET_LOCAL },
|
|
{ L"SANSC", LSAP_DB_SECRET_LOCAL }
|
|
};
|
|
|
|
//
|
|
// Until we know better, assume a normal secret
|
|
//
|
|
SecretType = LSAP_DB_SECRET_CLIENT;
|
|
|
|
for ( i = 0;
|
|
i < sizeof( SecretTypePrefixLookupTable ) / sizeof( LSAP_DB_SECRET_TYPE_LOOKUP );
|
|
i++ ) {
|
|
|
|
//
|
|
// Initialize a Unicode String with the Global Secret name Prefix.
|
|
//
|
|
RtlInitUnicodeString( &Prefix, SecretTypePrefixLookupTable[ i ].SecretPrefix );
|
|
|
|
//
|
|
// Now check if the given Name has the Global Prefix.
|
|
//
|
|
|
|
if ( RtlPrefixUnicodeString( &Prefix, (PUNICODE_STRING)SecretName, TRUE ) ) {
|
|
|
|
SecretType |= SecretTypePrefixLookupTable[ i ].SecretType;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If it's not known yet, see if it is one of the full named secrets we know about...
|
|
//
|
|
if ( SecretType == LSAP_DB_SECRET_CLIENT ) {
|
|
|
|
for ( i = 0;
|
|
i < sizeof( SecretTypeNameLookupTable ) / sizeof( LSAP_DB_SECRET_TYPE_LOOKUP );
|
|
i++ ) {
|
|
|
|
//
|
|
// Initialize a Unicode String with the Global Secret name Prefix.
|
|
//
|
|
RtlInitUnicodeString( &Prefix, SecretTypeNameLookupTable[ i ].SecretPrefix );
|
|
|
|
//
|
|
// Now check if the given Name matches our known secret name
|
|
//
|
|
|
|
if ( RtlEqualUnicodeString( &Prefix, ( PUNICODE_STRING )SecretName, TRUE ) ) {
|
|
|
|
SecretType |= SecretTypeNameLookupTable[ i ].SecretType;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return( SecretType );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapDbUpgradeSecretForKeyChange(
|
|
VOID
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSAPR_HANDLE SecretHandle;
|
|
PLSAPR_CR_CIPHER_VALUE Current, Old;
|
|
LARGE_INTEGER CurrentTime, OldTime;
|
|
LSA_ENUMERATION_HANDLE EnumContext = 0;
|
|
ULONG EnumCount, i;
|
|
PUNICODE_STRING SecretList;
|
|
BOOLEAN LockHeld = FALSE;
|
|
|
|
Status = LsapDbReferenceObject( LsapDbHandle,
|
|
0,
|
|
PolicyObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
LockHeld = TRUE;
|
|
}
|
|
|
|
while ( Status == STATUS_SUCCESS ) {
|
|
|
|
SecretList = NULL;
|
|
Status = LsaIEnumerateSecrets( LsapDbHandle,
|
|
&EnumContext,
|
|
( PVOID * )&SecretList,
|
|
2048,
|
|
&EnumCount );
|
|
|
|
for ( i = 0; NT_SUCCESS( Status ) && i < EnumCount; i++ ) {
|
|
|
|
Status = LsarOpenSecret( LsapDbHandle,
|
|
( PLSAPR_UNICODE_STRING )&SecretList[ i ],
|
|
SECRET_SET_VALUE | SECRET_QUERY_VALUE,
|
|
&SecretHandle );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsarQuerySecret( SecretHandle,
|
|
&Current,
|
|
&CurrentTime,
|
|
&Old,
|
|
&OldTime );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsapDbSetSecret( SecretHandle,
|
|
Current,
|
|
&CurrentTime,
|
|
Old,
|
|
&OldTime
|
|
#if defined(REMOTE_BOOT)
|
|
,
|
|
FALSE
|
|
#endif // defined(REMOTE_BOOT)
|
|
);
|
|
|
|
LsaIFree_LSAPR_CR_CIPHER_VALUE( Current );
|
|
LsaIFree_LSAPR_CR_CIPHER_VALUE( Old );
|
|
|
|
}
|
|
|
|
LsapCloseHandle( &SecretHandle, Status );
|
|
}
|
|
|
|
//
|
|
// If there was a problem with the secret, press on:
|
|
// we want to convert as many of them as possible.
|
|
//
|
|
// Log an event log message indicating the failure
|
|
//
|
|
|
|
if ( !NT_SUCCESS( Status )) {
|
|
|
|
SpmpReportEventU(
|
|
EVENTLOG_ERROR_TYPE,
|
|
LSA_SECRET_UPGRADE_ERROR,
|
|
0,
|
|
sizeof( ULONG ),
|
|
&Status,
|
|
1,
|
|
&SecretList[ i ]
|
|
);
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
MIDL_user_free( SecretList );
|
|
|
|
|
|
}
|
|
|
|
if ( Status == STATUS_NO_MORE_ENTRIES ) {
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
if ( LockHeld ) {
|
|
|
|
Status = LsapDbDereferenceObject( &LsapDbHandle,
|
|
PolicyObject,
|
|
SecretObject,
|
|
LSAP_DB_LOCK,
|
|
( SECURITY_DB_DELTA_TYPE )0,
|
|
Status );
|
|
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
LsaIChangeSecretCipherKey(
|
|
IN PVOID NewSysKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Given a new syskey, creates a new password encryption key and
|
|
re-encrypts all the secrets with it
|
|
|
|
Arguments:
|
|
|
|
NewSysKey - new syskey
|
|
|
|
Return Values:
|
|
|
|
NTSTATUS error code
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
LSAP_DB_ENCRYPTION_KEY NewEncryptionKey;
|
|
LSAP_DB_ATTRIBUTE Attributes[1];
|
|
PLSAP_DB_ATTRIBUTE NextAttribute = &Attributes[0];
|
|
ULONG AttributeCount = 0;
|
|
BOOLEAN SecretsLocked = FALSE;
|
|
|
|
LsapDbAcquireLockEx( SecretObject, 0 );
|
|
SecretsLocked = TRUE;
|
|
|
|
//
|
|
// Create a new key for secret encryption
|
|
//
|
|
|
|
Status = LsapDbGenerateNewKey( &NewEncryptionKey );
|
|
|
|
if ( !NT_SUCCESS( Status )) {
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Setup the secret cipher key. This key will be used for writing
|
|
// secrets back to the database. The key used for reading will not
|
|
// be changed until all secrets are re-encrypted.
|
|
//
|
|
|
|
LsapDbInitializeSecretCipherKeyWrite( &NewEncryptionKey );
|
|
|
|
//
|
|
// Now iterate over all secrets on the machine, re-encrypting them
|
|
//
|
|
|
|
Status = LsapDbUpgradeSecretForKeyChange();
|
|
|
|
if ( !NT_SUCCESS( Status )) {
|
|
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Now substitute the key used for reading secrets, as they are all
|
|
// encrypted using the new key
|
|
//
|
|
|
|
LsapDbInitializeSecretCipherKeyRead( &NewEncryptionKey );
|
|
|
|
LsapDbReleaseLockEx( SecretObject, 0 );
|
|
SecretsLocked = FALSE;
|
|
|
|
//
|
|
// Encrypt the key with syskey
|
|
//
|
|
|
|
LsapDbEncryptKeyWithSyskey(
|
|
&NewEncryptionKey,
|
|
NewSysKey,
|
|
LSAP_SYSKEY_SIZE
|
|
);
|
|
|
|
//
|
|
// Now write out the new password encryption key
|
|
//
|
|
|
|
LsapDbInitializeAttribute(
|
|
NextAttribute,
|
|
&LsapDbNames[PolSecretEncryptionKey],
|
|
&NewEncryptionKey,
|
|
sizeof( NewEncryptionKey ),
|
|
FALSE
|
|
);
|
|
|
|
NextAttribute++;
|
|
AttributeCount++;
|
|
|
|
Status = LsapDbReferenceObject(
|
|
LsapDbHandle,
|
|
0,
|
|
PolicyObject,
|
|
PolicyObject,
|
|
LSAP_DB_LOCK | LSAP_DB_START_TRANSACTION
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
ASSERT( AttributeCount <= ( sizeof( Attributes ) / sizeof( LSAP_DB_ATTRIBUTE ) ) );
|
|
|
|
Status = LsapDbWriteAttributesObject(
|
|
LsapDbHandle,
|
|
Attributes,
|
|
AttributeCount
|
|
);
|
|
|
|
//
|
|
// No attributes are replicatable.
|
|
//
|
|
|
|
Status = LsapDbDereferenceObject(
|
|
&LsapDbHandle,
|
|
PolicyObject,
|
|
PolicyObject,
|
|
(LSAP_DB_LOCK |
|
|
LSAP_DB_FINISH_TRANSACTION |
|
|
LSAP_DB_OMIT_REPLICATOR_NOTIFICATION ),
|
|
SecurityDbChange,
|
|
Status
|
|
);
|
|
}
|
|
|
|
//
|
|
// ISSUE-markpu-2001/06/27
|
|
// If something went wrong and we could not write the password encryption key
|
|
// out, Should we attempt to revert all secrets back to their original state?
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
if ( SecretsLocked ) {
|
|
|
|
LsapDbReleaseLockEx( SecretObject, 0 );
|
|
}
|
|
|
|
return Status;
|
|
|
|
Error:
|
|
|
|
ASSERT( !NT_SUCCESS( Status ));
|
|
goto Cleanup;
|
|
}
|