Skip to content

Commit 3e5517b

Browse files
authored
refactor SslStream internals (#68678)
* refactor SslStream internals * fix validation and certs * update fakes * feedback from review
1 parent 5ecaae9 commit 3e5517b

17 files changed

Lines changed: 347 additions & 624 deletions

src/libraries/System.Net.Security/src/System.Net.Security.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
<Compile Include="System\Net\Security\SslClientAuthenticationOptions.cs" />
3333
<Compile Include="System\Net\Security\SslClientHelloInfo.cs" />
3434
<Compile Include="System\Net\Security\SslServerAuthenticationOptions.cs" />
35-
<Compile Include="System\Net\Security\SecureChannel.cs" />
3635
<Compile Include="System\Net\Security\SslSessionsCache.cs" />
3736
<Compile Include="System\Net\Security\SslStream.cs" />
38-
<Compile Include="System\Net\Security\SslStream.Implementation.cs" />
37+
<Compile Include="System\Net\Security\SslStream.IO.cs" />
38+
<Compile Include="System\Net\Security\SslStream.Protocol.cs" />
3939
<Compile Include="System\Net\Security\SslStreamCertificateContext.cs" />
4040
<Compile Include="System\Net\Security\SslConnectionInfo.cs" />
4141
<Compile Include="System\Net\Security\StreamSizes.cs" />

src/libraries/System.Net.Security/src/System/Net/Security/NetEventSource.Security.cs

Lines changed: 65 additions & 79 deletions
Large diffs are not rendered by default.

src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,39 @@ namespace System.Net.Security
1010
{
1111
internal sealed class SslAuthenticationOptions
1212
{
13-
internal SslAuthenticationOptions(SslClientAuthenticationOptions sslClientAuthenticationOptions, RemoteCertificateValidationCallback? remoteCallback, LocalCertSelectionCallback? localCallback)
13+
internal SslAuthenticationOptions()
14+
{
15+
TargetHost = string.Empty;
16+
}
17+
18+
internal void UpdateOptions(SslClientAuthenticationOptions sslClientAuthenticationOptions)
1419
{
1520
Debug.Assert(sslClientAuthenticationOptions.TargetHost != null);
1621

22+
if (CertValidationDelegate == null)
23+
{
24+
CertValidationDelegate = sslClientAuthenticationOptions.RemoteCertificateValidationCallback;
25+
}
26+
else if (sslClientAuthenticationOptions.RemoteCertificateValidationCallback != null &&
27+
CertValidationDelegate != sslClientAuthenticationOptions.RemoteCertificateValidationCallback)
28+
{
29+
// Callback was set in constructor to differet value.
30+
throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(RemoteCertificateValidationCallback)));
31+
}
32+
33+
if (CertSelectionDelegate == null)
34+
{
35+
CertSelectionDelegate = sslClientAuthenticationOptions.LocalCertificateSelectionCallback;
36+
}
37+
else if (sslClientAuthenticationOptions.LocalCertificateSelectionCallback != null &&
38+
CertSelectionDelegate != sslClientAuthenticationOptions.LocalCertificateSelectionCallback)
39+
{
40+
throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(LocalCertificateSelectionCallback)));
41+
}
42+
1743
// Common options.
1844
AllowRenegotiation = sslClientAuthenticationOptions.AllowRenegotiation;
1945
ApplicationProtocols = sslClientAuthenticationOptions.ApplicationProtocols;
20-
CertValidationDelegate = remoteCallback;
2146
CheckCertName = true;
2247
EnabledSslProtocols = FilterOutIncompatibleSslProtocols(sslClientAuthenticationOptions.EnabledSslProtocols);
2348
EncryptionPolicy = sslClientAuthenticationOptions.EncryptionPolicy;
@@ -27,32 +52,57 @@ internal SslAuthenticationOptions(SslClientAuthenticationOptions sslClientAuthen
2752
TargetHost = sslClientAuthenticationOptions.TargetHost.TrimEnd('.');
2853

2954
// Client specific options.
30-
CertSelectionDelegate = localCallback;
3155
CertificateRevocationCheckMode = sslClientAuthenticationOptions.CertificateRevocationCheckMode;
3256
ClientCertificates = sslClientAuthenticationOptions.ClientCertificates;
3357
CipherSuitesPolicy = sslClientAuthenticationOptions.CipherSuitesPolicy;
3458
}
3559

