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