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

Implement part of SslCertificateTrust #55104

Merged
merged 16 commits into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from 11 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
25 changes: 25 additions & 0 deletions src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ internal enum ContextAttribute
SECPKG_ATTR_LOCAL_CERT_CONTEXT = 0x54, // returns PCCERT_CONTEXT
SECPKG_ATTR_ROOT_STORE = 0x55, // returns HCERTCONTEXT to the root store
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_CIPHER_INFO = 0x64, // returns SecPkgContext_CipherInfo
SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo
Expand Down Expand Up @@ -315,6 +316,23 @@ public SecBufferDesc(int count)
}
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe struct SecPkgCred_ClientCertPolicy
{
public int dwFlags;
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public fixed byte guid[16];
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public int dwCertFlags;
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public int dwUrlRetrievalTimeout;
[MarshalAs(UnmanagedType.Bool)]
public bool fCheckRevocationFreshnessTime;
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public int dwRevocationFreshnessTime;
wfurt marked this conversation as resolved.
Show resolved Hide resolved
[MarshalAs(UnmanagedType.Bool)]
public bool fOmitUsageCheck;
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public char* pwszSslCtlStoreName;
[MarshalAs(UnmanagedType.LPWStr)]
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public char* pwszSslCtlIdentifier;
}

[DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, SetLastError = true)]
internal static extern int EncryptMessage(
ref CredHandle contextHandle,
Expand Down Expand Up @@ -472,5 +490,12 @@ internal static extern SECURITY_STATUS SspiEncodeStringsAsAuthIdentity(
[In] string domainName,
[In] string password,
[Out] out SafeSspiAuthDataHandle authData);

[DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern SECURITY_STATUS SetCredentialsAttributesW(
[In] ref CredHandle handlePtr,
[In] long ulAttribute,
[In] ref SecPkgCred_ClientCertPolicy pBuffer,
[In] long cbBuffer);
}
}
14 changes: 14 additions & 0 deletions src/libraries/System.Net.Security/ref/System.Net.Security.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,24 @@ public readonly struct SslClientHelloInfo
public static bool operator !=(System.Net.Security.SslApplicationProtocol left, System.Net.Security.SslApplicationProtocol right) { throw null; }
public override string ToString() { throw null; }
}

public sealed partial class SslCertificateTrust
{
public static SslCertificateTrust CreateForX509Store(
System.Security.Cryptography.X509Certificates.X509Store store,
bool sendTrustInHandshake = false) { throw null; }
[System.Runtime.Versioning.UnsupportedOSPlatform("windows")]
public static SslCertificateTrust CreateForX509Collection(
System.Security.Cryptography.X509Certificates.X509Certificate2Collection trustList,
bool sendTrustInHandshake = false) { throw null; }
}

public sealed partial class SslStreamCertificateContext
{
internal SslStreamCertificateContext() { throw null; }
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public static SslStreamCertificateContext Create(System.Security.Cryptography.X509Certificates.X509Certificate2 target, System.Security.Cryptography.X509Certificates.X509Certificate2Collection? additionalCertificates, bool offline = false) { throw null; }
wfurt marked this conversation as resolved.
Show resolved Hide resolved
public static SslStreamCertificateContext Create(System.Security.Cryptography.X509Certificates.X509Certificate2 target, System.Security.Cryptography.X509Certificates.X509Certificate2Collection? additionalCertificates, bool offline = false, SslCertificateTrust? trust = null) { throw null; }
}
public partial class SslClientAuthenticationOptions
{
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Net.Security/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -458,4 +458,10 @@
<data name="net_android_ssl_api_level_unsupported" xml:space="preserve">
<value>Setting an SNI hostname is not supported on this API level.</value>
</data>
<data name="net_ssl_trust_store" xml:space="preserve">
<value>Only LocalMachine stores are supported on Windows.</value>
</data>
<data name="net_ssl_trust_collection" xml:space="preserve">
<value>Sending trust from collection is not supported on Windows.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)</TargetFrameworks>
<!-- This is needed so that code for TlsCipherSuite will have no namespace (causes compile errors) when used within T4 template -->
<DefineConstants>$(DefineConstants);PRODUCT</DefineConstants>
<DefineConstants Condition="'$(TargetsWindows)' == 'true'">$(DefineConstants);TARGET_WINDOWS</DefineConstants>
wfurt marked this conversation as resolved.
Show resolved Hide resolved
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
Expand All @@ -29,6 +30,7 @@
<Compile Include="System\Net\Security\ProtectionLevel.cs" />
<Compile Include="System\Net\Security\SslApplicationProtocol.cs" />
<Compile Include="System\Net\Security\SslAuthenticationOptions.cs" />
<Compile Include="System\Net\Security\SslCertificateTrust.cs" />
<Compile Include="System\Net\Security\SslClientAuthenticationOptions.cs" />
<Compile Include="System\Net\Security\SslClientHelloInfo.cs" />
<Compile Include="System\Net\Security\SslServerAuthenticationOptions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ internal SecurityStatusPal Decrypt(Span<byte> buffer, out int outputOffset, out
--*/

//This method validates a remote certificate.
internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remoteCertValidationCallback, ref ProtocolToken? alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus)
internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remoteCertValidationCallback, SslCertificateTrust? trust, ref ProtocolToken? alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus)
{
sslPolicyErrors = SslPolicyErrors.None;
chainStatus = X509ChainStatusFlags.NoError;
Expand Down Expand Up @@ -965,6 +965,19 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
chain.ChainPolicy.ExtraStore.AddRange(remoteCertificateStore);
}

if (trust != null)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
if (trust._store != null)
{
chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates);
}
if (trust._trustList != null)
{
chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList);
}
}

sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties(
_securityContext!,
chain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthen
if (certificateWithKey != null && certificateWithKey.HasPrivateKey)
{
// given cert is X509Certificate2 with key. We can use it directly.
CertificateContext = SslStreamCertificateContext.Create(certificateWithKey, null);
CertificateContext = SslStreamCertificateContext.Create(certificateWithKey, null, false, null);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Versioning;
using System.Security.Cryptography.X509Certificates;

namespace System.Net.Security
{
public sealed partial class SslCertificateTrust
wfurt marked this conversation as resolved.
Show resolved Hide resolved
{
internal X509Store? _store;
internal X509Certificate2Collection? _trustList;
internal bool _sendTrustInHandshake;

public static SslCertificateTrust CreateForX509Store(
wfurt marked this conversation as resolved.
Show resolved Hide resolved
X509Store store,
bool sendTrustInHandshake = false)
{

#if TARGET_WINDOWS
wfurt marked this conversation as resolved.
Show resolved Hide resolved
if (sendTrustInHandshake && store.Location != StoreLocation.LocalMachine)
{
throw new PlatformNotSupportedException(SR.net_ssl_trust_store);
}
#else
if (sendTrustInHandshake)
{
// to be removed when implemented.
throw new PlatformNotSupportedException("Not supported yet.");
}
#endif
if (!store.IsOpen)
{
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
}

var trust = new SslCertificateTrust();
wfurt marked this conversation as resolved.
Show resolved Hide resolved
trust._store = store;
trust._sendTrustInHandshake = sendTrustInHandshake;
return trust;
}

[UnsupportedOSPlatform("windows")]
public static SslCertificateTrust CreateForX509Collection(
X509Certificate2Collection trustList,
bool sendTrustInHandshake = false)
{
if (sendTrustInHandshake)
{
// to be removed when implemented.
throw new PlatformNotSupportedException("Not supported yet.");
}

#if TARGET_WINDOWS
if (sendTrustInHandshake)
{
throw new PlatformNotSupportedException(SR.net_ssl_trust_collection);
}
#endif
var trust = new SslCertificateTrust();
trust._trustList = trustList;
trust._sendTrustInHandshake = sendTrustInHandshake;
return trust;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ private bool CompleteHandshake(ref ProtocolToken? alertToken, out SslPolicyError
return true;
}

if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, ref alertToken, out sslPolicyErrors, out chainStatus))
if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, _sslAuthenticationOptions!.CertificateContext?.Trust, ref alertToken, out sslPolicyErrors, out chainStatus))
{
_handshakeCompleted = false;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ public partial class SslStreamCertificateContext
{
private const bool TrimRootCertificate = true;

private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust)
{
Certificate = target;
IntermediateCertificates = intermediates;
Trust = trust;
}

internal static SslStreamCertificateContext Create(X509Certificate2 target) => Create(target, null);
internal static SslStreamCertificateContext Create(X509Certificate2 target) => Create(target, null, false, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ public partial class SslStreamCertificateContext
// No leaf, no root.
private const bool TrimRootCertificate = true;

private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust)
{
Certificate = target;
IntermediateCertificates = intermediates;
Trust = trust;
}

internal static SslStreamCertificateContext Create(X509Certificate2 target)
{
// On OSX we do not need to build chain unless we are asked for it.
return new SslStreamCertificateContext(target, Array.Empty<X509Certificate2>());
return new SslStreamCertificateContext(target, Array.Empty<X509Certificate2>(), null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal static SslStreamCertificateContext Create(X509Certificate2 target)
return new SslStreamCertificateContext(target, Array.Empty<X509Certificate2>());
}

private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust = null)
wfurt marked this conversation as resolved.
Show resolved Hide resolved
{
if (intermediates.Length > 0)
{
Expand Down Expand Up @@ -103,6 +103,7 @@ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[]

Certificate = target;
IntermediateCertificates = intermediates;
Trust = trust;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@ public partial class SslStreamCertificateContext
{
internal readonly X509Certificate2 Certificate;
internal readonly X509Certificate2[] IntermediateCertificates;
internal readonly SslCertificateTrust? Trust;

public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false)
wfurt marked this conversation as resolved.
Show resolved Hide resolved
{
return Create(target, additionalCertificates, offline, null);
}

public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false, SslCertificateTrust? trust = null)
{
if (!target.HasPrivateKey)
{
Expand Down Expand Up @@ -81,13 +87,12 @@ public static SslStreamCertificateContext Create(X509Certificate2 target, X509Ce
}
}

return new SslStreamCertificateContext(target, intermediates);
return new SslStreamCertificateContext(target, intermediates, trust);
}

internal SslStreamCertificateContext Duplicate()
{
return new SslStreamCertificateContext(new X509Certificate2(Certificate), IntermediateCertificates);

return new SslStreamCertificateContext(new X509Certificate2(Certificate), IntermediateCertificates, Trust);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;
using Microsoft.Win32.SafeHandles;

namespace System.Net.Security
Expand Down Expand Up @@ -121,14 +123,41 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials
public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
// New crypto API supports TLS1.3 but it does not allow to force NULL encryption.
return !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ?
SafeFreeCredentials cred = !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ?
AcquireCredentialsHandleSchannelCred(certificateContext?.Certificate, protocols, policy, isServer) :
AcquireCredentialsHandleSchCredentials(certificateContext?.Certificate, protocols, policy, isServer);
if (certificateContext != null && certificateContext.Trust != null && certificateContext.Trust._sendTrustInHandshake)
{
AttachCertificateStore(cred, certificateContext.Trust._store!);
}

return cred;
}

private static unsafe void AttachCertificateStore(SafeFreeCredentials cred, X509Store store)
{
Interop.SspiCli.SecPkgCred_ClientCertPolicy clientCertPolicy = default;
fixed (char* ptr = store.Name)
{
clientCertPolicy.pwszSslCtlStoreName = ptr;
Interop.SECURITY_STATUS errorCode = Interop.SspiCli.SetCredentialsAttributesW(
ref cred._handle,
(long)Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CLIENT_CERT_POLICY,
ref clientCertPolicy,
sizeof(Interop.SspiCli.SecPkgCred_ClientCertPolicy));

if (errorCode != Interop.SECURITY_STATUS.OK)
{
throw new Win32Exception((int)errorCode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we be throwing something other than Win32Exception here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I look at the code around Acquire Credentials and it seems like that what we throw. I can check as we may perhaps wrap it.

}
}

return;
}

// This is legacy crypto API used on .NET Framework and older Windows versions.
// It only supports TLS up to 1.2
public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate2? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer);
Interop.SspiCli.SCHANNEL_CRED.Flags flags;
Expand Down Expand Up @@ -174,12 +203,11 @@ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X5
}

// This function uses new crypto API to support TLS 1.3 and beyond.
public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate2? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer);
Interop.SspiCli.SCH_CREDENTIALS.Flags flags;
Interop.SspiCli.CredentialUse direction;

if (isServer)
{
direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND;
Expand Down Expand Up @@ -215,7 +243,6 @@ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(
Interop.SspiCli.SCH_CREDENTIALS credential = default;
credential.dwVersion = Interop.SspiCli.SCH_CREDENTIALS.CurrentVersion;
credential.dwFlags = flags;

Interop.Crypt32.CERT_CONTEXT *certificateHandle = null;
if (certificate != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private async Task DoHandshakeWithOptions(SslStream clientSslStream, SslStream s
{
clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate;
clientOptions.TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false);
serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, null);
serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, null, false, null);

Task t1 = clientSslStream.AuthenticateAsClientAsync(TestAuthenticateAsync, clientOptions);
Task t2 = serverSslStream.AuthenticateAsServerAsync(TestAuthenticateAsync, serverOptions);
Expand Down
Loading