//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.Provider.Behaviors { using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Logging; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Behaviors; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.OpenId.RelyingParty; using Validation; /// /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile /// for the General Services Administration (GSA). /// /// /// Relying parties that include this profile are always held to the terms required by the profile, /// but Providers are only affected by the special behaviors of the profile when the RP specifically /// indicates that they want to use this profile. /// [Serializable] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] public sealed class GsaIcamProfile : GsaIcamProfileBase, IProviderBehavior { /// /// The maximum time a shared association can live. /// private static readonly TimeSpan MaximumAssociationLifetime = TimeSpan.FromSeconds(86400); /// /// Initializes a new instance of the class. /// public GsaIcamProfile() { if (DisableSslRequirement) { Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); } } /// /// Gets or sets the provider for generating PPID identifiers. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Acronym")] public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } #region IProviderBehavior Members /// /// Adapts the default security settings to the requirements of this behavior. /// /// The original security settings. void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { if (securitySettings.MaximumHashBitLength < 256) { securitySettings.MaximumHashBitLength = 256; } SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256, MaximumAssociationLifetime, securitySettings); SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1, MaximumAssociationLifetime, securitySettings); } /// /// Called when a request is received by the Provider. /// /// The incoming request. /// The cancellation token. /// /// true if this behavior owns this request and wants to stop other behaviors /// from handling it; false to allow other behaviors to process this request. /// /// /// Implementations may set a new value to but /// should not change the properties on the instance of /// itself as that instance may be shared across many requests. /// Task IProviderBehavior.OnIncomingRequestAsync(IRequest request, CancellationToken cancellationToken) { var hostProcessedRequest = request as IHostProcessedRequest; if (hostProcessedRequest != null) { // Only apply our special policies if the RP requested it. var papeRequest = request.GetExtension(); if (papeRequest != null) { if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { // Whenever we see this GSA policy requested, we MUST also see the PPID policy requested. ErrorUtilities.VerifyProtocol(papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), BehaviorStrings.PapeRequestMissingRequiredPolicies); ErrorUtilities.VerifyProtocol(string.Equals(hostProcessedRequest.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); // Apply GSA-specific security to this individual request. request.SecuritySettings.RequireSsl = !DisableSslRequirement; return Task.FromResult(true); } } } return Task.FromResult(false); } /// /// Called when the Provider is preparing to send a response to an authentication request. /// /// The request that is configured to generate the outgoing response. /// The cancellation token. /// /// true if this behavior owns this request and wants to stop other behaviors /// from handling it; false to allow other behaviors to process this request. /// async Task IProviderBehavior.OnOutgoingResponseAsync(Provider.IAuthenticationRequest request, CancellationToken cancellationToken) { bool result = false; // Nothing to do for negative assertions. if (!request.IsAuthenticated.Value) { return result; } var requestInternal = (Provider.AuthenticationRequest)request; var responseMessage = (IProtocolMessageWithExtensions)await requestInternal.GetResponseAsync(cancellationToken); // Only apply our special policies if the RP requested it. var papeRequest = request.GetExtension(); if (papeRequest != null) { var papeResponse = responseMessage.Extensions.OfType().SingleOrDefault(); if (papeResponse == null) { request.AddResponseExtension(papeResponse = new PolicyResponse()); } if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { result = true; if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { papeResponse.ActualPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); } // The spec requires that the OP perform discovery and if that fails, it must either sternly // warn the user of a potential threat or just abort the authentication. // We can't verify that the OP displayed anything to the user at this level, but we can // at least verify that the OP performed the discovery on the realm and halt things if it didn't. ErrorUtilities.VerifyHost(requestInternal.HasRealmDiscoveryBeenPerformed, BehaviorStrings.RealmDiscoveryNotPerformed); } if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); // Mask the user's identity with a PPID. ErrorUtilities.VerifyHost(PpidIdentifierProvider != null, BehaviorStrings.PpidProviderNotGiven); Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); // Indicate that the RP is receiving a PPID claimed_id if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); } } if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { ErrorUtilities.VerifyProtocol( !responseMessage.Extensions.OfType().Any() && !responseMessage.Extensions.OfType().Any(), BehaviorStrings.PiiIncludedWithNoPiiPolicy); // If no PII is given in extensions, and the claimed_id is a PPID, then we can state we issue no PII. if (papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { papeResponse.ActualPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); } } } Reporting.RecordEventOccurrence(this, "OP"); } return result; } #endregion /// /// Ensures the maximum association lifetime does not exceed a given limit. /// /// Type of the association. /// The maximum lifetime. /// The security settings to adjust. private static void SetMaximumAssociationLifetimeToNotExceed(string associationType, TimeSpan maximumLifetime, ProviderSecuritySettings securitySettings) { Requires.NotNullOrEmpty(associationType, "associationType"); Requires.That(maximumLifetime.TotalSeconds > 0, "maximumLifetime", "requires positive timespan"); if (!securitySettings.AssociationLifetimes.ContainsKey(associationType) || securitySettings.AssociationLifetimes[associationType] > maximumLifetime) { securitySettings.AssociationLifetimes[associationType] = maximumLifetime; } } } }