Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/6.0] fix IsMutuallyAuthenticated on SslStream #92684

Merged
merged 9 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ internal static void SSLStreamSetTargetHost(
throw new SslException();
}

[DllImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamIsLocalCertificateUsed")]
[return: MarshalAs(UnmanagedType.U1)]
internal static extern bool SSLStreamIsLocalCertificateUsed(SafeSslHandle sslHandle);

[DllImport(Interop.Libraries.AndroidCryptoNative, EntryPoint = "AndroidCryptoNative_SSLStreamRequestClientAuthentication")]
internal static extern void SSLStreamRequestClientAuthentication(SafeSslHandle sslHandle);

Expand Down
19 changes: 18 additions & 1 deletion src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ internal enum ContextAttribute
SECPKG_ATTR_ISSUER_LIST_EX = 0x59, // returns SecPkgContext_IssuerListInfoEx
SECPKG_ATTR_CLIENT_CERT_POLICY = 0x60, // sets SecPkgCred_ClientCertCtlPolicy
SECPKG_ATTR_CONNECTION_INFO = 0x5A, // returns SecPkgContext_ConnectionInfo
SECPKG_ATTR_SESSION_INFO = 0x5D, // sets SecPkgContext_SessionInfo
SECPKG_ATTR_CIPHER_INFO = 0x64, // returns SecPkgContext_CipherInfo
SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo
SECPKG_ATTR_REMOTE_CERT_CHAIN = 0x67, // returns PCCERT_CONTEXT
SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo
}

// These values are defined within sspi.h as ISC_REQ_*, ISC_RET_*, ASC_REQ_* and ASC_RET_*.
Expand Down Expand Up @@ -330,6 +332,21 @@ internal unsafe struct SecPkgCred_ClientCertPolicy
public char* pwszSslCtlIdentifier;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct SecPkgContext_SessionInfo
{
public uint dwFlags;
public uint cbSessionId;
public fixed byte rgbSessionId[32];

[Flags]
public enum Flags
{
Zero = 0,
SSL_SESSION_RECONNECT = 0x01,
};
}

[DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, SetLastError = true)]
internal static extern int EncryptMessage(
ref CredHandle contextHandle,
Expand Down
26 changes: 20 additions & 6 deletions src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,27 +426,41 @@ public static bool QueryBlittableContextAttributes<T>(ISSPIInterface secModule,
}
}

public static SafeFreeCertContext? QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext)
private static bool QueryCertContextAttribute(ISSPIInterface secModule, SafeDeleteContext securityContext, Interop.SspiCli.ContextAttribute attribute, out SafeFreeCertContext? certContext)
{
Span<IntPtr> buffer = stackalloc IntPtr[1];
int errorCode = secModule.QueryContextAttributes(
securityContext,
Interop.SspiCli.ContextAttribute.SECPKG_ATTR_REMOTE_CERT_CONTEXT,
attribute,
MemoryMarshal.AsBytes(buffer),
typeof(SafeFreeCertContext),
out SafeHandle? sspiHandle);

if (errorCode != 0)
// certificate is not always present (e.g. on server when querying client certificate)
// but we still want to consider such case as a success.
bool success = errorCode == 0 || errorCode == (int)Interop.SECURITY_STATUS.NoCredentials;

if (!success)
{
sspiHandle?.Dispose();
sspiHandle = null;
if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, $"ERROR = {ErrorDescription(errorCode)}");
return null;
}

var result = (SafeFreeCertContext)sspiHandle!;
return result;
certContext = sspiHandle as SafeFreeCertContext;
return success;
}

public static bool QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext)
=> QueryCertContextAttribute(secModule, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_REMOTE_CERT_CONTEXT, out certContext);

public static bool QueryContextAttributes_SECPKG_ATTR_LOCAL_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext)
=> QueryCertContextAttribute(secModule, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_LOCAL_CERT_CONTEXT, out certContext);

public static bool QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CHAIN(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext)
=> QueryCertContextAttribute(secModule, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_REMOTE_CERT_CHAIN, out certContext);
wfurt marked this conversation as resolved.
Show resolved Hide resolved