36-
internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions)
60+
internal void UpdateOptions(ServerOptionsSelectionCallback optionCallback, object? state)
3761
{
38-
// Common options.
39-
AllowRenegotiation = sslServerAuthenticationOptions.AllowRenegotiation;
40-
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols;
4162
CheckCertName = false;
42-
EnabledSslProtocols = FilterOutIncompatibleSslProtocols(sslServerAuthenticationOptions.EnabledSslProtocols);
43-
EncryptionPolicy = sslServerAuthenticationOptions.EncryptionPolicy;
63+
TargetHost = string.Empty;
4464
IsServer = true;
45-
RemoteCertRequired = sslServerAuthenticationOptions.ClientCertificateRequired;
46-
if (NetEventSource.Log.IsEnabled())
65+
UserState = state;
66+
ServerOptionDelegate = optionCallback;
67+
}
68+
69+
internal void UpdateOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions)
70+
{
71+
if (sslServerAuthenticationOptions.ServerCertificate == null &&
72+
sslServerAuthenticationOptions.ServerCertificateContext == null &&
73+
sslServerAuthenticationOptions.ServerCertificateSelectionCallback == null &&
74+
CertSelectionDelegate == null)
4775
{
48-
NetEventSource.Info(this, $"Server RemoteCertRequired: {RemoteCertRequired}.");
76+
throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
77+
}
78+
79+
if ((sslServerAuthenticationOptions.ServerCertificate != null ||
80+
sslServerAuthenticationOptions.ServerCertificateContext != null ||
81+
CertSelectionDelegate != null) &&
82+
sslServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
83+
{
84+
throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(ServerCertificateSelectionCallback)));
85+
}
86+
87+
if (CertValidationDelegate == null)
88+
{
89+
CertValidationDelegate = sslServerAuthenticationOptions.RemoteCertificateValidationCallback;
90+
}
91+
else if (sslServerAuthenticationOptions.RemoteCertificateValidationCallback != null &&
92+
CertValidationDelegate != sslServerAuthenticationOptions.RemoteCertificateValidationCallback)
93+
{
94+
// Callback was set in constructor to differet value.
95+
throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(RemoteCertificateValidationCallback)));
4996
}
50-
TargetHost = string.Empty;
5197

52-
// Server specific options.
98+
IsServer = true;
99+
AllowRenegotiation = sslServerAuthenticationOptions.AllowRenegotiation;
100+
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols;
101+
EnabledSslProtocols = FilterOutIncompatibleSslProtocols(sslServerAuthenticationOptions.EnabledSslProtocols);
102+
EncryptionPolicy = sslServerAuthenticationOptions.EncryptionPolicy;
103+
RemoteCertRequired = sslServerAuthenticationOptions.ClientCertificateRequired;
53104
CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy;
54105
CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode;
55-
56106
if (sslServerAuthenticationOptions.ServerCertificateContext != null)
57107
{
58108
CertificateContext = sslServerAuthenticationOptions.ServerCertificateContext;
@@ -70,7 +120,7 @@ internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthen
70120
{
71121
// This is legacy fix-up. If the Certificate did not have key, we will search stores and we
72122
// will try to find one with matching hash.
73-
certificateWithKey = SecureChannel.FindCertificateWithPrivateKey(this, true, sslServerAuthenticationOptions.ServerCertificate);
123+
certificateWithKey = SslStream.FindCertificateWithPrivateKey(this, true, sslServerAuthenticationOptions.ServerCertificate);
74124
if (certificateWithKey == null)
75125
{
76126
throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
@@ -80,45 +130,9 @@ internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthen
80130
}
81131
}
82132

83-
if (sslServerAuthenticationOptions.RemoteCertificateValidationCallback != null)
133+
if (sslServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
84134
{
85-
CertValidationDelegate = sslServerAuthenticationOptions.RemoteCertificateValidationCallback;
86-
}
87-
}
88-
89-
internal SslAuthenticationOptions(ServerOptionsSelectionCallback optionCallback, object? state, RemoteCertificateValidationCallback? remoteCallback)
90-
{
91-
CheckCertName = false;
92-
TargetHost = string.Empty;
93-
IsServer = true;
94-
UserState = state;
95-
ServerOptionDelegate = optionCallback;
96-
CertValidationDelegate = remoteCallback;
97-
}
98-
99-
internal void UpdateOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions)
100-
{
101-
AllowRenegotiation = sslServerAuthenticationOptions.AllowRenegotiation;
102-
ApplicationProtocols = sslServerAuthenticationOptions.ApplicationProtocols;
103-
EnabledSslProtocols = FilterOutIncompatibleSslProtocols(sslServerAuthenticationOptions.EnabledSslProtocols);
104-
EncryptionPolicy = sslServerAuthenticationOptions.EncryptionPolicy;
105-
RemoteCertRequired = sslServerAuthenticationOptions.ClientCertificateRequired;
106-
CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy;
107-
CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode;
108-
if (sslServerAuthenticationOptions.ServerCertificateContext != null)
109-
{
110-
CertificateContext = sslServerAuthenticationOptions.ServerCertificateContext;
111-
}
112-
else if (sslServerAuthenticationOptions.ServerCertificate is X509Certificate2 certificateWithKey &&
113-
certificateWithKey.HasPrivateKey)
114-
{
115-
// given cert is X509Certificate2 with key. We can use it directly.
116-
CertificateContext = SslStreamCertificateContext.Create(certificateWithKey);
117-
}
118-
119-
if (sslServerAuthenticationOptions.RemoteCertificateValidationCallback != null)
120-
{
121-
CertValidationDelegate = sslServerAuthenticationOptions.RemoteCertificateValidationCallback;
135+
ServerCertSelectionDelegate = sslServerAuthenticationOptions.ServerCertificateSelectionCallback;
122136
}
123137
}
124138

