summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2009-05-30 21:23:59 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2009-06-02 16:59:49 -0700
commitf594aa3a0d0399f57858dc9933b3437dd60b63a1 (patch)
tree394a7dbc033809e033b0a2ea62baf61a505526d0 /src
parenta4d3c3f66de90c0665e221758c3a89df60eb4c36 (diff)
downloadDotNetOpenAuth-f594aa3a0d0399f57858dc9933b3437dd60b63a1.zip
DotNetOpenAuth-f594aa3a0d0399f57858dc9933b3437dd60b63a1.tar.gz
DotNetOpenAuth-f594aa3a0d0399f57858dc9933b3437dd60b63a1.tar.bz2
First run at security profile extensibility and the GSA profile class.
OP PPID support has not been put in yet, and the security profile interfaces are facade class level instead of channel level.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs20
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs20
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj16
-rw-r--r--src/DotNetOpenAuth/OpenId/ISecurityProfile.cs80
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs30
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/IProviderSecurityProfile.cs32
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs51
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs23
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs15
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartySecurityProfile.cs34
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs7
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs21
-rw-r--r--src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.Designer.cs126
-rw-r--r--src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.resx141
-rw-r--r--src/DotNetOpenAuth/OpenId/SecurityProfiles/USGovernmentLevel1.cs282
-rw-r--r--src/DotNetOpenAuth/OpenId/SecuritySettings.cs29
16 files changed, 913 insertions, 14 deletions
diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs
index 72a6481..03f4786 100644
--- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs
+++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs
@@ -41,6 +41,11 @@ namespace DotNetOpenAuth.Configuration {
private const string RequireSslConfigName = "requireSsl";
/// <summary>
+ /// Gets the name of the &lt;profiles&gt; sub-element.
+ /// </summary>
+ private const string ProfilesElementName = "profiles";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="OpenIdProviderSecuritySettingsElement"/> class.
/// </summary>
public OpenIdProviderSecuritySettingsElement() {
@@ -84,6 +89,16 @@ namespace DotNetOpenAuth.Configuration {
}
/// <summary>
+ /// Gets or sets the predefined security profiles to apply.
+ /// </summary>
+ [ConfigurationProperty(ProfilesElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<IProviderSecurityProfile>))]
+ public TypeConfigurationCollection<IProviderSecurityProfile> Profiles {
+ get { return (TypeConfigurationCollection<IProviderSecurityProfile>)this[ProfilesElementName] ?? new TypeConfigurationCollection<IProviderSecurityProfile>(); }
+ set { this[ProfilesElementName] = value; }
+ }
+
+ /// <summary>
/// Gets or sets the configured lifetimes of the various association types.
/// </summary>
[ConfigurationProperty(AssociationsConfigName, IsDefaultCollection = false)]
@@ -113,6 +128,11 @@ namespace DotNetOpenAuth.Configuration {
Contract.Assume(element != null);
settings.AssociationLifetimes.Add(element.AssociationType, element.MaximumLifetime);
}
+
+ foreach (var profile in this.Profiles.CreateInstances(false)) {
+ settings.SecurityProfiles.Add(profile);
+ }
+
return settings;
}
}
diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
index 437a0da..ffc5616 100644
--- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
+++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs
@@ -66,6 +66,11 @@ namespace DotNetOpenAuth.Configuration {
private const string PrivateSecretMaximumAgeConfigName = "privateSecretMaximumAge";
/// <summary>
+ /// Gets the name of the &lt;profiles&gt; sub-element.
+ /// </summary>
+ private const string ProfilesElementName = "profiles";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="OpenIdRelyingPartySecuritySettingsElement"/> class.
/// </summary>
public OpenIdRelyingPartySecuritySettingsElement() {
@@ -183,6 +188,16 @@ namespace DotNetOpenAuth.Configuration {
}
/// <summary>
+ /// Gets or sets the predefined security profiles to apply.
+ /// </summary>
+ [ConfigurationProperty(ProfilesElementName, IsDefaultCollection = false)]
+ [ConfigurationCollection(typeof(TypeConfigurationCollection<IRelyingPartySecurityProfile>))]
+ public TypeConfigurationCollection<IRelyingPartySecurityProfile> Profiles {
+ get { return (TypeConfigurationCollection<IRelyingPartySecurityProfile>)this[ProfilesElementName] ?? new TypeConfigurationCollection<IRelyingPartySecurityProfile>(); }
+ set { this[ProfilesElementName] = value; }
+ }
+
+ /// <summary>
/// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file.
/// </summary>
/// <returns>The newly created security settings object.</returns>
@@ -200,6 +215,11 @@ namespace DotNetOpenAuth.Configuration {
settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions;
settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers;
settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions;
+
+ foreach (var profile in this.Profiles.CreateInstances(false)) {
+ settings.SecurityProfiles.Add(profile);
+ }
+
return settings;
}
}
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
index 7a28841..6c137a4 100644
--- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj
+++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj
@@ -169,6 +169,9 @@
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
+ <Reference Include="WindowsBase">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ComponentModel\ClaimTypeSuggestions.cs" />
@@ -365,6 +368,7 @@
<Compile Include="OpenId\Interop\AuthenticationResponseShim.cs" />
<Compile Include="OpenId\Interop\ClaimsResponseShim.cs" />
<Compile Include="OpenId\Interop\OpenIdRelyingPartyShim.cs" />
+ <Compile Include="OpenId\ISecurityProfile.cs" />
<Compile Include="OpenId\Messages\CheckAuthenticationRequest.cs" />
<Compile Include="OpenId\Messages\CheckAuthenticationResponse.cs" />
<Compile Include="OpenId\Messages\CheckIdRequest.cs" />
@@ -392,6 +396,7 @@
<Compile Include="OpenId\Provider\IdentityEndpointNormalizationEventArgs.cs" />
<Compile Include="OpenId\Provider\IErrorReporting.cs" />
<Compile Include="OpenId\Provider\IProviderApplicationStore.cs" />
+ <Compile Include="OpenId\Provider\IProviderSecurityProfile.cs" />
<Compile Include="OpenId\Provider\IRequest.cs" />
<Compile Include="OpenId\Provider\ProviderEndpoint.cs" />
<Compile Include="OpenId\Provider\RelyingPartyDiscoveryResult.cs" />
@@ -429,6 +434,7 @@
<Compile Include="OpenId\RelyingParty\AssociationPreference.cs" />
<Compile Include="OpenId\RelyingParty\AuthenticationRequest.cs" />
<Compile Include="OpenId\RelyingParty\AuthenticationRequestMode.cs" />
+ <Compile Include="OpenId\RelyingParty\IRelyingPartySecurityProfile.cs" />
<Compile Include="OpenId\RelyingParty\NegativeAuthenticationResponse.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdAjaxTextBox.cs" />
<Compile Include="OpenId\RelyingParty\OpenIdEventArgs.cs" />
@@ -461,6 +467,12 @@
<Compile Include="OpenId\RelyingParty\ServiceEndpoint.cs" />
<Compile Include="OpenId\OpenIdXrdsHelper.cs" />
<Compile Include="OpenId\RelyingParty\StandardRelyingPartyApplicationStore.cs" />
+ <Compile Include="OpenId\SecurityProfiles\SecurityProfileStrings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>SecurityProfileStrings.resx</DependentUpon>
+ </Compile>
+ <Compile Include="OpenId\SecurityProfiles\USGovernmentLevel1.cs" />
<Compile Include="OpenId\SecuritySettings.cs" />
<Compile Include="Messaging\UntrustedWebRequestHandler.cs" />
<Compile Include="OpenId\UriIdentifier.cs" />
@@ -555,6 +567,10 @@
<EmbeddedResource Include="InfoCard\infocard_81x57.png" />
<EmbeddedResource Include="InfoCard\infocard_92x64.png" />
<EmbeddedResource Include="InfoCard\SupportingScript.js" />
+ <EmbeddedResource Include="OpenId\SecurityProfiles\SecurityProfileStrings.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>SecurityProfileStrings.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" />
diff --git a/src/DotNetOpenAuth/OpenId/ISecurityProfile.cs b/src/DotNetOpenAuth/OpenId/ISecurityProfile.cs
new file mode 100644
index 0000000..b9ec1fe
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/ISecurityProfile.cs
@@ -0,0 +1,80 @@
+//-----------------------------------------------------------------------
+// <copyright file="ISecurityProfile.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId {
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Applies a custom security policy to certain OpenID security settings and behaviors.
+ /// </summary>
+ [ContractClass(typeof(ISecurityProfileContract))]
+ internal interface ISecurityProfile {
+ /// <summary>
+ /// Applies a well known set of security requirements.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void ApplySecuritySettings(SecuritySettings securitySettings);
+
+ /// <summary>
+ /// Checks whether the given security settings comply with security requirements and throws otherwise.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to check for compliance.</param>
+ /// <remarks>
+ /// Security settings should <em>not</em> be changed by this method. Any settings
+ /// that do not comply should cause an exception to be thrown.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the given security settings are not compliant with the requirements of this security profile.</exception>
+ void EnsureCompliance(SecuritySettings securitySettings);
+ }
+
+ /// <summary>
+ /// Code contract for the <see cref="ISecurityProfile"/> type.
+ /// </summary>
+ [ContractClassFor(typeof(ISecurityProfile))]
+ internal abstract class ISecurityProfileContract : ISecurityProfile {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="ISecurityProfileContract"/> class from being created.
+ /// </summary>
+ private ISecurityProfileContract() {
+ }
+
+ #region ISecurityProfile Members
+
+ /// <summary>
+ /// Applies a well known set of security requirements.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void ISecurityProfile.ApplySecuritySettings(SecuritySettings securitySettings) {
+ Contract.Requires(securitySettings != null);
+ }
+
+ /// <summary>
+ /// Checks whether the given security settings comply with security requirements and throws otherwise.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to check for compliance.</param>
+ /// <remarks>
+ /// Security settings should <em>not</em> be changed by this method. Any settings
+ /// that do not comply should cause an exception to be thrown.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the given security settings are not compliant with the requirements of this security profile.</exception>
+ void ISecurityProfile.EnsureCompliance(SecuritySettings securitySettings) {
+ Contract.Requires(securitySettings != null);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs
index b368022..2744fcf 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs
@@ -25,6 +25,11 @@ namespace DotNetOpenAuth.OpenId.Provider {
private readonly NegativeAssertionResponse negativeResponse;
/// <summary>
+ /// A cache of the result from discovery of the Realm URL.
+ /// </summary>
+ private RelyingPartyDiscoveryResult? realmDiscoveryResult;
+
+ /// <summary>
/// Initializes a new instance of the <see cref="HostProcessedRequest"/> class.
/// </summary>
/// <param name="provider">The provider that received the request.</param>
@@ -64,6 +69,13 @@ namespace DotNetOpenAuth.OpenId.Provider {
#endregion
/// <summary>
+ /// Gets a value indicating whether realm discovery been performed.
+ /// </summary>
+ internal bool HasRealmDiscoveryBeenPerformed {
+ get { return this.realmDiscoveryResult.HasValue; }
+ }
+
+ /// <summary>
/// Gets the negative response.
/// </summary>
protected NegativeAssertionResponse NegativeResponse {
@@ -93,9 +105,25 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// See OpenID Authentication 2.0 spec section 9.2.1.
/// </remarks>
public RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(OpenIdProvider provider) {
- Contract.Requires(provider != null);
+ Contract.Requires<ArgumentNullException>(provider != null);
ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
+ if (!this.realmDiscoveryResult.HasValue) {
+ this.realmDiscoveryResult = this.IsReturnUrlDiscoverableCore(provider);
+ }
+
+ return this.realmDiscoveryResult.Value;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether verification of the return URL claimed by the Relying Party
+ /// succeeded.
+ /// </summary>
+ /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param>
+ /// <returns>Result of realm discovery.</returns>
+ private RelyingPartyDiscoveryResult IsReturnUrlDiscoverableCore(OpenIdProvider provider) {
+ Contract.Requires(provider != null);
+
ErrorUtilities.VerifyInternal(this.Realm != null, "Realm should have been read or derived by now.");
try {
if (provider.SecuritySettings.RequireSsl && this.Realm.Scheme != Uri.UriSchemeHttps) {
diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderSecurityProfile.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderSecurityProfile.cs
new file mode 100644
index 0000000..cf5e732
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderSecurityProfile.cs
@@ -0,0 +1,32 @@
+//-----------------------------------------------------------------------
+// <copyright file="IProviderSecurityProfile.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.Provider {
+ using DotNetOpenAuth.OpenId.ChannelElements;
+
+ /// <summary>
+ /// Applies a custom security policy to certain OpenID security settings and behaviors.
+ /// </summary>
+ /// <remarks>
+ /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level
+ /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods.
+ /// </remarks>
+ internal interface IProviderSecurityProfile : ISecurityProfile {
+ /// <summary>
+ /// Called when a request is received by the Provider.
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ /// <param name="request">The incoming request.</param>
+ void OnIncomingRequest(OpenIdProvider provider, IRequest request);
+
+ /// <summary>
+ /// Called when the Provider is preparing to send a response to an authentication request.
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ /// <param name="request">The request that is configured to generate the outgoing response.</param>
+ void OnOutgoingResponse(OpenIdProvider provider, IAuthenticationRequest request);
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
index 0ec97ff..0eb8d7e 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
@@ -199,24 +199,40 @@ namespace DotNetOpenAuth.OpenId.Provider {
ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany);
}
+ IRequest result = null;
+
var checkIdMessage = incomingMessage as CheckIdRequest;
if (checkIdMessage != null) {
- return new AuthenticationRequest(this, checkIdMessage);
+ result = new AuthenticationRequest(this, checkIdMessage);
+ }
+
+ if (result == null) {
+ var extensionOnlyRequest = incomingMessage as SignedResponseRequest;
+ if (extensionOnlyRequest != null) {
+ result = new AnonymousRequest(this, extensionOnlyRequest);
+ }
}
- var extensionOnlyRequest = incomingMessage as SignedResponseRequest;
- if (extensionOnlyRequest != null) {
- return new AnonymousRequest(this, extensionOnlyRequest);
+ if (result == null) {
+ var checkAuthMessage = incomingMessage as CheckAuthenticationRequest;
+ if (checkAuthMessage != null) {
+ result = new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this));
+ }
}
- var checkAuthMessage = incomingMessage as CheckAuthenticationRequest;
- if (checkAuthMessage != null) {
- return new AutoResponsiveRequest(incomingMessage, new CheckAuthenticationResponse(checkAuthMessage, this));
+ if (result == null) {
+ var associateMessage = incomingMessage as AssociateRequest;
+ if (associateMessage != null) {
+ result = new AutoResponsiveRequest(incomingMessage, associateMessage.CreateResponse(this.AssociationStore, this.SecuritySettings));
+ }
}
- var associateMessage = incomingMessage as AssociateRequest;
- if (associateMessage != null) {
- return new AutoResponsiveRequest(incomingMessage, associateMessage.CreateResponse(this.AssociationStore, this.SecuritySettings));
+ if (result != null) {
+ foreach (var profile in this.SecuritySettings.SecurityProfiles) {
+ profile.OnIncomingRequest(this, result);
+ }
+
+ return result;
}
throw ErrorUtilities.ThrowProtocol(MessagingStrings.UnexpectedMessageReceivedOfMany);
@@ -248,6 +264,7 @@ namespace DotNetOpenAuth.OpenId.Provider {
Contract.Requires(((Request)request).IsResponseReady);
ErrorUtilities.VerifyArgumentNotNull(request, "request");
+ this.ApplySecurityProfilesToResponse(request);
Request requestInternal = (Request)request;
this.Channel.Send(requestInternal.Response);
}
@@ -264,6 +281,7 @@ namespace DotNetOpenAuth.OpenId.Provider {
Contract.Requires(((Request)request).IsResponseReady);
ErrorUtilities.VerifyArgumentNotNull(request, "request");
+ this.ApplySecurityProfilesToResponse(request);
Request requestInternal = (Request)request;
return this.Channel.PrepareResponse(requestInternal.Response);
}
@@ -391,6 +409,19 @@ namespace DotNetOpenAuth.OpenId.Provider {
#endregion
/// <summary>
+ /// Applies all security profiles to the response message.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ private void ApplySecurityProfilesToResponse(IRequest request) {
+ var authRequest = request as IAuthenticationRequest;
+ if (authRequest != null) {
+ foreach (var profile in this.SecuritySettings.SecurityProfiles) {
+ profile.OnOutgoingResponse(this, authRequest);
+ }
+ }
+ }
+
+ /// <summary>
/// Prepares the return value for the GetRequest method in the event of an exception.
/// </summary>
/// <param name="ex">The exception that forms the basis of the error response. Must not be null.</param>
diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs
index 4568dc2..bedc6d0 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs
@@ -7,6 +7,9 @@
namespace DotNetOpenAuth.OpenId.Provider {
using System;
using System.Collections.Generic;
+ using System.Collections.ObjectModel;
+ using System.Collections.Specialized;
+ using System.Linq;
using DotNetOpenAuth.Messaging;
/// <summary>
@@ -24,6 +27,11 @@ namespace DotNetOpenAuth.OpenId.Provider {
internal const bool SignOutgoingExtensionsDefault = true;
/// <summary>
+ /// Backing store for the <see cref="SecurityProfiles"/> property.
+ /// </summary>
+ private readonly ObservableCollection<IProviderSecurityProfile> securityProfiles = new ObservableCollection<IProviderSecurityProfile>();
+
+ /// <summary>
/// The subset of association types and their customized lifetimes.
/// </summary>
private IDictionary<string, TimeSpan> associationLifetimes = new Dictionary<string, TimeSpan>();
@@ -35,6 +43,7 @@ namespace DotNetOpenAuth.OpenId.Provider {
: base(true) {
this.SignOutgoingExtensions = SignOutgoingExtensionsDefault;
this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault;
+ this.securityProfiles.CollectionChanged += this.OnSecurityProfilesChanged;
}
/// <summary>
@@ -53,6 +62,13 @@ namespace DotNetOpenAuth.OpenId.Provider {
public bool RequireSsl { get; set; }
/// <summary>
+ /// Gets a list of custom security profiles to apply to OpenID actions.
+ /// </summary>
+ internal ICollection<IProviderSecurityProfile> SecurityProfiles {
+ get { return this.securityProfiles; }
+ }
+
+ /// <summary>
/// Gets or sets a value indicating whether OpenID 1.x relying parties that may not be
/// protecting their users from replay attacks are protected from
/// replay attacks by this provider.
@@ -81,5 +97,12 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// needed for testing the RP's rejection of unsigned extensions.
/// </remarks>
internal bool SignOutgoingExtensions { get; set; }
+
+ /// <summary>
+ /// Gets the custom security profiles.
+ /// </summary>
+ internal override IEnumerable<ISecurityProfile> CustomSecurityProfiles {
+ get { return this.securityProfiles.Cast<ISecurityProfile>(); }
+ }
}
}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs
index 25c4a15..c2c07f4 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs
@@ -89,7 +89,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
/// <value></value>
public OutgoingWebResponse RedirectingResponse {
- get { return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage()); }
+ get {
+ foreach (var profile in this.RelyingParty.SecuritySettings.SecurityProfiles) {
+ profile.OnOutgoingAuthenticationRequest(this.RelyingParty, this);
+ }
+
+ return this.RelyingParty.Channel.PrepareResponse(this.CreateRequestMessage());
+ }
}
/// <summary>
@@ -162,6 +168,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
set { this.associationPreference = value; }
}
+ /// <summary>
+ /// Gets the extensions that have been added to th request.
+ /// </summary>
+ internal IEnumerable<IOpenIdMessageExtension> AppliedExtensions {
+ get { return this.extensions; }
+ }
+
#region IAuthenticationRequest methods
/// <summary>
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartySecurityProfile.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartySecurityProfile.cs
new file mode 100644
index 0000000..da7f06a
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartySecurityProfile.cs
@@ -0,0 +1,34 @@
+//-----------------------------------------------------------------------
+// <copyright file="IRelyingPartySecurityProfile.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.RelyingParty {
+ /// <summary>
+ /// Applies a custom security policy to certain OpenID security settings and behaviors.
+ /// </summary>
+ /// <remarks>
+ /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level
+ /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods.
+ /// </remarks>
+ internal interface IRelyingPartySecurityProfile : ISecurityProfile {
+ /// <summary>
+ /// Called when an authentication request is about to be sent.
+ /// </summary>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="request">The request.</param>
+ /// <remarks>
+ /// Implementations should be prepared to be called multiple times on the same outgoing message
+ /// without malfunctioning.
+ /// </remarks>
+ void OnOutgoingAuthenticationRequest(OpenIdRelyingParty relyingParty, IAuthenticationRequest request);
+
+ /// <summary>
+ /// Called when an incoming positive assertion is received.
+ /// </summary>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="assertion">The positive assertion.</param>
+ void OnIncomingPositiveAssertion(OpenIdRelyingParty relyingParty, IAuthenticationResponse assertion);
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs
index 1c82098..f2c8cc3 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs
@@ -474,7 +474,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
NegativeAssertionResponse negativeAssertion;
IndirectSignedResponse positiveExtensionOnly;
if ((positiveAssertion = message as PositiveAssertionResponse) != null) {
- return new PositiveAuthenticationResponse(positiveAssertion, this);
+ var response = new PositiveAuthenticationResponse(positiveAssertion, this);
+ foreach (var profile in this.SecuritySettings.SecurityProfiles) {
+ profile.OnIncomingPositiveAssertion(this, response);
+ }
+
+ return response;
} else if ((positiveExtensionOnly = message as IndirectSignedResponse) != null) {
return new PositiveAnonymousResponse(positiveExtensionOnly);
} else if ((negativeAssertion = message as NegativeAssertionResponse) != null) {
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
index 7d910d2..7c6a59c 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/RelyingPartySecuritySettings.cs
@@ -7,6 +7,7 @@
namespace DotNetOpenAuth.OpenId.RelyingParty {
using System;
using System.Collections.Generic;
+ using System.Collections.ObjectModel;
using System.Linq;
using DotNetOpenAuth.Messaging;
@@ -15,11 +16,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
/// </summary>
public sealed class RelyingPartySecuritySettings : SecuritySettings {
/// <summary>
+ /// Backing store for the <see cref="SecurityProfiles"/> property.
+ /// </summary>
+ private readonly ObservableCollection<IRelyingPartySecurityProfile> securityProfiles = new ObservableCollection<IRelyingPartySecurityProfile>();
+
+ /// <summary>
/// Initializes a new instance of the <see cref="RelyingPartySecuritySettings"/> class.
/// </summary>
internal RelyingPartySecuritySettings()
: base(false) {
this.PrivateSecretMaximumAge = TimeSpan.FromDays(7);
+ this.securityProfiles.CollectionChanged += this.OnSecurityProfilesChanged;
}
/// <summary>
@@ -110,6 +117,20 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
public bool RequireAssociation { get; set; }
/// <summary>
+ /// Gets a list of custom security profiles to apply to OpenID actions.
+ /// </summary>
+ internal ICollection<IRelyingPartySecurityProfile> SecurityProfiles {
+ get { return this.securityProfiles; }
+ }
+
+ /// <summary>
+ /// Gets the custom security profiles.
+ /// </summary>
+ internal override IEnumerable<ISecurityProfile> CustomSecurityProfiles {
+ get { return this.securityProfiles.Cast<ISecurityProfile>(); }
+ }
+
+ /// <summary>
/// Filters out any disallowed endpoints.
/// </summary>
/// <param name="endpoints">The endpoints discovered on an Identifier.</param>
diff --git a/src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.Designer.cs
new file mode 100644
index 0000000..94b3ffb
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.Designer.cs
@@ -0,0 +1,126 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.4918
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.SecurityProfiles {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class SecurityProfileStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal SecurityProfileStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OpenId.SecurityProfiles.SecurityProfileStrings", typeof(SecurityProfileStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The PAPE request has an incomplete set of authentication policies..
+ /// </summary>
+ internal static string PapeRequestMissingRequiredPolicies {
+ get {
+ return ResourceManager.GetString("PapeRequestMissingRequiredPolicies", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A PAPE response is missing or is missing required policies..
+ /// </summary>
+ internal static string PapeResponseOrRequiredPoliciesMissing {
+ get {
+ return ResourceManager.GetString("PapeResponseOrRequiredPoliciesMissing", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No personally identifiable information should be included in authentication responses when the PAPE authentication policy http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf is present..
+ /// </summary>
+ internal static string PiiIncludedWithNoPiiPolicy {
+ get {
+ return ResourceManager.GetString("PiiIncludedWithNoPiiPolicy", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to No personally identifiable information should be requested when the http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf PAPE policy is present..
+ /// </summary>
+ internal static string PiiRequestedWithNoPiiPolicy {
+ get {
+ return ResourceManager.GetString("PiiRequestedWithNoPiiPolicy", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Discovery on the Realm URL MUST be performed before sending a positive assertion..
+ /// </summary>
+ internal static string RealmDiscoveryNotPerformed {
+ get {
+ return ResourceManager.GetString("RealmDiscoveryNotPerformed", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The Realm in an authentication request must be an HTTPS URL..
+ /// </summary>
+ internal static string RealmMustBeHttps {
+ get {
+ return ResourceManager.GetString("RealmMustBeHttps", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The security profile {0} mandates security settings that have been changed to disallowed values..
+ /// </summary>
+ internal static string SecuritySettingsNotCompliantWithProfile {
+ get {
+ return ResourceManager.GetString("SecuritySettingsNotCompliantWithProfile", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.resx b/src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.resx
new file mode 100644
index 0000000..1c8fc7e
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/SecurityProfiles/SecurityProfileStrings.resx
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="PapeRequestMissingRequiredPolicies" xml:space="preserve">
+ <value>The PAPE request has an incomplete set of authentication policies.</value>
+ </data>
+ <data name="PapeResponseOrRequiredPoliciesMissing" xml:space="preserve">
+ <value>A PAPE response is missing or is missing required policies.</value>
+ </data>
+ <data name="PiiIncludedWithNoPiiPolicy" xml:space="preserve">
+ <value>No personally identifiable information should be included in authentication responses when the PAPE authentication policy http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf is present.</value>
+ </data>
+ <data name="PiiRequestedWithNoPiiPolicy" xml:space="preserve">
+ <value>No personally identifiable information should be requested when the http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf PAPE policy is present.</value>
+ </data>
+ <data name="RealmDiscoveryNotPerformed" xml:space="preserve">
+ <value>Discovery on the Realm URL MUST be performed before sending a positive assertion.</value>
+ </data>
+ <data name="RealmMustBeHttps" xml:space="preserve">
+ <value>The Realm in an authentication request must be an HTTPS URL.</value>
+ </data>
+ <data name="SecuritySettingsNotCompliantWithProfile" xml:space="preserve">
+ <value>The security profile {0} mandates security settings that have been changed to disallowed values.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth/OpenId/SecurityProfiles/USGovernmentLevel1.cs b/src/DotNetOpenAuth/OpenId/SecurityProfiles/USGovernmentLevel1.cs
new file mode 100644
index 0000000..04fa772
--- /dev/null
+++ b/src/DotNetOpenAuth/OpenId/SecurityProfiles/USGovernmentLevel1.cs
@@ -0,0 +1,282 @@
+//-----------------------------------------------------------------------
+// <copyright file="USGovernmentLevel1.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OpenId.SecurityProfiles {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OpenId.ChannelElements;
+ using DotNetOpenAuth.OpenId.Extensions.AttributeExchange;
+ using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy;
+ using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration;
+ using DotNetOpenAuth.OpenId.Provider;
+ using DotNetOpenAuth.OpenId.RelyingParty;
+
+ /// <summary>
+ /// Implements the GSA level 1 OpenID profile.
+ /// </summary>
+ /// <remarks>
+ /// <para>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. </para>
+ /// </remarks>
+ public sealed class USGovernmentLevel1 : IRelyingPartySecurityProfile, IProviderSecurityProfile {
+ /// <summary>
+ /// The maximum time a shared association can live.
+ /// </summary>
+ private static readonly TimeSpan MaximumAssociationLifetime = TimeSpan.FromSeconds(86400);
+
+ /// <summary>
+ /// Gets or sets a value indicating whether PII is allowed to be requested or received via OpenID.
+ /// </summary>
+ /// <value>The default value is <c>false</c>.</value>
+ public bool AllowPersonallyIdentifiableInformation { get; set; }
+
+ #region ISecurityProfile Members
+
+ /// <summary>
+ /// Applies a well known set of security requirements.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param>
+ /// <remarks>
+ /// Care should be taken to never decrease security when applying a profile.
+ /// Profiles should only enhance security requirements to avoid being
+ /// incompatible with each other.
+ /// </remarks>
+ void ISecurityProfile.ApplySecuritySettings(SecuritySettings securitySettings) {
+ ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings");
+
+ if (securitySettings.MaximumHashBitLength < 256) {
+ securitySettings.MaximumHashBitLength = 256;
+ }
+
+ var rpSecuritySettings = securitySettings as RelyingPartySecuritySettings;
+ if (rpSecuritySettings != null) {
+ rpSecuritySettings.RequireSsl = true;
+ rpSecuritySettings.RequireDirectedIdentity = true;
+ rpSecuritySettings.RequireAssociation = true;
+ rpSecuritySettings.RejectDelegatingIdentifiers = true;
+ rpSecuritySettings.IgnoreUnsignedExtensions = true;
+ rpSecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20;
+ }
+
+ var opSecuritySettings = securitySettings as ProviderSecuritySettings;
+ if (opSecuritySettings != null) {
+ opSecuritySettings.RequireSsl = true;
+ SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256, MaximumAssociationLifetime, opSecuritySettings);
+ SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1, MaximumAssociationLifetime, opSecuritySettings);
+ }
+ }
+
+ /// <summary>
+ /// Checks whether the given security settings comply with security requirements and throws otherwise.
+ /// </summary>
+ /// <param name="securitySettings">The security settings to check for compliance.</param>
+ /// <remarks>
+ /// Security settings should <em>not</em> be changed by this method. Any settings
+ /// that do not comply should cause an exception to be thrown.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the given security settings are not compliant with the requirements of this security profile.</exception>
+ void ISecurityProfile.EnsureCompliance(SecuritySettings securitySettings) {
+ ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings");
+ ErrorUtilities.VerifyProtocol(securitySettings.MaximumHashBitLength >= 256, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+
+ var rpSecuritySettings = securitySettings as RelyingPartySecuritySettings;
+ if (rpSecuritySettings != null) {
+ ErrorUtilities.VerifyProtocol(rpSecuritySettings.RequireSsl, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ ErrorUtilities.VerifyProtocol(rpSecuritySettings.RequireDirectedIdentity, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ ErrorUtilities.VerifyProtocol(rpSecuritySettings.RequireAssociation, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ ErrorUtilities.VerifyProtocol(rpSecuritySettings.IgnoreUnsignedExtensions, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ }
+
+ var opSecuritySettings = securitySettings as ProviderSecuritySettings;
+ if (opSecuritySettings != null) {
+ ErrorUtilities.VerifyProtocol(opSecuritySettings.RequireSsl, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ ErrorUtilities.VerifyProtocol(opSecuritySettings.AssociationLifetimes.ContainsKey(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256) && opSecuritySettings.AssociationLifetimes[Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256] <= MaximumAssociationLifetime, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ ErrorUtilities.VerifyProtocol(opSecuritySettings.AssociationLifetimes.ContainsKey(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1) && opSecuritySettings.AssociationLifetimes[Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1] <= MaximumAssociationLifetime, SecurityProfileStrings.SecuritySettingsNotCompliantWithProfile, this.GetType().Name);
+ }
+ }
+
+ #endregion
+
+ #region IRelyingPartySecurityProfile Members
+
+ /// <summary>
+ /// Called when an authentication request is about to be sent.
+ /// </summary>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="request">The request.</param>
+ void IRelyingPartySecurityProfile.OnOutgoingAuthenticationRequest(OpenIdRelyingParty relyingParty, RelyingParty.IAuthenticationRequest request) {
+ ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty");
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request;
+ ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal), SecurityProfileStrings.RealmMustBeHttps);
+
+ var pape = requestInternal.AppliedExtensions.OfType<PolicyRequest>().SingleOrDefault();
+ if (pape == null) {
+ request.AddExtension(pape = new PolicyRequest());
+ }
+
+ if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) {
+ pape.PreferredPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier);
+ }
+
+ if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) {
+ pape.PreferredPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1);
+ }
+
+ if (!this.AllowPersonallyIdentifiableInformation && !pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) {
+ pape.PreferredPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation);
+ }
+
+ if (pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) {
+ ErrorUtilities.VerifyProtocol(
+ (!requestInternal.AppliedExtensions.OfType<ClaimsRequest>().Any() &&
+ !requestInternal.AppliedExtensions.OfType<FetchRequest>().Any()),
+ SecurityProfileStrings.PiiIncludedWithNoPiiPolicy);
+ }
+ }
+
+ /// <summary>
+ /// Called when an incoming positive assertion is received.
+ /// </summary>
+ /// <param name="relyingParty">The relying party.</param>
+ /// <param name="assertion">The positive assertion.</param>
+ void IRelyingPartySecurityProfile.OnIncomingPositiveAssertion(OpenIdRelyingParty relyingParty, IAuthenticationResponse assertion) {
+ ErrorUtilities.VerifyArgumentNotNull(relyingParty, "relyingParty");
+ ErrorUtilities.VerifyArgumentNotNull(assertion, "assertion");
+
+ PolicyResponse pape = assertion.GetExtension<PolicyResponse>();
+ ErrorUtilities.VerifyProtocol(
+ pape != null &&
+ pape.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1) &&
+ pape.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier),
+ SecurityProfileStrings.PapeResponseOrRequiredPoliciesMissing);
+
+ ErrorUtilities.VerifyProtocol(this.AllowPersonallyIdentifiableInformation || pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation), SecurityProfileStrings.PapeResponseOrRequiredPoliciesMissing);
+
+ if (pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) {
+ ErrorUtilities.VerifyProtocol(
+ assertion.GetExtension<ClaimsResponse>() == null &&
+ assertion.GetExtension<FetchResponse>() == null,
+ SecurityProfileStrings.PiiIncludedWithNoPiiPolicy);
+ }
+ }
+
+ #endregion
+
+ #region IProviderSecurityProfile Members
+
+ /// <summary>
+ /// Called when a request is received by the Provider.
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ /// <param name="request">The incoming request.</param>
+ void IProviderSecurityProfile.OnIncomingRequest(OpenIdProvider provider, IRequest request) {
+ ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ var hostProcessedRequest = request as IHostProcessedRequest;
+ if (hostProcessedRequest != null) {
+ // Only apply our special policies if the RP requested it.
+ var papeRequest = request.GetExtension<PolicyRequest>();
+ 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), SecurityProfileStrings.PapeRequestMissingRequiredPolicies);
+ ErrorUtilities.VerifyProtocol(string.Equals(hostProcessedRequest.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal), SecurityProfileStrings.RealmMustBeHttps);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called when the Provider is preparing to send a response to an authentication request.
+ /// </summary>
+ /// <param name="provider">The provider.</param>
+ /// <param name="request">The request that is configured to generate the outgoing response.</param>
+ void IProviderSecurityProfile.OnOutgoingResponse(OpenIdProvider provider, Provider.IAuthenticationRequest request) {
+ ErrorUtilities.VerifyArgumentNotNull(provider, "provider");
+ ErrorUtilities.VerifyArgumentNotNull(request, "request");
+
+ // Nothing to do for negative assertions.
+ if (!request.IsAuthenticated.Value) {
+ return;
+ }
+
+ var requestInternal = (Provider.AuthenticationRequest)request;
+ var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response;
+
+ // Only apply our special policies if the RP requested it.
+ var papeRequest = request.GetExtension<PolicyRequest>();
+ if (papeRequest != null) {
+ var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault();
+ if (papeResponse == null) {
+ request.AddResponseExtension(papeResponse = new PolicyResponse());
+ }
+
+ if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) {
+ 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, SecurityProfileStrings.RealmDiscoveryNotPerformed);
+ }
+
+ if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) {
+ ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed);
+
+ // Generate a PPID from the ClaimedIdentifier.
+ throw new NotImplementedException();
+
+ // 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<ClaimsResponse>().Any() &&
+ !responseMessage.Extensions.OfType<FetchResponse>().Any(),
+ SecurityProfileStrings.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);
+ }
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Ensures the maximum association lifetime does not exceed a given limit.
+ /// </summary>
+ /// <param name="associationType">Type of the association.</param>
+ /// <param name="maximumLifetime">The maximum lifetime.</param>
+ /// <param name="securitySettings">The security settings to adjust.</param>
+ private static void SetMaximumAssociationLifetimeToNotExceed(string associationType, TimeSpan maximumLifetime, ProviderSecuritySettings securitySettings) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType));
+ Contract.Requires<ArgumentOutOfRangeException>(maximumLifetime.TotalSeconds > 0);
+ if (!securitySettings.AssociationLifetimes.ContainsKey(associationType) ||
+ securitySettings.AssociationLifetimes[associationType] > maximumLifetime) {
+ securitySettings.AssociationLifetimes[associationType] = maximumLifetime;
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs
index a44b5fe..43533c7 100644
--- a/src/DotNetOpenAuth/OpenId/SecuritySettings.cs
+++ b/src/DotNetOpenAuth/OpenId/SecuritySettings.cs
@@ -5,12 +5,14 @@
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.OpenId {
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
using DotNetOpenAuth.Messaging;
/// <summary>
/// Security settings that may be applicable to both relying parties and providers.
/// </summary>
- public class SecuritySettings {
+ public abstract class SecuritySettings {
/// <summary>
/// Gets the default minimum hash bit length.
/// </summary>
@@ -63,6 +65,11 @@ namespace DotNetOpenAuth.OpenId {
public int MaximumHashBitLength { get; set; }
/// <summary>
+ /// Gets the custom security profiles that apply to these security settings.
+ /// </summary>
+ internal abstract IEnumerable<ISecurityProfile> CustomSecurityProfiles { get; }
+
+ /// <summary>
/// Determines whether a named association fits the security requirements.
/// </summary>
/// <param name="protocol">The protocol carrying the association.</param>
@@ -86,5 +93,25 @@ namespace DotNetOpenAuth.OpenId {
ErrorUtilities.VerifyArgumentNotNull(association, "association");
return association.HashBitLength >= this.MinimumHashBitLength && association.HashBitLength <= this.MaximumHashBitLength;
}
+
+ /// <summary>
+ /// Ensures that all security profiles are satisfied.
+ /// </summary>
+ internal void EnsureSecurityProfilesSatisfied() {
+ foreach (ISecurityProfile profile in this.CustomSecurityProfiles) {
+ profile.EnsureCompliance(this);
+ }
+ }
+
+ /// <summary>
+ /// Called by derived classes when security profiles are added or removed.
+ /// </summary>
+ /// <param name="sender">The collection being modified.</param>
+ /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
+ protected void OnSecurityProfilesChanged(object sender, NotifyCollectionChangedEventArgs e) {
+ foreach (ISecurityProfile profile in e.NewItems) {
+ profile.ApplySecuritySettings(this);
+ }
+ }
}
}