1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 namespace System.ServiceModel.Channels
5 {
6     using System.Collections.ObjectModel;
7     using System.Diagnostics;
8     using System.Globalization;
9     using System.IdentityModel.Policy;
10     using System.IdentityModel.Selectors;
11     using System.IdentityModel.Tokens;
12     using System.Net;
13     using System.Runtime;
14     using System.Security.Cryptography.X509Certificates;
15     using System.Security.Principal;
16     using System.ServiceModel;
17     using System.ServiceModel.Activation;
18     using System.ServiceModel.Description;
19     using System.ServiceModel.Diagnostics;
20     using System.ServiceModel.Security;
21 
22     class HttpsChannelListener<TChannel> : HttpChannelListener<TChannel>
23                         where TChannel : class, IChannel
24     {
25         readonly bool useCustomClientCertificateVerification;
26         bool shouldValidateClientCertificate;
27         bool useHostedClientCertificateMapping;
28         bool requireClientCertificate;
29         SecurityTokenAuthenticator certificateAuthenticator;
30         const HttpStatusCode CertificateErrorStatusCode = HttpStatusCode.Forbidden;
31         IChannelBindingProvider channelBindingProvider;
32 
HttpsChannelListener(HttpsTransportBindingElement httpsBindingElement, BindingContext context)33         public HttpsChannelListener(HttpsTransportBindingElement httpsBindingElement, BindingContext context)
34             : base(httpsBindingElement, context)
35         {
36             this.requireClientCertificate = httpsBindingElement.RequireClientCertificate;
37             this.shouldValidateClientCertificate = ShouldValidateClientCertificate(this.requireClientCertificate, context);
38 
39             // Pick up the MapCertificateToWindowsAccount setting from the configured token authenticator.
40             SecurityCredentialsManager credentialProvider =
41                 context.BindingParameters.Find<SecurityCredentialsManager>();
42             if (credentialProvider == null)
43             {
44                 credentialProvider = ServiceCredentials.CreateDefaultCredentials();
45             }
46             SecurityTokenManager tokenManager = credentialProvider.CreateSecurityTokenManager();
47             this.certificateAuthenticator =
48                     TransportSecurityHelpers.GetCertificateTokenAuthenticator(tokenManager, context.Binding.Scheme,
49                     TransportSecurityHelpers.GetListenUri(context.ListenUriBaseAddress, context.ListenUriRelativeAddress));
50 
51 
52             ServiceCredentials serviceCredentials = credentialProvider as ServiceCredentials;
53 
54             if (serviceCredentials != null &&
55                 serviceCredentials.ClientCertificate.Authentication.CertificateValidationMode == X509CertificateValidationMode.Custom)
56             {
57                 useCustomClientCertificateVerification = true;
58             }
59             else
60             {
61                 useCustomClientCertificateVerification = false;
62 
63                 X509SecurityTokenAuthenticator authenticator = this.certificateAuthenticator as X509SecurityTokenAuthenticator;
64 
65                 if (authenticator != null)
66                 {
67                     this.certificateAuthenticator = new X509SecurityTokenAuthenticator(X509CertificateValidator.None,
68                         authenticator.MapCertificateToWindowsAccount, this.ExtractGroupsForWindowsAccounts, false);
69                 }
70             }
71 
72             if (this.RequireClientCertificate &&
73                 this.AuthenticationScheme.IsNotSet(AuthenticationSchemes.Anonymous))
74             {
75                 throw DiagnosticUtility.ExceptionUtility.ThrowHelper(new InvalidOperationException(SR.GetString(
76                     SR.HttpAuthSchemeAndClientCert, this.AuthenticationScheme)), TraceEventType.Error);
77             }
78 
79             this.channelBindingProvider = new ChannelBindingProviderHelper();
80         }
81 
82         public bool RequireClientCertificate
83         {
84             get
85             {
86                 return this.requireClientCertificate;
87             }
88         }
89 
90         public override string Scheme
91         {
92             get
93             {
94                 return Uri.UriSchemeHttps;
95             }
96         }
97 
98         public override bool IsChannelBindingSupportEnabled
99         {
100             get
101             {
102                 return this.channelBindingProvider.IsChannelBindingSupportEnabled;
103             }
104         }
105 
106         internal override UriPrefixTable<ITransportManagerRegistration> TransportManagerTable
107         {
108             get
109             {
110                 return SharedHttpsTransportManager.StaticTransportManagerTable;
111             }
112         }
113 
GetProperty()114         public override T GetProperty<T>()
115         {
116             if (typeof(T) == typeof(IChannelBindingProvider))
117             {
118                 return (T)(object)this.channelBindingProvider;
119             }
120 
121             return base.GetProperty<T>();
122         }
123 
ApplyHostedContext(string virtualPath, bool isMetadataListener)124         internal override void ApplyHostedContext(string virtualPath, bool isMetadataListener)
125         {
126             base.ApplyHostedContext(virtualPath, isMetadataListener);
127             useHostedClientCertificateMapping = AspNetEnvironment.Current.ValidateHttpsSettings(virtualPath, ref this.requireClientCertificate);
128 
129             // We want to validate the certificate if IIS is set to require a client certificate
130             if (this.requireClientCertificate)
131             {
132                 this.shouldValidateClientCertificate = true;
133             }
134         }
135 
CreateTransportManagerRegistration(Uri listenUri)136         internal override ITransportManagerRegistration CreateTransportManagerRegistration(Uri listenUri)
137         {
138             return new SharedHttpsTransportManager(listenUri, this);
139         }
140 
141         // Note: the returned SecurityMessageProperty has ownership of certificate and identity.
CreateSecurityProperty(X509Certificate2 certificate, WindowsIdentity identity, string authType)142         SecurityMessageProperty CreateSecurityProperty(X509Certificate2 certificate, WindowsIdentity identity, string authType)
143         {
144             SecurityToken token;
145             if (identity != null)
146             {
147                 token = new X509WindowsSecurityToken(certificate, identity, authType, false);
148             }
149             else
150             {
151                 token = new X509SecurityToken(certificate, false);
152             }
153 
154             ReadOnlyCollection<IAuthorizationPolicy> policies = this.certificateAuthenticator.ValidateToken(token);
155             SecurityMessageProperty result = new SecurityMessageProperty();
156             result.TransportToken = new SecurityTokenSpecification(token, policies);
157             result.ServiceSecurityContext = new ServiceSecurityContext(policies);
158             return result;
159         }
160 
ProcessAuthentication(IHttpAuthenticationContext authenticationContext)161         public override SecurityMessageProperty ProcessAuthentication(IHttpAuthenticationContext authenticationContext)
162         {
163             if (this.shouldValidateClientCertificate)
164             {
165                 SecurityMessageProperty retValue;
166                 X509Certificate2 certificate = null;
167 
168                 try
169                 {
170                     bool isCertificateValid;
171                     certificate = authenticationContext.GetClientCertificate(out isCertificateValid);
172                     Fx.Assert(!this.requireClientCertificate || certificate != null, "ClientCertificate must be present");
173 
174                     if (certificate != null)
175                     {
176                         if (!this.useCustomClientCertificateVerification)
177                         {
178                             Fx.Assert(isCertificateValid, "ClientCertificate must be valid");
179                         }
180 
181                         WindowsIdentity identity = null;
182                         string authType = base.GetAuthType(authenticationContext);
183 
184                         if (this.useHostedClientCertificateMapping)
185                         {
186                             identity = authenticationContext.LogonUserIdentity;
187                             if (identity == null || !identity.IsAuthenticated)
188                             {
189                                 identity = WindowsIdentity.GetAnonymous();
190                             }
191                             else
192                             {
193                                 // it is not recommended to call identity.AuthenticationType as this is a privileged instruction.
194                                 // when the identity is cloned, it will be created with an authtype indicating WindowsIdentity from a cert.
195                                 identity = SecurityUtils.CloneWindowsIdentityIfNecessary(identity, SecurityUtils.AuthTypeCertMap);
196                                 authType = SecurityUtils.AuthTypeCertMap;
197                             }
198                         }
199 
200                         retValue = CreateSecurityProperty(certificate, identity, authType);
201                     }
202                     else if (this.AuthenticationScheme == AuthenticationSchemes.Anonymous)
203                     {
204                         return new SecurityMessageProperty();
205                     }
206                     else
207                     {
208                         return base.ProcessAuthentication(authenticationContext);
209                     }
210                 }
211 #pragma warning suppress 56500 // covered by FXCop
212                 catch (Exception exception)
213                 {
214                     if (Fx.IsFatal(exception))
215                         throw;
216 
217                     // Audit Authentication failure
218                     if (AuditLevel.Failure == (this.AuditBehavior.MessageAuthenticationAuditLevel & AuditLevel.Failure))
219                         WriteAuditEvent(AuditLevel.Failure, (certificate != null) ? SecurityUtils.GetCertificateId(certificate) : String.Empty, exception);
220 
221                     throw;
222                 }
223 
224                 // Audit Authentication success
225                 if (AuditLevel.Success == (this.AuditBehavior.MessageAuthenticationAuditLevel & AuditLevel.Success))
226                     WriteAuditEvent(AuditLevel.Success, (certificate != null) ? SecurityUtils.GetCertificateId(certificate) : String.Empty, null);
227 
228                 return retValue;
229             }
230             else if (this.AuthenticationScheme == AuthenticationSchemes.Anonymous)
231             {
232                 return new SecurityMessageProperty();
233             }
234             else
235             {
236                 return base.ProcessAuthentication(authenticationContext);
237             }
238         }
239 
ProcessAuthentication(HttpListenerContext listenerContext)240         public override SecurityMessageProperty ProcessAuthentication(HttpListenerContext listenerContext)
241         {
242             if (this.shouldValidateClientCertificate)
243             {
244                 SecurityMessageProperty retValue;
245                 X509Certificate2 certificateEx = null;
246 
247                 try
248                 {
249                     X509Certificate certificate = listenerContext.Request.GetClientCertificate();
250                     Fx.Assert(!this.requireClientCertificate || certificate != null,
251                         "HttpListenerRequest.ClientCertificate is not present");
252 
253                     if (certificate != null)
254                     {
255                         if (!useCustomClientCertificateVerification)
256                         {
257                             Fx.Assert(listenerContext.Request.ClientCertificateError == 0,
258                                 "HttpListenerRequest.ClientCertificate is not valid");
259                         }
260                         certificateEx = new X509Certificate2(certificate);
261                         retValue = CreateSecurityProperty(certificateEx, null, string.Empty);
262                     }
263                     else if (this.AuthenticationScheme == AuthenticationSchemes.Anonymous)
264                     {
265                         return new SecurityMessageProperty();
266                     }
267                     else
268                     {
269                         return base.ProcessAuthentication(listenerContext);
270                     }
271                 }
272 #pragma warning suppress 56500 // covered by FXCop
273                 catch (Exception exception)
274                 {
275                     if (Fx.IsFatal(exception))
276                         throw;
277 
278                     // Audit Authentication failure
279                     if (AuditLevel.Failure == (this.AuditBehavior.MessageAuthenticationAuditLevel & AuditLevel.Failure))
280                         WriteAuditEvent(AuditLevel.Failure, (certificateEx != null) ? SecurityUtils.GetCertificateId(certificateEx) : String.Empty, exception);
281 
282                     throw;
283                 }
284 
285                 // Audit Authentication success
286                 if (AuditLevel.Success == (this.AuditBehavior.MessageAuthenticationAuditLevel & AuditLevel.Success))
287                     WriteAuditEvent(AuditLevel.Success, (certificateEx != null) ? SecurityUtils.GetCertificateId(certificateEx) : String.Empty, null);
288 
289                 return retValue;
290             }
291             else if (this.AuthenticationScheme == AuthenticationSchemes.Anonymous)
292             {
293                 return new SecurityMessageProperty();
294             }
295             else
296             {
297                 return base.ProcessAuthentication(listenerContext);
298             }
299         }
300 
301         [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability103",
302                             Justification = "The exceptions are wrapped already.")]
ValidateAuthentication(IHttpAuthenticationContext authenticationContext)303         public override HttpStatusCode ValidateAuthentication(IHttpAuthenticationContext authenticationContext)
304         {
305             HttpStatusCode result = base.ValidateAuthentication(authenticationContext);
306             if (result == HttpStatusCode.OK)
307             {
308                 if (this.shouldValidateClientCertificate)
309                 {
310                     bool isValidCertificate;
311                     X509Certificate2 clientCertificate = authenticationContext.GetClientCertificate(out isValidCertificate);
312                     if (clientCertificate == null)
313                     {
314                         if (this.RequireClientCertificate)
315                         {
316                             if (DiagnosticUtility.ShouldTraceError)
317                             {
318                                 TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.HttpsClientCertificateNotPresent, SR.GetString(SR.TraceCodeHttpsClientCertificateNotPresent),
319                                     authenticationContext.CreateTraceRecord(), this, null);
320                             }
321                             result = CertificateErrorStatusCode;
322                         }
323                     }
324                     else if (!isValidCertificate && !this.useCustomClientCertificateVerification)
325                     {
326                         if (DiagnosticUtility.ShouldTraceError)
327                         {
328                             TraceUtility.TraceEvent(TraceEventType.Error, TraceCode.HttpsClientCertificateInvalid, SR.GetString(SR.TraceCodeHttpsClientCertificateInvalid),
329                                 authenticationContext.CreateTraceRecord(), this, null);
330                         }
331                         result = CertificateErrorStatusCode;
332                     }
333 
334                     // Audit Authentication failure
335                     if (result != HttpStatusCode.OK && (AuditLevel.Failure == (this.AuditBehavior.MessageAuthenticationAuditLevel & AuditLevel.Failure)))
336                     {
337                         string message = SR.GetString(SR.HttpAuthenticationFailed, this.AuthenticationScheme, result);
338                         Exception exception = DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(message));
339                         WriteAuditEvent(AuditLevel.Failure, (clientCertificate != null) ? SecurityUtils.GetCertificateId(clientCertificate) : String.Empty, exception);
340                     }
341                 }
342             }
343 
344             return result;
345         }
346 
347         [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability103",
348                             Justification = "The exceptions are wrapped already.")]
ValidateAuthentication(HttpListenerContext listenerContext)349         public override HttpStatusCode ValidateAuthentication(HttpListenerContext listenerContext)
350         {
351             HttpStatusCode result = base.ValidateAuthentication(listenerContext);
352             if (result == HttpStatusCode.OK)
353             {
354                 if (this.shouldValidateClientCertificate)
355                 {
356                     HttpListenerRequest request = listenerContext.Request;
357                     X509Certificate2 certificateEx = request.GetClientCertificate();
358                     if (certificateEx == null)
359                     {
360                         if (this.RequireClientCertificate)
361                         {
362                             if (DiagnosticUtility.ShouldTraceWarning)
363                             {
364                                 TraceUtility.TraceEvent(TraceEventType.Warning, TraceCode.HttpsClientCertificateNotPresent,
365                                     SR.GetString(SR.TraceCodeHttpsClientCertificateNotPresent),
366                                     new HttpListenerRequestTraceRecord(listenerContext.Request), this, null);
367                             }
368                             result = CertificateErrorStatusCode;
369                         }
370                     }
371                     else if (request.ClientCertificateError != 0 && !useCustomClientCertificateVerification)
372                     {
373                         if (DiagnosticUtility.ShouldTraceWarning)
374                         {
375                             TraceUtility.TraceEvent(TraceEventType.Warning, TraceCode.HttpsClientCertificateInvalid,
376                                 SR.GetString(SR.TraceCodeHttpsClientCertificateInvalid1, "0x" + (request.ClientCertificateError & 65535).ToString("X", CultureInfo.InvariantCulture)),
377                                 new HttpListenerRequestTraceRecord(listenerContext.Request), this, null);
378                         }
379                         result = CertificateErrorStatusCode;
380                     }
381 
382                     // Audit Authentication failure
383                     if (result != HttpStatusCode.OK && (AuditLevel.Failure == (this.AuditBehavior.MessageAuthenticationAuditLevel & AuditLevel.Failure)))
384                     {
385                         string message = SR.GetString(SR.HttpAuthenticationFailed, this.AuthenticationScheme, result);
386                         Exception exception = DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(message));
387                         WriteAuditEvent(AuditLevel.Failure, (certificateEx != null) ? SecurityUtils.GetCertificateId(certificateEx) : String.Empty, exception);
388                     }
389                 }
390             }
391             return result;
392         }
393 
ShouldValidateClientCertificate(bool requireClientCertificateValidation, BindingContext context)394         private static bool ShouldValidateClientCertificate(bool requireClientCertificateValidation, BindingContext context)
395         {
396             if (requireClientCertificateValidation)
397             {
398                 return true;
399             }
400 
401             return EndpointSettings.GetValue<bool>(context, EndpointSettings.ValidateOptionalClientCertificates, false);
402         }
403     }
404 }
405