public static bool QueryContextAttributes_SECPKG_ATTR_ISSUER_LIST_EX(ISSPIInterface secModule, SafeDeleteContext securityContext, ref Interop.SspiCli.SecPkgContext_IssuerListInfoEx ctx, out SafeHandle? sspiHandle)
{
Span<Interop.SspiCli.SecPkgContext_IssuerListInfoEx> buffer =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32.SafeHandles;

namespace System.Net.Security
Expand Down Expand Up @@ -320,10 +321,15 @@ public static unsafe int AcquireCredentialsHandle(

internal sealed class SafeFreeCredential_SECURITY : SafeFreeCredentials
{
#pragma warning disable 0649
// This is used only by SslStream but it is included elsewhere
public X509Certificate? LocalCertificate;
#pragma warning restore 0649
public SafeFreeCredential_SECURITY() : base() { }

protected override bool ReleaseHandle()
{
LocalCertificate?.Dispose();
wfurt marked this conversation as resolved.
Show resolved Hide resolved
return Interop.SspiCli.FreeCredentialsHandle(ref _handle) == 0;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ internal static SslPolicyErrors VerifyCertificateProperties(
return cert;
}

// Check if the local certificate has been sent to the peer during the handshake.
internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _, SafeDeleteContext? securityContext)
{
SafeSslHandle? sslContext = ((SafeDeleteSslContext?)securityContext)?.SslContext;
if (sslContext == null)
return false;

return Interop.AndroidCrypto.SSLStreamIsLocalCertificateUsed(sslContext);
}

//
// Used only by client SSL code, never returns null.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ internal static SslPolicyErrors VerifyCertificateProperties(
return result;
}

// This is only called when we selected local client certificate.
// Currently this is only when Apple crypto asked for it.
internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _1, SafeDeleteContext? _2) => true;


//
// Used only by client SSL code, never returns null.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ internal static SslPolicyErrors VerifyCertificateProperties(
return result;
}


// This is only called when we selected local client certificate.
// Currently this is only when OpenSSL needs it because peer asked.
internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _1, SafeDeleteContext? _2) => true;

//
// Used only by client SSL code, never returns null.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using static Interop.SspiCli;

namespace System.Net
{
Expand Down Expand Up @@ -48,7 +49,7 @@ internal static SslPolicyErrors VerifyCertificateProperties(
SafeFreeCertContext? remoteContext = null;
try
{
remoteContext = SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext);
SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext, out remoteContext);
if (remoteContext != null && !remoteContext.IsInvalid)
{
result = new X509Certificate2(remoteContext.DangerousGetHandle());
Expand All @@ -71,6 +72,43 @@ internal static SslPolicyErrors VerifyCertificateProperties(
return result;
}

// Check that local certificate was used by schannel.
internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _credentialsHandle, SafeDeleteContext securityContext)
{
SecPkgContext_SessionInfo info = default;
// fails on Server 2008 and older. We will fall-back to probing LOCAL_CERT_CONTEXT in that case.
if (SSPIWrapper.QueryBlittableContextAttributes(
GlobalSSPI.SSPISecureChannel,
securityContext,
Interop.SspiCli.ContextAttribute.SECPKG_ATTR_SESSION_INFO,
ref info) &&
((SecPkgContext_SessionInfo.Flags)info.dwFlags).HasFlag(SecPkgContext_SessionInfo.Flags.SSL_SESSION_RECONNECT))
{
// This is TLS Resumed session. Windows can fail to query the local cert bellow.
// Instead, we will determine the usage form used credentials.
SafeFreeCredential_SECURITY creds = (SafeFreeCredential_SECURITY)_credentialsHandle!;
return creds.LocalCertificate != null;
}

SafeFreeCertContext? localContext = null;
try
{
if (SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_LOCAL_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext, out localContext) &&
localContext != null)
{
return !localContext.IsInvalid;
}
}
finally
{
localContext?.Dispose();
}

// Some older Windows do not support that. This is only called when client certificate was provided
// so assume it was for a reason.
return true;
}

//
// Used only by client SSL code, never returns null.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ internal X509Certificate? LocalClientCertificate
{
get
{
return _selectedClientCertificate;
if (_selectedClientCertificate != null && CertificateValidationPal.IsLocalCertificateUsed(_credentialsHandle, _securityContext!))
{
return _selectedClientCertificate;
}

return null;
}
}