@@ -150,10 +164,10 @@ private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols proto
150164
internal bool RemoteCertRequired { get; set; }
151165
internal bool CheckCertName { get; set; }
152166
internal RemoteCertificateValidationCallback? CertValidationDelegate { get; set; }
153-
internal LocalCertSelectionCallback? CertSelectionDelegate { get; set; }
154-
internal ServerCertSelectionCallback? ServerCertSelectionDelegate { get; set; }
167+
internal LocalCertificateSelectionCallback? CertSelectionDelegate { get; set; }
168+
internal ServerCertificateSelectionCallback? ServerCertSelectionDelegate { get; set; }
155169
internal CipherSuitesPolicy? CipherSuitesPolicy { get; set; }
156-
internal object? UserState { get; }
157-
internal ServerOptionsSelectionCallback? ServerOptionDelegate { get; }
170+
internal object? UserState { get; set; }
171+
internal ServerOptionsSelectionCallback? ServerOptionDelegate { get; set; }
158172
}
159173
}

src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.Android.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
namespace System.Net.Security
99
{
10-
internal sealed partial class SslConnectionInfo
10+
internal partial struct SslConnectionInfo
1111
{
12-
public SslConnectionInfo(SafeSslHandle sslContext)
12+
public void UpdateSslConnectionInfo(SafeSslHandle sslContext)
1313
{
1414
string protocolString = Interop.AndroidCrypto.SSLStreamGetProtocol(sslContext);
1515
SslProtocols protocol = protocolString switch
@@ -26,6 +26,7 @@ public SslConnectionInfo(SafeSslHandle sslContext)
2626
_ => SslProtocols.None,
2727
};
2828
Protocol = (int)protocol;
29+
ApplicationProtocol = Interop.AndroidCrypto.SSLStreamGetApplicationProtocol(sslContext);
2930

3031
// Enum value names should match the cipher suite name, so we just parse the
3132
string cipherSuite = Interop.AndroidCrypto.SSLStreamGetCipherSuite(sslContext);

src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.Linux.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@
66

77
namespace System.Net.Security
88
{
9-
internal sealed partial class SslConnectionInfo
9+
internal partial struct SslConnectionInfo
1010
{
11-
public SslConnectionInfo(SafeSslHandle sslContext)
11+
public void UpdateSslConnectionInfo(SafeSslHandle sslContext)
1212
{
1313
Protocol = (int)MapProtocolVersion(Interop.Ssl.SslGetVersion(sslContext));
14+
ApplicationProtocol = Interop.Ssl.SslGetAlpnSelected(sslContext);
1415

1516
MapCipherSuite(SslGetCurrentCipherSuite(sslContext));
1617
}

src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.OSX.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
namespace System.Net.Security
99
{
10-
internal sealed partial class SslConnectionInfo
10+
internal partial struct SslConnectionInfo
1111
{
12-
public SslConnectionInfo(SafeSslHandle sslContext)
12+
public void UpdateSslConnectionInfo(SafeSslHandle sslContext)
1313
{
1414
SslProtocols protocol;
1515
TlsCipherSuite cipherSuite;
@@ -26,6 +26,7 @@ public SslConnectionInfo(SafeSslHandle sslContext)
2626

2727
Protocol = (int)protocol;
2828
TlsCipherSuite = cipherSuite;
29+
ApplicationProtocol = Interop.AppleCrypto.SslGetAlpnSelected(sslContext);
2930

3031
MapCipherSuite(cipherSuite);
3132
}

src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace System.Net.Security
99
{
10-
internal sealed partial class SslConnectionInfo
10+
internal partial struct SslConnectionInfo
1111
{
1212
private void MapCipherSuite(TlsCipherSuite cipherSuite)
1313
{
Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,47 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
46
namespace System.Net.Security
57
{
6-
internal sealed partial class SslConnectionInfo
8+
internal partial struct SslConnectionInfo
79
{
8-
public SslConnectionInfo(SecPkgContext_ConnectionInfo interopConnectionInfo, TlsCipherSuite cipherSuite)
10+
private static byte[]? GetNegotiatedApplicationProtocol(SafeDeleteContext context)
11+
{
12+
Interop.SecPkgContext_ApplicationProtocol alpnContext = default;
13+
bool success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPISecureChannel, context, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_APPLICATION_PROTOCOL, ref alpnContext);
14+
15+
// Check if the context returned is alpn data, with successful negotiation.
16+
if (success &&
17+
alpnContext.ProtoNegoExt == Interop.ApplicationProtocolNegotiationExt.ALPN &&
18+
alpnContext.ProtoNegoStatus == Interop.ApplicationProtocolNegotiationStatus.Success)
19+
{
20+
return alpnContext.Protocol;
21+
}
22+
23+
return null;
24+
}
25+
26+
public void UpdateSslConnectionInfo(SafeDeleteContext securityContext)
927
{
28+
SecPkgContext_ConnectionInfo interopConnectionInfo = default;
29+
bool success = SSPIWrapper.QueryBlittableContextAttributes(
30+
GlobalSSPI.SSPISecureChannel,
31+
securityContext,
32+
Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CONNECTION_INFO,
33+
ref interopConnectionInfo);
34+
Debug.Assert(success);
35+
36+
TlsCipherSuite cipherSuite = default;
37+
SecPkgContext_CipherInfo cipherInfo = default;
38+
39+
success = SSPIWrapper.QueryBlittableContextAttributes(GlobalSSPI.SSPISecureChannel, securityContext, Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CIPHER_INFO, ref cipherInfo);
40+
if (success)
41+
{
42+
cipherSuite = (TlsCipherSuite)cipherInfo.dwCipherSuite;
43+
}
44+
1045
Protocol = interopConnectionInfo.Protocol;
1146
DataCipherAlg = interopConnectionInfo.DataCipherAlg;
1247
DataKeySize = interopConnectionInfo.DataKeySize;
@@ -16,6 +51,8 @@ public SslConnectionInfo(SecPkgContext_ConnectionInfo interopConnectionInfo, Tls
1651
KeyExchKeySize = interopConnectionInfo.KeyExchKeySize;
1752

1853
TlsCipherSuite = cipherSuite;
54+
55+
ApplicationProtocol = GetNegotiatedApplicationProtocol(securityContext);
1956
}
2057
}
2158
}

src/libraries/System.Net.Security/src/System/Net/Security/SslConnectionInfo.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33

44
namespace System.Net.Security
55
{
6-
internal sealed partial class SslConnectionInfo
6+
internal partial struct SslConnectionInfo
77
{
8-
public int Protocol { get; }
8+
public int Protocol { get; private set; }
99
public TlsCipherSuite TlsCipherSuite { get; private set; }
1010
public int DataCipherAlg { get; private set; }
1111
public int DataKeySize { get; private set; }
1212
public int DataHashAlg { get; private set; }
1313
public int DataHashKeySize { get; private set; }
1414
public int KeyExchangeAlg { get; private set; }
1515
public int KeyExchKeySize { get; private set; }
16+
17+
public byte[]? ApplicationProtocol { get; internal set; }
1618
}
1719
}

0 commit comments

Comments
 (0)