Background
The Windows SSPI API is an interface into the Windows security service that allows you to authenticate clients and servers to each other. One of the major uses of the API is to provide Windows Integrated Authentication aka Single Sign-on - applications are able to automatically authenticate users to servers by using the user's credentials from when they logged on.
The normal flow for this process:
- The user logs onto their computer, usually using a username and password.
- The user runs an application that uses SSPI to authenticate the user to a service.
- That application calls
AcquireCredentialsHandle()
to get a handle to the existing credentials the user created when they logged on. - The application uses the handle returned by
AcquireCredentialsHandle()
to participate in the SSPI authentication cycle with a server, using theInitializeSecurityContext()
API. - The application and the server exchange opaque arrays of bytes ('tokens') using the
InitializeSecurityContext()
(client side) andAcceptSecurityContext()
(server side) APIs to verify each others' tokens and continue the authentication cycle. - The authentication cycle completes at some point when enough tokens have been passed back and forth.
- The application has authenticated itself on the user's behalf to the server. Authentication is complete.
This is the normal flow for this process when using the API as part of single sign-on.
The signature for AcquireCredentialsHandle()
is:
SECURITY_STATUS SEC_Entry AcquireCredentialsHandle(
_In_ SEC_CHAR *pszPrincipal,
_In_ SEC_CHAR *pszPackage,
_In_ ULONG fCredentialUse,
_In_ PLUID pvLogonID,
_In_ PVOID pAuthData,
_In_ SEC_GET_KEY_FN pGetKeyFn,
_In_ PVOID pvGetKeyArgument,
_Out_ PCredHandle phCredential,
_Out_ PTimeStamp ptsExpiry
);
When used in the typical Windows Integrated Authentication case as above, the pAuthData
parameter is usually not provided - a null reference is provided instead.
Problem
I wish to be able to use AcquireCredentialsHandle()
in a manner where I provide a username and password to it directly. It would seem to handle this, since the pAuthData
parameter (which was null above) can be a reference to the SEC_WINNT_AUTH_IDENTITY
structure, which allows you to specify a username and password outright.
I've attempted to invoke AcquireCredentialsHandle()
this way, providing it a filled out SEC_WINNT_AUTH_IDENTITY
structure with my username and password. However, every time I invoke it, I get back success, even if I use made-up usernames or passwords. As a sanity check, I tried invoking LogonUser()
with the same credentials and they either worked or failed, as expected, depending on whether I gave it a valid username/password combo.
What am I doing wrong? Why does AcquireCredentialsHandle()
always return success even with absolutely incorrect credentials?
Below shows the basics of the code I'm using to invoke AcquireCredentialsHandle()
:
public class SuppliedCredential : Credential
{
public SuppliedCredential( string securityPackage, CredentialUse use, string username, string domain, string password ) :
base( securityPackage )
{
Init( use, username, domain, password );
}
private void Init( CredentialUse use, string username, string domain, string password )
{
// Copy off for the call, since this.SecurityPackage is a property.
string packageName = this.SecurityPackage;
TimeStamp rawExpiry = new TimeStamp();
SecurityStatus status = SecurityStatus.InternalError;
AuthIdentity auth = new AuthIdentity();
auth.User = username;
auth.UserLength = username.Length;
auth.Domain = domain;
auth.DomainLength = domain.Length;
auth.Password = password;
auth.PasswordLength = password.Length;
auth.Flags = 0x2; // unicode
this.Handle = new SafeCredentialHandle();
RuntimeHelpers.PrepareConstrainedRegions();
try { }
finally
{
status = CredentialNativeMethods.AcquireCredentialsHandle_2(
"",
packageName,
use,
IntPtr.Zero,
ref auth,
IntPtr.Zero,
IntPtr.Zero,
ref this.Handle.rawHandle,
ref rawExpiry
);
}
if( status != SecurityStatus.OK )
{
throw new SSPIException( "Failed to call AcquireCredentialHandle", status );
}
this.Expiry = rawExpiry.ToDateTime();
}
}
...
[StructLayout( LayoutKind.Sequential )]
public struct AuthIdentity
{
[MarshalAs(UnmanagedType.LPWStr)]
public string User;
public int UserLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string Domain;
public int DomainLength;
[MarshalAs(UnmanagedType.LPWStr)]
public string Password;
public int PasswordLength;
public int Flags;
};
...
[ReliabilityContract( Consistency.WillNotCorruptState, Cer.MayFail )]
[DllImport( "Secur32.dll", EntryPoint = "AcquireCredentialsHandle", CharSet = CharSet.Unicode )]
internal static extern SecurityStatus AcquireCredentialsHandle_2(
string principleName,
string packageName,
CredentialUse credentialUse,
IntPtr loginId,
ref AuthIdentity packageData,
IntPtr getKeyFunc,
IntPtr getKeyData,
ref RawSspiHandle credentialHandle,
ref TimeStamp expiry
);