Expand Down Expand Up @@ -332,7 +337,7 @@ This will not restart a session but helps minimizing the number of handles we cr

--*/

private bool AcquireClientCredentials(ref byte[]? thumbPrint)
private bool AcquireClientCredentials(ref byte[]? thumbPrint, bool newCredentialsRequested = false)
{
// Acquire possible Client Certificate information and set it on the handle.
X509Certificate? clientCertificate = null; // This is a candidate that can come from the user callback or be guessed when targeting a session restart.
Expand Down Expand Up @@ -604,7 +609,7 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint)
}

_credentialsHandle = SslStreamPal.AcquireCredentialsHandle(_sslAuthenticationOptions.CertificateContext,
_sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer);
_sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer, newCredentialsRequested);

thumbPrint = guessedThumbPrint; // Delay until here in case something above threw.
_selectedClientCertificate = clientCertificate;
Expand Down Expand Up @@ -711,7 +716,7 @@ private bool AcquireServerCredentials(ref byte[]? thumbPrint)
else
{
_credentialsHandle = SslStreamPal.AcquireCredentialsHandle(_sslAuthenticationOptions.CertificateContext, _sslAuthenticationOptions.EnabledSslProtocols,
_sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer);
_sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer, newCredentialsRequested: false);
thumbPrint = guessedThumbPrint;
}

Expand Down Expand Up @@ -807,6 +812,23 @@ private SecurityStatusPal GenerateToken(ReadOnlySpan<byte> inputBuffer, ref byte
inputBuffer,
ref result,
_sslAuthenticationOptions);

if (status.ErrorCode == SecurityStatusPalErrorCode.CredentialsNeeded)
{
_refreshCredentialNeeded = true;
cachedCreds = AcquireClientCredentials(ref thumbPrint, newCredentialsRequested: true);

if (NetEventSource.Log.IsEnabled())
NetEventSource.Info(this, "InitializeSecurityContext() returned 'CredentialsNeeded'.");

status = SslStreamPal.InitializeSecurityContext(
ref _credentialsHandle!,
ref _securityContext,
_sslAuthenticationOptions.TargetHost,
inputBuffer,
ref result,
_sslAuthenticationOptions);
}
}
} while (cachedCreds && _credentialsHandle == null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public static SafeFreeCredentials AcquireCredentialsHandle(
SslStreamCertificateContext? certificateContext,
SslProtocols protocols,
EncryptionPolicy policy,
bool isServer)
bool isServer,
bool newCredentialsRequested = false)
{
return new SafeFreeSslCredentials(certificateContext, protocols, policy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public static SafeFreeCredentials AcquireCredentialsHandle(
SslStreamCertificateContext? certificateContext,
SslProtocols protocols,
EncryptionPolicy policy,
bool isServer)
bool isServer,
bool newCredentialsRequested = false)
{
return new SafeFreeSslCredentials(certificateContext, protocols, policy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static SecurityStatusPal InitializeSecurityContext(ref SafeFreeCredential
}

public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext,
SslProtocols protocols, EncryptionPolicy policy, bool isServer)
SslProtocols protocols, EncryptionPolicy policy, bool isServer, bool newCredentialsRequested = false)
{
return new SafeFreeSslCredentials(certificateContext?.Certificate, protocols, policy);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials
return status;
}

public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer, bool newCredentialsRequested)
{
try
{
Expand All @@ -133,6 +133,16 @@ public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateC
AttachCertificateStore(cred, certificateContext.Trust._store!);
}

// Windows can fail to get local credentials in case of TLS Resume.
// We will store associated certificate in credentials and use it in case
// of TLS resume. It will be disposed when the credentials are.
if (newCredentialsRequested && certificateContext != null)
{
SafeFreeCredential_SECURITY handle = (SafeFreeCredential_SECURITY)cred;
// We need to create copy to avoid Disposal issue.
handle.LocalCertificate = new X509Certificate2(certificateContext.Certificate);
}

return cred;
}
catch (Win32Exception e)
Expand Down
Loading
Loading