0

Background:

  • Computer mycomputer is running Windows 10 and is joined to domain mydomain.com.
  • A user is logged with local account mycomputer\localuser on mycomputer.
  • The user also knows the password of domain account mydomain\domainuser.
  • The service principal name myprotocol/domainuser is registered in Active Directory and maps to domain account mydomain\domainuser.
  • Local user mycomputer\localuser is not allowed to start a process as mydomain\domainuser.

The user wants to launch a server process under the local account which would then use the domain account to authenticate incoming connections with Kerberos.

I want to write the code of that server.

Client code:

The client code is straightforward and consist of a call to AcquireCredentialsHandle followed by a call to InitializeSecurityContext:

AcquireCredentialsHandle(
    nullptr,
    "Kerberos",
    SECPKG_CRED_OUTBOUND,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    &credentials,
    &lifetime);
InitializeSecurityContext(
    &credentials,
    nullptr,
    "myprotocol/myport",
    ISC_REQ_CONFIDENTIALITY,
    0,
    SECURITY_NATIVE_DREP,
    nullptr,
    0,
    &securityContext,
    &outBufferArray,
    &contextAttributes,
    &lifetime);

Note the simplified usage of strings in the code snippets. The reality which have to deal with wchar_t and const correctness is somewhat uglier.

Also note that this code works when launched by a local user if appropriate credentials are stored in Control Panel's Credential Manager - i.e. with hostname domainuser (sic.)

Server code:

I already have a code which works when the process is launched by mydomain\domainuser:

AcquireCredentialsHandle(
    nullptr,
    "Kerberos",
    SECPKG_CRED_INBOUND,
    nullptr,
    nullptr,
    nullptr,
    nullptr,
    &credentials,
    &lifetime);
AcceptSecurityContext(
    &credentials,
    nullptr,
    &inBufferArray,
    attribs,
    SECURITY_NATIVE_DREP,
    &securityContext,
    nullptr,
    &attribs,
    &lifetime);

But when the server is launched by mycomputer\localuser, the call to AcquireCredentialsHandle fails with code SEC_E_NO_CREDENTIALS.

  • I tried modifying the first argument of that call to "myprotocol/domainuser", "domainuser", "mydomain\domainuser" or even "domainuser@mydomain.com".
  • I tried adding the required credentials in Control Panel's Credential Manager using hostname mycomputer and even domainuser.

What can I do to acquire the credential handle of mydomain\domainuser in a process launched by mycomputer\localuser ?

Compiling code snippet:

#include <string>

#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define SECURITY_WIN32
#include <sspi.h>//Requires linking on Secur32.lib

int main(){
    CredHandle credentials;
    TimeStamp lifetime;
    std::string package="Kerberos";
    std::string principal="myprotocol/domainuser";
    auto res=AcquireCredentialsHandle(
        principal.data(),
        package.data(),
        SECPKG_CRED_INBOUND,
        nullptr,
        nullptr,
        nullptr,
        nullptr,
        &credentials,
        &lifetime);
    if(res==SEC_E_OK){
        std::printf("Success\n");
        FreeCredentialsHandle(&credentials);
        return 0;}
    else{
        std::printf("Failure\n");
        return res;}}
Arnaud
  • 3,765
  • 3
  • 39
  • 69
  • How are you setting credentials? That is almost always going to be the cause of SEC_E_NO_CREDENTIALS. It's also a bit strange that ACH is returning that. Usually it's ASC or ISC that returns it, since ACH doesn't do a lot under the covers. – Steve Sep 29 '20 at 15:04
  • @Steve I am not sure I understand your question. The user logs into Windows using his local account and then he runs the server code which executes the code snippet above. `AcquireCredentialsHandle` is supposed to initialize `credentials` but it fails because of `SECPKG_CRED_INBOUND`. On the client side, the same call with `SECPKG_CRED_OUTBOUND` works perfectly. – Arnaud Sep 29 '20 at 15:42
  • Sorry, was doing this from memory and misremembered the parameters. You're passing null authdata (param 5) on the outbound ACH call, so it has to rely on the default local creds. However, local user credentials don't support SSO in this way because it has no knowledge of what realm you're in. – Steve Sep 29 '20 at 19:08
  • 1
    Check [this sample](https://learn.microsoft.com/en-us/windows/win32/secauthn/obtaining-alternate-digest-credentials) to obtain credentials for a specific user account. – Drake Wu Sep 30 '20 at 02:26
  • 1
    @DrakeWu-MSFT It works. Please write an answer with the link. I will upvote. – Arnaud Sep 30 '20 at 09:14

1 Answers1

2

To obtain credentials other than those associated with the current logon session, populate a SEC_WINNT_AUTH_IDENTITY structure with information for the alternate security principal. Pass the structure to the AcquireCredentialsHandle function using the pAuthData parameter.

And this micrsoft example demonstrates a client-side call to obtain Digest credentials for a specific user account:

#include <windows.h>

#ifdef UNICODE
  ClientAuthID.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
#else
  ClientAuthID.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
#endif

void main()
{
    SECURITY_STATUS SecStatus; 
    TimeStamp tsLifetime; 
    CredHandle hCred;
    SEC_WINNT_AUTH_IDENTITY ClientAuthID;
    LPTSTR UserName = TEXT("ASecurityPrinciple");
    LPTSTR DomainName = TEXT("AnAuthenticatingDomain");

    // Initialize the memory.
    ZeroMemory( &ClientAuthID, sizeof(ClientAuthID) );

    // Specify string format for the ClientAuthID structure.


    // Specify an alternate user, domain and password.
      ClientAuthID.User = (unsigned char *) UserName;
      ClientAuthID.UserLength = _tcslen(UserName);

      ClientAuthID.Domain = (unsigned char *) DomainName;
      ClientAuthID.DomainLength = _tcslen(DomainName);

    // Password is an application-defined LPTSTR variable
    // containing the user password.
      ClientAuthID.Password = Password;
      ClientAuthID.PasswordLength = _tcslen(Password);

    // Get the client side credential handle.
    SecStatus = AcquireCredentialsHandle (
      NULL,                  // Default principal.
      WDIGEST_SP_NAME,       // The Digest SSP. 
      SECPKG_CRED_OUTBOUND,  // Client will use the credentials.
      NULL,                  // Do not specify LOGON id.
      &ClientAuthID,         // User information.
      NULL,                  // Not used with Digest SSP.
      NULL,                  // Not used with Digest SSP.
      &hCred,                // Receives the credential handle.
      &tsLifetime            // Receives the credential time limit.
    );
}
Drake Wu
  • 6,927
  • 1
  • 7
  • 30