Skip to content

[API Proposal]: Expose a high level authentication API #69920

@wfurt

Description

@wfurt

Background and motivation

runtime already have very similar implementation - used primarily by SocketsHttphandler and partially by SMTP.
Kestrel currently use this internal API via Reflection (#29270). Additionally, we would like to have similar authentication in Android handler that lives outside of runtime repo.

Tha main goal is to expose existing functionality via public API so other components can leverage it as well.

API Proposal

namespace System.Net.Security
{
 public class NegotiateAuthenticationClientOptions
 {
     // Specifies the GSSAPI authentication package used for the authentication.
     // Common values are Negotiate, NTLM or Kerberos. Default value is Negotiate.
     public string Package { get; set; }

     // The NetworkCredential that is used to establish the identity of the client.
     // Default value is CrendentialCache.DefaultCredential.
     public NetworkCredential Credential { get; set; }

     // The Service Principal Name (SPN) that uniquely identifies the server to authenticate.
     public string? TargetName { get; set; }

     // The ChannelBinding that is used for extended protection.
     public ChannelBinding? Binding { get; set; }

     // Indicates the requires level of protection of the authentication exchange
     // and any further data exchange.
     // Valid values: None, Sign, EncryptAndSign
     // Default value: None
     System.Net.Security.ProtectionLevel RequiredProtectionLevel { get; set; }
 }

 public class NegotiateAuthenticationServerOptions
 {
    // Specifies the GSSAPI authentication package used for the authentication.
    // Common values are Negotiate, NTLM or Kerberos. Default value is Negotiate.
    public string Package { get; set; }

    // The NetworkCredential that is used to establish the identity of the client.
    // Default value is CrendentialCache.DefaultCredential.
    // Note: I'm not quite sure of the meaning of this on the server side but
    // it exists on GSSAPI side.
    public NetworkCredential Credential { get; set; }

    // The ChannelBinding that is used for extended protection.
    public ChannelBinding? Binding { get; set; }

    // Indicates the requires level of protection of the authentication exchange
    // and any further data exchange.
    // Valid values: None, Sign, EncryptAndSign
    // Default value: None
    System.Net.Security.ProtectionLevel RequiredProtectionLevel { get; set; }
 }

 public class NegotiateAuthentication : IDisposable
 {
    // Create client-side authentication
    public NegotiateAuthentication(NegotiateAuthenticationClientOptions clientOptions);

    // Create server-side authentication
    public NegotiateAuthentication(NegotiateAuthenticationServerOptions serverOptions);

    // Indicates whether the initial authentication finished.
    // NOTE: Original it was named IsCompleted in the proposal but I renamed
    // it to match the property on NegotiateStream.
    public bool IsAuthenticated { get; set; }

    // Indicates negotiated protection level (can be higher than the required one).
    // Returns None if authentication was not finished yet.
    public System.Net.Security.ProtectionLevel ProtectionLevel { get; set; }

    // Indicates whether signing was negotiated.
    // Returns false if authentication was not finished yet.
    public bool IsSigned { get; set; }

    // Indicates whether signing was negotiated.
    // Returns false if authentication was not finished yet.
    public bool IsEncrypted { get; set; }

    // Indicates whether the client and server are mutually authenticated.
    // Returns false if authentication was not finished yet.
    public bool IsMutuallyAuthenticated { get; set; }

    // Indicates whether this is server-side authentication context.
    // Returns value based on the constructor used, provided for parity with
    // NegoatiateStream.
    public bool IsServer { get; set; }

    // The GSSAPI package name that was used for the authentiation. Initially it's
    // the name specified in the options in constructor. After successful authentication
    // it should return the actual mechanism used. For example, if Negotiate is
    // specified as the requested GSSAPI package this may return NTLM or Kerberos once
    // authentication exchange is complete (eg. IsAuthenticated == true).
    //
    // NOTE: This is partly duplicate with RemoteIdentity so it may not be necessary
    // on the public API.
    public string Package { get; }

    // For server context it returns the SPN target name of the client after successful
    // authentication. For client context it returns the target name specified in the
    // constructor options.
    public string? TargetName { get; }

    // Gets information about the identity of the remote party.
    //
    // When accessed by the client, this property returns a GenericIdentity containing
    // the Service Principal Name (SPN) of the server and the authentication protocol used.
    //
    // Throws InvalidOperationException if authentication was not finished yet
    // (IsAuthenticated == false) for server-side.
    public System.Security.Principal.IIdentity RemoteIdentity { get };

    public byte[] GetOutgoingBlob(ReadOnlySpan<byte> incomingBlob, out NegotiateAuthenticationStatusCode statusCode);

    // Base64 version of GetOutgoingBlob
    public string GetOutgoingBlob(string incomingBlob, out NegotiateAuthenticationStatusCode statusCode);

    // TODO (APIs not necessary for HTTP authentication but necessary for high-level protocols
    // like SASL and NegotiateStream):
    // Wrap, Unwrap as replacement for Encrypt, Decrypt, MakeSignature and VerifySignature
    // GetMIC and VerifyMIC if we find a use for that
 }

 // NOTE: Mirrors SecurityStatusPalErrorCode at the moment but it should mostly map to GSSAPI error
 // codes.
 public enum NegotiateAuthenticationStatusCode
 {
    NotSet = 0,
    OK,
    ContinueNeeded,
    CompleteNeeded,
    CompleteAndContinue,
    ContextExpired,
    CredentialsNeeded,
    Renegotiate,
    TryAgain,

    // Errors
    OutOfMemory,
    InvalidHandle,
    Unsupported,
    TargetUnknown,
    InternalError,
    PackageNotFound,
    NotOwner,
    CannotInstall,
    InvalidToken,
    CannotPack,
    QopNotSupported,
    NoImpersonation,
    LogonDenied,
    UnknownCredentials,
    NoCredentials,
    MessageAltered,
    OutOfSequence,
    NoAuthenticatingAuthority,
    IncompleteMessage,
    IncompleteCredentials,
    BufferNotEnough,
    WrongPrincipal,
    TimeSkew,
    UntrustedRoot,
    IllegalMessage,
    CertUnknown,
    CertExpired,
    DecryptFailure,
    AlgorithmMismatch,
    SecurityQosFailed,
    SmartcardLogonRequired,
    UnsupportedPreauth,
    BadBinding,
    DowngradeDetected,
    ApplicationProtocolMismatch,
    NoRenegotiation
 }
}

API Usage

var auth = new NegotiateAuthentication(new NegotiateAuthenticationClientOptions("NTLM", testCredential "HTTP/foo"));
NegotiateAuthenticationStatusCode statusCode;

byte[]? negotiateBlob = ntAuth.GetOutgoingBlob(ReadOnlySpan<byte>.Empty, out NegotiateAuthenticationStatusCode statusCode);
sendBlob(negotiateBlob);
do
{
  buffer = receiveBlob();
  byte[]? negotiateBlob = ntAuth.GetOutgoingBlob(buffer, out statusCode);
} while (statusCode == NegotiateAuthenticationStatusCode .ContinueNeeded);

if (statusCode == NegotiateAuthenticationStatusCode.OK)
{
 ....
}
else
{
...
}

Alternative Designs

No response

Risks

This API currently works well for bot client and server side of HTTP. We really have weak test coverage for SMTP and other protocols.

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Net.SecurityblockingMarks issues that we want to fast track in order to unblock other important work

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions