44using System ;
55using System . Collections . Generic ;
66using System . Diagnostics ;
7+ using System . Linq ;
78using System . Net . Security ;
89using System . Security . Authentication ;
910using System . Security . Cryptography . X509Certificates ;
@@ -42,13 +43,13 @@ public SniOptionsSelector(
4243
4344 foreach ( var ( name , sniConfig ) in endpointConfig . SNI )
4445 {
45- var sslServerOptions = new SslServerAuthenticationOptions
46+ var sslOptions = new SslServerAuthenticationOptions
4647 {
4748 ServerCertificate = configLoader . LoadCertificate ( sniConfig . Certificate , endpointConfig . Name ) ,
4849 EnabledSslProtocols = sniConfig . SslProtocols ?? fallbackOptions . SslProtocols ,
4950 } ;
5051
51- if ( sslServerOptions . ServerCertificate is null )
52+ if ( sslOptions . ServerCertificate is null )
5253 {
5354 if ( fallbackOptions . ServerCertificate is null && fallbackOptions . ServerCertificateSelector is null )
5455 {
@@ -58,27 +59,27 @@ public SniOptionsSelector(
5859 if ( fallbackOptions . ServerCertificateSelector is null )
5960 {
6061 // Cache the fallback ServerCertificate since there's no fallback ServerCertificateSelector taking precedence.
61- sslServerOptions . ServerCertificate = fallbackOptions . ServerCertificate ;
62+ sslOptions . ServerCertificate = fallbackOptions . ServerCertificate ;
6263 }
6364 }
6465
6566 var clientCertificateMode = sniConfig . ClientCertificateMode ?? fallbackOptions . ClientCertificateMode ;
6667
6768 if ( clientCertificateMode != ClientCertificateMode . NoCertificate )
6869 {
69- sslServerOptions . ClientCertificateRequired = true ;
70- sslServerOptions . RemoteCertificateValidationCallback = ( sender , certificate , chain , sslPolicyErrors ) =>
70+ sslOptions . ClientCertificateRequired = true ;
71+ sslOptions . RemoteCertificateValidationCallback = ( sender , certificate , chain , sslPolicyErrors ) =>
7172 HttpsConnectionMiddleware . RemoteCertificateValidationCallback (
7273 clientCertificateMode , fallbackOptions . ClientCertificateValidation , certificate , chain , sslPolicyErrors ) ;
7374 }
7475
7576 var httpProtocols = sniConfig . Protocols ?? fallbackHttpProtocols ;
7677 httpProtocols = HttpsConnectionMiddleware . ValidateAndNormalizeHttpProtocols ( httpProtocols , logger ) ;
77- HttpsConnectionMiddleware . ConfigureAlpn ( sslServerOptions , httpProtocols ) ;
78+ HttpsConnectionMiddleware . ConfigureAlpn ( sslOptions , httpProtocols ) ;
7879
7980 var sniOptions = new SniOptions
8081 {
81- SslOptions = sslServerOptions ,
82+ SslOptions = sslOptions ,
8283 HttpProtocols = httpProtocols ,
8384 } ;
8485
@@ -97,37 +98,19 @@ public SniOptionsSelector(
9798 }
9899 }
99100
100- public SniOptions GetOptions ( ConnectionContext connection , string serverName )
101+ public SslServerAuthenticationOptions GetOptions ( ConnectionContext connection , string serverName )
101102 {
102- SniOptions options = null ;
103+ SniOptions sniOptions = null ;
103104
104- if ( ! string . IsNullOrEmpty ( serverName ) )
105+ if ( ! string . IsNullOrEmpty ( serverName ) && ! _fullNameOptions . TryGetValue ( serverName , out sniOptions ) )
105106 {
106- if ( _fullNameOptions . TryGetValue ( serverName , out options ) )
107- {
108- return options ;
109- }
110-
111- var matchedNameLength = 0 ;
112- ReadOnlySpan < char > serverNameSpan = serverName ;
113-
114- foreach ( var ( nameCandidate , optionsCandidate ) in _wildcardPrefixOptions )
115- {
116- ReadOnlySpan < char > nameCandidateSpan = nameCandidate ;
117-
118- // Note that we only slice off the `*`. We want to match the leading `.` also.
119- if ( serverNameSpan . EndsWith ( nameCandidateSpan . Slice ( wildcardHost . Length ) , StringComparison . OrdinalIgnoreCase ) &&
120- nameCandidateSpan . Length > matchedNameLength )
121- {
122- matchedNameLength = nameCandidateSpan . Length ;
123- options = optionsCandidate ;
124- }
125- }
107+ TryGetWildcardPrefixedOptions ( serverName , out sniOptions ) ;
126108 }
127109
128- options ??= _wildcardHostOptions ;
110+ // Fully wildcarded ("*") options can be used even when given an empty server name.
111+ sniOptions ??= _wildcardHostOptions ;
129112
130- if ( options is null )
113+ if ( sniOptions is null )
131114 {
132115 if ( serverName is null )
133116 {
@@ -139,25 +122,75 @@ public SniOptions GetOptions(ConnectionContext connection, string serverName)
139122 }
140123 }
141124
142- if ( options . SslOptions . ServerCertificate is null )
125+ connection . Features . Set ( new HttpProtocolsFeature ( sniOptions . HttpProtocols ) ) ;
126+
127+ var sslOptions = sniOptions . SslOptions ;
128+
129+ if ( sslOptions . ServerCertificate is null )
143130 {
144131 Debug . Assert ( _fallbackServerCertificateSelector != null ,
145132 "The cached SniOptions ServerCertificate can only be null if there's a fallback certificate selector." ) ;
146133
147134 // If a ServerCertificateSelector doesn't return a cert, HttpsConnectionMiddleware doesn't fallback to the ServerCertificate.
148- options = options . Clone ( ) ;
149- options . SslOptions . ServerCertificate = _fallbackServerCertificateSelector ( connection , serverName ) ;
135+ sslOptions = CloneSslOptions ( sslOptions ) ;
136+ sslOptions . ServerCertificate = _fallbackServerCertificateSelector ( connection , serverName ) ;
150137 }
151138
152139 if ( _onAuthenticateCallback != null )
153140 {
154- options = options . Clone ( ) ;
141+ sslOptions = CloneSslOptions ( sslOptions ) ;
155142
156143 // From doc comments: "This is called after all of the other settings have already been applied."
157- _onAuthenticateCallback ( connection , options . SslOptions ) ;
158- }
144+ _onAuthenticateCallback ( connection , sslOptions ) ;
145+ }
146+
147+ return sslOptions ;
148+ }
159149
160- return options ;
150+ private bool TryGetWildcardPrefixedOptions ( string serverName , out SniOptions sniOptions )
151+ {
152+ sniOptions = null ;
153+
154+ var matchedNameLength = 0 ;
155+ ReadOnlySpan < char > serverNameSpan = serverName ;
156+
157+ foreach ( var ( nameCandidate , optionsCandidate ) in _wildcardPrefixOptions )
158+ {
159+ ReadOnlySpan < char > nameCandidateSpan = nameCandidate ;
160+
161+ // Note that we only slice off the `*`. We want to match the leading `.` also.
162+ if ( serverNameSpan . EndsWith ( nameCandidateSpan . Slice ( wildcardHost . Length ) , StringComparison . OrdinalIgnoreCase ) &&
163+ nameCandidateSpan . Length > matchedNameLength )
164+ {
165+ matchedNameLength = nameCandidateSpan . Length ;
166+ sniOptions = optionsCandidate ;
167+ }
168+ }
169+
170+ return sniOptions != null ;
171+ }
172+
173+ // TODO: Reflection based test to ensure we clone everything!
174+ internal static SslServerAuthenticationOptions CloneSslOptions ( SslServerAuthenticationOptions sslOptions ) =>
175+ new SslServerAuthenticationOptions
176+ {
177+ AllowRenegotiation = sslOptions . AllowRenegotiation ,
178+ ApplicationProtocols = sslOptions . ApplicationProtocols ? . ToList ( ) ,
179+ CertificateRevocationCheckMode = sslOptions . CertificateRevocationCheckMode ,
180+ CipherSuitesPolicy = sslOptions . CipherSuitesPolicy ,
181+ ClientCertificateRequired = sslOptions . ClientCertificateRequired ,
182+ EnabledSslProtocols = sslOptions . EnabledSslProtocols ,
183+ EncryptionPolicy = sslOptions . EncryptionPolicy ,
184+ RemoteCertificateValidationCallback = sslOptions . RemoteCertificateValidationCallback ,
185+ ServerCertificate = sslOptions . ServerCertificate ,
186+ ServerCertificateContext = sslOptions . ServerCertificateContext ,
187+ ServerCertificateSelectionCallback = sslOptions . ServerCertificateSelectionCallback ,
188+ } ;
189+
190+ private class SniOptions
191+ {
192+ public SslServerAuthenticationOptions SslOptions { get ; set ; }
193+ public HttpProtocols HttpProtocols { get ; set ; }
161194 }
162195 }
163196}
0 commit comments