diff options
165 files changed, 4097 insertions, 2477 deletions
diff --git a/projecttemplates/RelyingPartyDatabase/Properties/Database.sqlsettings b/projecttemplates/RelyingPartyDatabase/Properties/Database.sqlsettings index 5f3b26c..f83aff5 100644 --- a/projecttemplates/RelyingPartyDatabase/Properties/Database.sqlsettings +++ b/projecttemplates/RelyingPartyDatabase/Properties/Database.sqlsettings @@ -38,9 +38,10 @@ <Recovery>SIMPLE</Recovery> <RecursiveTriggersEnabled>False</RecursiveTriggersEnabled> <ServiceBrokerOption>DisableBroker</ServiceBrokerOption> + <SupplementalLoggingOn>False</SupplementalLoggingOn> <TornPageDetection>False</TornPageDetection> <Trustworthy>False</Trustworthy> <UpdateOptions>READ_WRITE</UpdateOptions> - <VardecimalStorageFormatOn>False</VardecimalStorageFormatOn> + <VardecimalStorageFormatOn>True</VardecimalStorageFormatOn> </Properties> </CatalogProperties>
\ No newline at end of file diff --git a/projecttemplates/RelyingPartyDatabase/RelyingPartyDatabase.dbproj b/projecttemplates/RelyingPartyDatabase/RelyingPartyDatabase.dbproj index 3df061c..b47ce94 100644 --- a/projecttemplates/RelyingPartyDatabase/RelyingPartyDatabase.dbproj +++ b/projecttemplates/RelyingPartyDatabase/RelyingPartyDatabase.dbproj @@ -144,7 +144,7 @@ <AnsiNulls>On</AnsiNulls> <QuotedIdentifier>On</QuotedIdentifier> </Build> - <Build Include="Schema Objects\Schemas\dbo\Programmability\Stored Procedures\ClearExpiredAssociations.proc.sql"> + <Build Include="Schema Objects\Schemas\dbo\Programmability\Stored Procedures\ClearExpiredCryptoKeys.proc.sql"> <SubType>Code</SubType> <AnsiNulls>On</AnsiNulls> <QuotedIdentifier>On</QuotedIdentifier> @@ -168,12 +168,6 @@ <Build Include="Schema Objects\Schemas\dbo\Tables\Constraints\DF_AuthenticationToken_UsageCount.defconst.sql"> <SubType>Code</SubType> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Constraints\DF_IssuedToken_CreatedOn.defconst.sql"> - <SubType>Code</SubType> - </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Constraints\DF_IssuedToken_IsAccessToken.defconst.sql"> - <SubType>Code</SubType> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Constraints\DF_Nonce_Issued.defconst.sql"> <SubType>Code</SubType> </Build> @@ -183,40 +177,15 @@ <Build Include="Schema Objects\Schemas\dbo\Tables\Constraints\DF_User_EmailAddressVerified.defconst.sql"> <SubType>Code</SubType> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Consumer.table.sql"> - <SubType>Code</SubType> - <AnsiNulls>On</AnsiNulls> - <QuotedIdentifier>On</QuotedIdentifier> - </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Indexes\IX_Consumer.index.sql"> - <SubType>Code</SubType> - </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Indexes\IX_IssuedToken.index.sql"> - <SubType>Code</SubType> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Indexes\IX_Nonce_Code.index.sql"> <SubType>Code</SubType> </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Indexes\IX_Nonce_Expires.index.sql"> <SubType>Code</SubType> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Indexes\IX_OpenIDAssociations.index.sql"> - <SubType>Code</SubType> - </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\IssuedToken.table.sql"> - <SubType>Code</SubType> - <AnsiNulls>On</AnsiNulls> - <QuotedIdentifier>On</QuotedIdentifier> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\FK_AuthenticationToken_User.fkey.sql"> <SubType>Code</SubType> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\FK_IssuedToken_Consumer.fkey.sql"> - <SubType>Code</SubType> - </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\FK_IssuedToken_User.fkey.sql"> - <SubType>Code</SubType> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\FK_UserRole_Role.fkey.sql"> <SubType>Code</SubType> </Build> @@ -226,18 +195,9 @@ <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_AuthenticationToken.pkey.sql"> <SubType>Code</SubType> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_Consumer.pkey.sql"> - <SubType>Code</SubType> - </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_IssuedToken.pkey.sql"> - <SubType>Code</SubType> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_Nonce.pkey.sql"> <SubType>Code</SubType> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_OpenIDAssociations.pkey.sql"> - <SubType>Code</SubType> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_Role.pkey.sql"> <SubType>Code</SubType> </Build> @@ -255,11 +215,6 @@ <AnsiNulls>On</AnsiNulls> <QuotedIdentifier>On</QuotedIdentifier> </Build> - <Build Include="Schema Objects\Schemas\dbo\Tables\OpenIDAssociation.table.sql"> - <SubType>Code</SubType> - <AnsiNulls>On</AnsiNulls> - <QuotedIdentifier>On</QuotedIdentifier> - </Build> <Build Include="Schema Objects\Schemas\dbo\Tables\Role.table.sql"> <SubType>Code</SubType> <AnsiNulls>On</AnsiNulls> @@ -319,6 +274,17 @@ <AnsiNulls>On</AnsiNulls> <QuotedIdentifier>On</QuotedIdentifier> </Build> + <Build Include="Schema Objects\Schemas\dbo\Tables\Indexes\IX_CryptoKeys.index.sql"> + <SubType>Code</SubType> + </Build> + <Build Include="Schema Objects\Schemas\dbo\Tables\Keys\PK_CryptoKeys.pkey.sql"> + <SubType>Code</SubType> + </Build> + <Build Include="Schema Objects\Schemas\dbo\Tables\CryptoKey.table.sql"> + <SubType>Code</SubType> + <AnsiNulls>On</AnsiNulls> + <QuotedIdentifier>On</QuotedIdentifier> + </Build> </ItemGroup> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Programmability/Stored Procedures/ClearExpiredAssociations.proc.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Programmability/Stored Procedures/ClearExpiredAssociations.proc.sql deleted file mode 100644 index 6a143d0..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Programmability/Stored Procedures/ClearExpiredAssociations.proc.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE PROCEDURE dbo.ClearExpiredAssociations -AS - -DELETE FROM dbo.OpenIDAssociation -WHERE [Expiration] < getutcdate() diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Programmability/Stored Procedures/ClearExpiredCryptoKeys.proc.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Programmability/Stored Procedures/ClearExpiredCryptoKeys.proc.sql new file mode 100644 index 0000000..777ba9b --- /dev/null +++ b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Programmability/Stored Procedures/ClearExpiredCryptoKeys.proc.sql @@ -0,0 +1,5 @@ +CREATE PROCEDURE dbo.ClearExpiredCryptoKeys +AS + +DELETE FROM dbo.CryptoKey +WHERE [Expiration] < getutcdate() diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Constraints/DF_IssuedToken_CreatedOn.defconst.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Constraints/DF_IssuedToken_CreatedOn.defconst.sql deleted file mode 100644 index 28f59be..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Constraints/DF_IssuedToken_CreatedOn.defconst.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*ALTER TABLE [dbo].[IssuedToken] - ADD CONSTRAINT [DF_IssuedToken_CreatedOn] DEFAULT (getutcdate()) FOR [CreatedOn];*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Constraints/DF_IssuedToken_IsAccessToken.defconst.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Constraints/DF_IssuedToken_IsAccessToken.defconst.sql deleted file mode 100644 index cf3ec08..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Constraints/DF_IssuedToken_IsAccessToken.defconst.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*ALTER TABLE [dbo].[IssuedToken] - ADD CONSTRAINT [DF_IssuedToken_IsAccessToken] DEFAULT ((0)) FOR [IsAccessToken];*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Consumer.table.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Consumer.table.sql deleted file mode 100644 index ab14759..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Consumer.table.sql +++ /dev/null @@ -1,11 +0,0 @@ -/*CREATE TABLE [dbo].[Consumer] ( - [ConsumerId] INT IDENTITY (1, 1) NOT NULL, - [ConsumerKey] NVARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, - [ConsumerSecret] NVARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NULL, - [X509Certificate] IMAGE NULL, - [Callback] NVARCHAR (2048) NULL, - [VerificationCodeFormat] INT NOT NULL, - [VerificationCodeLength] INT NOT NULL, - [Name] NVARCHAR (50) NULL -);*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/CryptoKey.table.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/CryptoKey.table.sql new file mode 100644 index 0000000..a5af46c --- /dev/null +++ b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/CryptoKey.table.sql @@ -0,0 +1,10 @@ +CREATE TABLE [dbo].[CryptoKey] ( + [CryptoKeyId] INT IDENTITY (1, 1) NOT NULL, + [Bucket] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, + [Handle] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, + [Expiration] DATETIME NOT NULL, + [Secret] VARBINARY (4096) NOT NULL +); + + + diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_Consumer.index.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_Consumer.index.sql deleted file mode 100644 index d469e71..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_Consumer.index.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*CREATE UNIQUE NONCLUSTERED INDEX [IX_Consumer] - ON [dbo].[Consumer]([ConsumerKey] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, MAXDOP = 0);*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_CryptoKeys.index.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_CryptoKeys.index.sql new file mode 100644 index 0000000..bd8876e --- /dev/null +++ b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_CryptoKeys.index.sql @@ -0,0 +1,4 @@ +CREATE UNIQUE NONCLUSTERED INDEX [IX_CryptoKeys] + ON [dbo].[CryptoKey]([Bucket] ASC, [Handle] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, MAXDOP = 0) + ON [PRIMARY]; + diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_IssuedToken.index.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_IssuedToken.index.sql deleted file mode 100644 index f2f59e1..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_IssuedToken.index.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*CREATE UNIQUE NONCLUSTERED INDEX [IX_IssuedToken] - ON [dbo].[IssuedToken]([Token] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, MAXDOP = 0);*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_OpenIDAssociations.index.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_OpenIDAssociations.index.sql deleted file mode 100644 index c137af6..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Indexes/IX_OpenIDAssociations.index.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE UNIQUE NONCLUSTERED INDEX [IX_OpenIDAssociations] - ON [dbo].[OpenIDAssociation]([DistinguishingFactor] ASC, [AssociationHandle] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, MAXDOP = 0); - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/IssuedToken.table.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/IssuedToken.table.sql deleted file mode 100644 index ee0ffdf..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/IssuedToken.table.sql +++ /dev/null @@ -1,15 +0,0 @@ -/*CREATE TABLE [dbo].[IssuedToken] ( - [IssuedTokenId] INT IDENTITY (1, 1) NOT NULL, - [ConsumerId] INT NOT NULL, - [UserId] INT NULL, - [Token] NVARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, - [TokenSecret] NVARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, - [CreatedOn] DATETIME NOT NULL, - [Callback] NVARCHAR (2048) NULL, - [VerificationCode] NVARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NULL, - [ConsumerVersion] VARCHAR (10) NULL, - [ExpirationDate] DATETIME NULL, - [IsAccessToken] BIT NOT NULL, - [Scope] NVARCHAR (255) NULL -);*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/FK_IssuedToken_Consumer.fkey.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/FK_IssuedToken_Consumer.fkey.sql deleted file mode 100644 index fe25092..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/FK_IssuedToken_Consumer.fkey.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*ALTER TABLE [dbo].[IssuedToken] - ADD CONSTRAINT [FK_IssuedToken_Consumer] FOREIGN KEY ([ConsumerId]) REFERENCES [dbo].[Consumer] ([ConsumerId]) ON DELETE CASCADE ON UPDATE CASCADE;*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/FK_IssuedToken_User.fkey.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/FK_IssuedToken_User.fkey.sql deleted file mode 100644 index 3b9bbc1..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/FK_IssuedToken_User.fkey.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*ALTER TABLE [dbo].[IssuedToken] - ADD CONSTRAINT [FK_IssuedToken_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([UserId]) ON DELETE CASCADE ON UPDATE CASCADE;*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_Consumer.pkey.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_Consumer.pkey.sql deleted file mode 100644 index 181fdf5..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_Consumer.pkey.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*ALTER TABLE [dbo].[Consumer] - ADD CONSTRAINT [PK_Consumer] PRIMARY KEY CLUSTERED ([ConsumerId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF);*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_CryptoKeys.pkey.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_CryptoKeys.pkey.sql new file mode 100644 index 0000000..ebe7f67 --- /dev/null +++ b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_CryptoKeys.pkey.sql @@ -0,0 +1,3 @@ +ALTER TABLE [dbo].[CryptoKey] + ADD CONSTRAINT [PK_CryptoKeys] PRIMARY KEY CLUSTERED ([CryptoKeyId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); + diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_IssuedToken.pkey.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_IssuedToken.pkey.sql deleted file mode 100644 index 931fa54..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_IssuedToken.pkey.sql +++ /dev/null @@ -1,3 +0,0 @@ -/*ALTER TABLE [dbo].[IssuedToken] - ADD CONSTRAINT [PK_IssuedToken] PRIMARY KEY CLUSTERED ([IssuedTokenId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF);*/ - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_OpenIDAssociations.pkey.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_OpenIDAssociations.pkey.sql deleted file mode 100644 index cdadaf7..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/Keys/PK_OpenIDAssociations.pkey.sql +++ /dev/null @@ -1,3 +0,0 @@ -ALTER TABLE [dbo].[OpenIDAssociation] - ADD CONSTRAINT [PK_OpenIDAssociations] PRIMARY KEY CLUSTERED ([AssociationId] ASC) WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF); - diff --git a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/OpenIDAssociation.table.sql b/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/OpenIDAssociation.table.sql deleted file mode 100644 index bbcf527..0000000 --- a/projecttemplates/RelyingPartyDatabase/Schema Objects/Schemas/dbo/Tables/OpenIDAssociation.table.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE [dbo].[OpenIDAssociation] ( - [AssociationId] INT IDENTITY (1, 1) NOT NULL, - [DistinguishingFactor] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, - [AssociationHandle] VARCHAR (255) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, - [Expiration] DATETIME NOT NULL, - [PrivateData] BINARY (64) NOT NULL, - [PrivateDataLength] INT NOT NULL -); - diff --git a/projecttemplates/RelyingPartyLogic/Model.Designer.cs b/projecttemplates/RelyingPartyLogic/Model.Designer.cs index 6f237a1..fe9e43b 100644 --- a/projecttemplates/RelyingPartyLogic/Model.Designer.cs +++ b/projecttemplates/RelyingPartyLogic/Model.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.1 +// Runtime Version:4.0.30319.225 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -15,7 +15,7 @@ [assembly: global::System.Data.Objects.DataClasses.EdmRelationshipAttribute("DatabaseModel", "FK_IssuedToken_User", "User", global::System.Data.Metadata.Edm.RelationshipMultiplicity.One, typeof(RelyingPartyLogic.User), "ClientAuthorization", global::System.Data.Metadata.Edm.RelationshipMultiplicity.Many, typeof(RelyingPartyLogic.ClientAuthorization))] // Original file name: -// Generation date: 7/22/2010 11:15:43 AM +// Generation date: 5/20/2011 5:13:47 PM namespace RelyingPartyLogic { @@ -118,23 +118,6 @@ namespace RelyingPartyLogic [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] private global::System.Data.Objects.ObjectQuery<Nonce> _Nonces; /// <summary> - /// There are no comments for OpenIdAssociations in the schema. - /// </summary> - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public global::System.Data.Objects.ObjectQuery<OpenIdAssociation> OpenIdAssociations - { - get - { - if ((this._OpenIdAssociations == null)) - { - this._OpenIdAssociations = base.CreateQuery<OpenIdAssociation>("[OpenIdAssociations]"); - } - return this._OpenIdAssociations; - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private global::System.Data.Objects.ObjectQuery<OpenIdAssociation> _OpenIdAssociations; - /// <summary> /// There are no comments for Clients in the schema. /// </summary> [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] @@ -169,6 +152,23 @@ namespace RelyingPartyLogic [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] private global::System.Data.Objects.ObjectQuery<ClientAuthorization> _ClientAuthorizations; /// <summary> + /// There are no comments for SymmetricCryptoKeys in the schema. + /// </summary> + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public global::System.Data.Objects.ObjectQuery<SymmetricCryptoKey> SymmetricCryptoKeys + { + get + { + if ((this._SymmetricCryptoKeys == null)) + { + this._SymmetricCryptoKeys = base.CreateQuery<SymmetricCryptoKey>("[SymmetricCryptoKeys]"); + } + return this._SymmetricCryptoKeys; + } + } + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + private global::System.Data.Objects.ObjectQuery<SymmetricCryptoKey> _SymmetricCryptoKeys; + /// <summary> /// There are no comments for Roles in the schema. /// </summary> [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] @@ -201,14 +201,6 @@ namespace RelyingPartyLogic base.AddObject("Nonces", nonce); } /// <summary> - /// There are no comments for OpenIdAssociations in the schema. - /// </summary> - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public void AddToOpenIdAssociations(OpenIdAssociation openIdAssociation) - { - base.AddObject("OpenIdAssociations", openIdAssociation); - } - /// <summary> /// There are no comments for Clients in the schema. /// </summary> [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] @@ -224,6 +216,14 @@ namespace RelyingPartyLogic { base.AddObject("ClientAuthorizations", clientAuthorization); } + /// <summary> + /// There are no comments for SymmetricCryptoKeys in the schema. + /// </summary> + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public void AddToSymmetricCryptoKeys(SymmetricCryptoKey symmetricCryptoKey) + { + base.AddObject("SymmetricCryptoKeys", symmetricCryptoKey); + } } /// <summary> /// There are no comments for DatabaseModel.AuthenticationToken in the schema. @@ -980,201 +980,6 @@ namespace RelyingPartyLogic partial void OnExpiresUtcChanged(); } /// <summary> - /// There are no comments for DatabaseModel.OpenIdAssociation in the schema. - /// </summary> - /// <KeyProperties> - /// AssociationId - /// </KeyProperties> - [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="OpenIdAssociation")] - [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] - [global::System.Serializable()] - public partial class OpenIdAssociation : global::System.Data.Objects.DataClasses.EntityObject - { - /// <summary> - /// Create a new OpenIdAssociation object. - /// </summary> - /// <param name="associationId">Initial value of AssociationId.</param> - /// <param name="distinguishingFactor">Initial value of DistinguishingFactor.</param> - /// <param name="associationHandle">Initial value of AssociationHandle.</param> - /// <param name="expirationUtc">Initial value of ExpirationUtc.</param> - /// <param name="privateData">Initial value of PrivateData.</param> - /// <param name="privateDataLength">Initial value of PrivateDataLength.</param> - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public static OpenIdAssociation CreateOpenIdAssociation(int associationId, string distinguishingFactor, string associationHandle, global::System.DateTime expirationUtc, byte[] privateData, int privateDataLength) - { - OpenIdAssociation openIdAssociation = new OpenIdAssociation(); - openIdAssociation.AssociationId = associationId; - openIdAssociation.DistinguishingFactor = distinguishingFactor; - openIdAssociation.AssociationHandle = associationHandle; - openIdAssociation.ExpirationUtc = expirationUtc; - openIdAssociation.PrivateData = privateData; - openIdAssociation.PrivateDataLength = privateDataLength; - return openIdAssociation; - } - /// <summary> - /// There are no comments for property AssociationId in the schema. - /// </summary> - [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)] - [global::System.Runtime.Serialization.DataMemberAttribute()] - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public int AssociationId - { - get - { - return this._AssociationId; - } - set - { - this.OnAssociationIdChanging(value); - this.ReportPropertyChanging("AssociationId"); - this._AssociationId = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); - this.ReportPropertyChanged("AssociationId"); - this.OnAssociationIdChanged(); - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private int _AssociationId; - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnAssociationIdChanging(int value); - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnAssociationIdChanged(); - /// <summary> - /// Gets or sets the Provider Endpoint URL the association is with. - /// </summary> - [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] - [global::System.Runtime.Serialization.DataMemberAttribute()] - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public string DistinguishingFactor - { - get - { - return this._DistinguishingFactor; - } - set - { - this.OnDistinguishingFactorChanging(value); - this.ReportPropertyChanging("DistinguishingFactor"); - this._DistinguishingFactor = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); - this.ReportPropertyChanged("DistinguishingFactor"); - this.OnDistinguishingFactorChanged(); - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private string _DistinguishingFactor; - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnDistinguishingFactorChanging(string value); - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnDistinguishingFactorChanged(); - /// <summary> - /// There are no comments for property AssociationHandle in the schema. - /// </summary> - [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] - [global::System.Runtime.Serialization.DataMemberAttribute()] - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public string AssociationHandle - { - get - { - return this._AssociationHandle; - } - set - { - this.OnAssociationHandleChanging(value); - this.ReportPropertyChanging("AssociationHandle"); - this._AssociationHandle = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); - this.ReportPropertyChanged("AssociationHandle"); - this.OnAssociationHandleChanged(); - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private string _AssociationHandle; - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnAssociationHandleChanging(string value); - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnAssociationHandleChanged(); - /// <summary> - /// There are no comments for property ExpirationUtc in the schema. - /// </summary> - [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] - [global::System.Runtime.Serialization.DataMemberAttribute()] - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public global::System.DateTime ExpirationUtc - { - get - { - return this._ExpirationUtc; - } - set - { - this.OnExpirationUtcChanging(value); - this.ReportPropertyChanging("ExpirationUtc"); - this._ExpirationUtc = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); - this.ReportPropertyChanged("ExpirationUtc"); - this.OnExpirationUtcChanged(); - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private global::System.DateTime _ExpirationUtc; - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnExpirationUtcChanging(global::System.DateTime value); - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnExpirationUtcChanged(); - /// <summary> - /// There are no comments for property PrivateData in the schema. - /// </summary> - [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] - [global::System.Runtime.Serialization.DataMemberAttribute()] - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public byte[] PrivateData - { - get - { - return global::System.Data.Objects.DataClasses.StructuralObject.GetValidValue(this._PrivateData); - } - set - { - this.OnPrivateDataChanging(value); - this.ReportPropertyChanging("PrivateData"); - this._PrivateData = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); - this.ReportPropertyChanged("PrivateData"); - this.OnPrivateDataChanged(); - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private byte[] _PrivateData; - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnPrivateDataChanging(byte[] value); - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnPrivateDataChanged(); - /// <summary> - /// There are no comments for property PrivateDataLength in the schema. - /// </summary> - [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] - [global::System.Runtime.Serialization.DataMemberAttribute()] - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - public int PrivateDataLength - { - get - { - return this._PrivateDataLength; - } - set - { - this.OnPrivateDataLengthChanging(value); - this.ReportPropertyChanging("PrivateDataLength"); - this._PrivateDataLength = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); - this.ReportPropertyChanged("PrivateDataLength"); - this.OnPrivateDataLengthChanged(); - } - } - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - private int _PrivateDataLength; - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnPrivateDataLengthChanging(int value); - [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] - partial void OnPrivateDataLengthChanged(); - } - /// <summary> /// There are no comments for DatabaseModel.Client in the schema. /// </summary> /// <KeyProperties> @@ -1569,4 +1374,170 @@ namespace RelyingPartyLogic } } } + /// <summary> + /// There are no comments for DatabaseModel.SymmetricCryptoKey in the schema. + /// </summary> + /// <KeyProperties> + /// CryptoKeyId + /// </KeyProperties> + [global::System.Data.Objects.DataClasses.EdmEntityTypeAttribute(NamespaceName="DatabaseModel", Name="SymmetricCryptoKey")] + [global::System.Runtime.Serialization.DataContractAttribute(IsReference=true)] + [global::System.Serializable()] + public partial class SymmetricCryptoKey : global::System.Data.Objects.DataClasses.EntityObject + { + /// <summary> + /// Create a new SymmetricCryptoKey object. + /// </summary> + /// <param name="cryptoKeyId">Initial value of CryptoKeyId.</param> + /// <param name="bucket">Initial value of Bucket.</param> + /// <param name="handle">Initial value of Handle.</param> + /// <param name="expirationUtc">Initial value of ExpirationUtc.</param> + /// <param name="secret">Initial value of Secret.</param> + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public static SymmetricCryptoKey CreateSymmetricCryptoKey(int cryptoKeyId, string bucket, string handle, global::System.DateTime expirationUtc, byte[] secret) + { + SymmetricCryptoKey symmetricCryptoKey = new SymmetricCryptoKey(); + symmetricCryptoKey.CryptoKeyId = cryptoKeyId; + symmetricCryptoKey.Bucket = bucket; + symmetricCryptoKey.Handle = handle; + symmetricCryptoKey.ExpirationUtc = expirationUtc; + symmetricCryptoKey.Secret = secret; + return symmetricCryptoKey; + } + /// <summary> + /// There are no comments for property CryptoKeyId in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public int CryptoKeyId + { + get + { + return this._CryptoKeyId; + } + set + { + this.OnCryptoKeyIdChanging(value); + this.ReportPropertyChanging("CryptoKeyId"); + this._CryptoKeyId = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("CryptoKeyId"); + this.OnCryptoKeyIdChanged(); + } + } + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + private int _CryptoKeyId; + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnCryptoKeyIdChanging(int value); + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnCryptoKeyIdChanged(); + /// <summary> + /// There are no comments for property Bucket in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public string Bucket + { + get + { + return this._Bucket; + } + set + { + this.OnBucketChanging(value); + this.ReportPropertyChanging("Bucket"); + this._Bucket = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("Bucket"); + this.OnBucketChanged(); + } + } + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + private string _Bucket; + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnBucketChanging(string value); + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnBucketChanged(); + /// <summary> + /// There are no comments for property Handle in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public string Handle + { + get + { + return this._Handle; + } + set + { + this.OnHandleChanging(value); + this.ReportPropertyChanging("Handle"); + this._Handle = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("Handle"); + this.OnHandleChanged(); + } + } + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + private string _Handle; + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnHandleChanging(string value); + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnHandleChanged(); + /// <summary> + /// There are no comments for property ExpirationUtc in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public global::System.DateTime ExpirationUtc + { + get + { + return this._ExpirationUtc; + } + set + { + this.OnExpirationUtcChanging(value); + this.ReportPropertyChanging("ExpirationUtc"); + this._ExpirationUtc = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value); + this.ReportPropertyChanged("ExpirationUtc"); + this.OnExpirationUtcChanged(); + } + } + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + private global::System.DateTime _ExpirationUtc; + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnExpirationUtcChanging(global::System.DateTime value); + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnExpirationUtcChanged(); + /// <summary> + /// There are no comments for property Secret in the schema. + /// </summary> + [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)] + [global::System.Runtime.Serialization.DataMemberAttribute()] + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + public byte[] Secret + { + get + { + return global::System.Data.Objects.DataClasses.StructuralObject.GetValidValue(this._Secret); + } + set + { + this.OnSecretChanging(value); + this.ReportPropertyChanging("Secret"); + this._Secret = global::System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value, false); + this.ReportPropertyChanged("Secret"); + this.OnSecretChanged(); + } + } + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + private byte[] _Secret; + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnSecretChanging(byte[] value); + [global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")] + partial void OnSecretChanged(); + } } diff --git a/projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs b/projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs deleted file mode 100644 index 94ab09a..0000000 --- a/projecttemplates/RelyingPartyLogic/Model.OpenIdAssociation.cs +++ /dev/null @@ -1,18 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="Model.OpenIdAssociation.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace RelyingPartyLogic { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - public partial class OpenIdAssociation { - partial void OnPrivateDataChanged() { - this.PrivateDataLength = this.PrivateData.Length; - } - } -} diff --git a/projecttemplates/RelyingPartyLogic/Model.cs b/projecttemplates/RelyingPartyLogic/Model.cs index ca87c90..77d1b83 100644 --- a/projecttemplates/RelyingPartyLogic/Model.cs +++ b/projecttemplates/RelyingPartyLogic/Model.cs @@ -27,8 +27,8 @@ namespace RelyingPartyLogic { /// Clears the expired associations. /// </summary> /// <param name="transaction">The transaction to use, if any.</param> - internal void ClearExpiredAssociations(EntityTransaction transaction) { - this.ExecuteCommand(transaction, "DatabaseEntities.ClearExpiredAssociations"); + internal void ClearExpiredCryptoKeys(EntityTransaction transaction) { + this.ExecuteCommand(transaction, "DatabaseEntities.ClearExpiredCryptoKeys"); } } } diff --git a/projecttemplates/RelyingPartyLogic/Model.edmx b/projecttemplates/RelyingPartyLogic/Model.edmx index fd9f0fb..fa8d570 100644 --- a/projecttemplates/RelyingPartyLogic/Model.edmx +++ b/projecttemplates/RelyingPartyLogic/Model.edmx @@ -4,13 +4,13 @@ <edmx:Runtime> <!-- SSDL content --> <edmx:StorageModels> - <Schema Namespace="DatabaseModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl"> + <Schema Namespace="DatabaseModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl"> <EntityContainer Name="DatabaseModelStoreContainer"> <EntitySet Name="AuthenticationToken" EntityType="DatabaseModel.Store.AuthenticationToken" store:Type="Tables" Schema="dbo" /> <EntitySet Name="Client" EntityType="DatabaseModel.Store.Client" store:Type="Tables" Schema="dbo" /> <EntitySet Name="ClientAuthorization" EntityType="DatabaseModel.Store.ClientAuthorization" store:Type="Tables" Schema="dbo" /> + <EntitySet Name="CryptoKey" EntityType="DatabaseModel.Store.CryptoKey" store:Type="Tables" Schema="dbo" /> <EntitySet Name="Nonce" EntityType="DatabaseModel.Store.Nonce" store:Type="Tables" Schema="dbo" /> - <EntitySet Name="OpenIDAssociation" EntityType="DatabaseModel.Store.OpenIDAssociation" store:Type="Tables" Schema="dbo" /> <EntitySet Name="Role" EntityType="DatabaseModel.Store.Role" store:Type="Tables" Schema="dbo" /> <EntitySet Name="User" EntityType="DatabaseModel.Store.User" store:Type="Tables" Schema="dbo" /> <EntitySet Name="UserRole" EntityType="DatabaseModel.Store.UserRole" store:Type="Tables" Schema="dbo" /> @@ -68,6 +68,16 @@ <Property Name="ExpirationDate" Type="datetime" /> <Property Name="Scope" Type="varchar" MaxLength="2048" /> </EntityType> + <EntityType Name="CryptoKey"> + <Key> + <PropertyRef Name="CryptoKeyId" /> + </Key> + <Property Name="CryptoKeyId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> + <Property Name="Bucket" Type="varchar" Nullable="false" MaxLength="255" /> + <Property Name="Handle" Type="varchar" Nullable="false" MaxLength="255" /> + <Property Name="Expiration" Type="datetime" Nullable="false" /> + <Property Name="Secret" Type="varbinary" Nullable="false" MaxLength="4096" /> + </EntityType> <EntityType Name="Nonce"> <Key> <PropertyRef Name="NonceId" /> @@ -78,17 +88,6 @@ <Property Name="Issued" Type="datetime" Nullable="false" /> <Property Name="Expires" Type="datetime" Nullable="false" /> </EntityType> - <EntityType Name="OpenIDAssociation"> - <Key> - <PropertyRef Name="AssociationId" /> - </Key> - <Property Name="AssociationId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" /> - <Property Name="DistinguishingFactor" Type="varchar" Nullable="false" MaxLength="255" /> - <Property Name="AssociationHandle" Type="varchar" Nullable="false" MaxLength="255" /> - <Property Name="Expiration" Type="datetime" Nullable="false" /> - <Property Name="PrivateData" Type="binary" Nullable="false" MaxLength="64" /> - <Property Name="PrivateDataLength" Type="int" Nullable="false" /> - </EntityType> <EntityType Name="Role"> <Key> <PropertyRef Name="RoleId" /> @@ -185,7 +184,7 @@ </Dependent> </ReferentialConstraint> </Association> - <Function Name="ClearExpiredAssociations" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" /> + <Function Name="ClearExpiredCryptoKeys" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" /> <Function Name="ClearExpiredNonces" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="dbo" /> </Schema></edmx:StorageModels> <!-- CSDL content --> @@ -203,7 +202,6 @@ <End Role="User" EntitySet="Users" /> <End Role="AuthenticationToken" EntitySet="AuthenticationTokens" /></AssociationSet> <EntitySet Name="Nonces" EntityType="DatabaseModel.Nonce" /> - <EntitySet Name="OpenIdAssociations" EntityType="DatabaseModel.OpenIdAssociation" /> <FunctionImport Name="ClearExpiredNonces" /> <EntitySet Name="Clients" EntityType="DatabaseModel.Client" /> <EntitySet Name="ClientAuthorizations" EntityType="DatabaseModel.ClientAuthorization" /> @@ -215,6 +213,7 @@ <End Role="User" EntitySet="Users" /> <End Role="ClientAuthorization" EntitySet="ClientAuthorizations" /> </AssociationSet> + <EntitySet Name="SymmetricCryptoKeys" EntityType="DatabaseModel.SymmetricCryptoKey" /> </EntityContainer> <EntityType Name="AuthenticationToken" Abstract="false"> <Key> @@ -265,17 +264,6 @@ <Property Name="Code" Type="String" Nullable="false" /> <Property Name="IssuedUtc" Type="DateTime" Nullable="false" /> <Property Name="ExpiresUtc" Type="DateTime" Nullable="false" /></EntityType> - <EntityType Name="OpenIdAssociation" a:TypeAccess="Public" xmlns:a="http://schemas.microsoft.com/ado/2006/04/codegeneration"> - <Key> - <PropertyRef Name="AssociationId" /></Key> - <Property Name="AssociationId" Type="Int32" Nullable="false" /> - <Property Name="DistinguishingFactor" Type="String" Nullable="false"> - <Documentation> - <Summary>Gets or sets the Provider Endpoint URL the association is with.</Summary></Documentation></Property> - <Property Name="AssociationHandle" Type="String" Nullable="false" /> - <Property Name="ExpirationUtc" Type="DateTime" Nullable="false" /> - <Property Name="PrivateData" Type="Binary" Nullable="false" /> - <Property Name="PrivateDataLength" Type="Int32" Nullable="false" /></EntityType> <EntityType Name="Client"> <Key> <PropertyRef Name="ClientId" /> @@ -305,7 +293,17 @@ <Association Name="FK_IssuedToken_User"> <End Type="DatabaseModel.User" Role="User" Multiplicity="1" /> <End Type="DatabaseModel.ClientAuthorization" Role="ClientAuthorization" Multiplicity="*" /> - </Association></Schema> + </Association> + <EntityType Name="SymmetricCryptoKey"> + <Key> + <PropertyRef Name="CryptoKeyId" /> + </Key> + <Property Type="Int32" Name="CryptoKeyId" Nullable="false" a:StoreGeneratedPattern="Identity" xmlns:a="http://schemas.microsoft.com/ado/2009/02/edm/annotation" /> + <Property Type="String" Name="Bucket" Nullable="false" MaxLength="255" FixedLength="false" Unicode="false" /> + <Property Type="String" Name="Handle" Nullable="false" MaxLength="255" FixedLength="false" Unicode="false" /> + <Property Type="DateTime" Name="ExpirationUtc" Nullable="false" /> + <Property Type="Binary" Name="Secret" Nullable="false" MaxLength="4096" FixedLength="false" /> + </EntityType></Schema> </edmx:ConceptualModels> <!-- C-S mapping content --> <edmx:Mappings> @@ -361,15 +359,6 @@ <ScalarProperty Name="Code" ColumnName="Code" /> <ScalarProperty Name="Context" ColumnName="Context" /> <ScalarProperty Name="NonceId" ColumnName="NonceId" /></MappingFragment></EntityTypeMapping></EntitySetMapping> - <EntitySetMapping Name="OpenIdAssociations"> - <EntityTypeMapping TypeName="IsTypeOf(DatabaseModel.OpenIdAssociation)"> - <MappingFragment StoreEntitySet="OpenIDAssociation"> - <ScalarProperty Name="PrivateDataLength" ColumnName="PrivateDataLength" /> - <ScalarProperty Name="PrivateData" ColumnName="PrivateData" /> - <ScalarProperty Name="ExpirationUtc" ColumnName="Expiration" /> - <ScalarProperty Name="AssociationHandle" ColumnName="AssociationHandle" /> - <ScalarProperty Name="DistinguishingFactor" ColumnName="DistinguishingFactor" /> - <ScalarProperty Name="AssociationId" ColumnName="AssociationId" /></MappingFragment></EntityTypeMapping></EntitySetMapping> <FunctionImportMapping FunctionImportName="ClearExpiredNonces" FunctionName="DatabaseModel.Store.ClearExpiredNonces" /> <EntitySetMapping Name="Clients"> <EntityTypeMapping TypeName="DatabaseModel.Client"> @@ -407,7 +396,18 @@ <EndProperty Name="User"> <ScalarProperty Name="UserId" ColumnName="UserId" /> </EndProperty> - </AssociationSetMapping></EntityContainerMapping> + </AssociationSetMapping> + <EntitySetMapping Name="SymmetricCryptoKeys"> + <EntityTypeMapping TypeName="DatabaseModel.SymmetricCryptoKey"> + <MappingFragment StoreEntitySet="CryptoKey"> + <ScalarProperty Name="Secret" ColumnName="Secret" /> + <ScalarProperty Name="ExpirationUtc" ColumnName="Expiration" /> + <ScalarProperty Name="Handle" ColumnName="Handle" /> + <ScalarProperty Name="Bucket" ColumnName="Bucket" /> + <ScalarProperty Name="CryptoKeyId" ColumnName="CryptoKeyId" /> + </MappingFragment> + </EntityTypeMapping> + </EntitySetMapping></EntityContainerMapping> </Mapping> </edmx:Mappings> </edmx:Runtime> @@ -440,8 +440,7 @@ <AssociationConnector Association="DatabaseModel.FK_AuthenticationToken_User"> <ConnectorPoint PointX="4.625" PointY="1.9324446614583337" /> <ConnectorPoint PointX="5.25" PointY="1.9324446614583337" /></AssociationConnector> - <EntityTypeShape EntityType="DatabaseModel.Nonce" Width="1.5" PointX="9.375" PointY="0.75" Height="1.9802864583333326" /> - <EntityTypeShape EntityType="DatabaseModel.OpenIdAssociation" Width="1.75" PointX="7.375" PointY="0.75" Height="2.1725878906249996" /> + <EntityTypeShape EntityType="DatabaseModel.Nonce" Width="1.5" PointX="9.625" PointY="0.75" Height="1.9802864583333326" /> <EntityTypeShape EntityType="DatabaseModel.Client" Width="1.625" PointX="5.25" PointY="3.75" Height="2.1725878906249996" /> <EntityTypeShape EntityType="DatabaseModel.ClientAuthorization" Width="1.75" PointX="2.875" PointY="3.75" Height="2.1725878906250031" /> <AssociationConnector Association="DatabaseModel.FK_IssuedToken_Consumer" > @@ -451,6 +450,7 @@ <AssociationConnector Association="DatabaseModel.FK_IssuedToken_User" > <ConnectorPoint PointX="3.75" PointY="3.2494921875" /> <ConnectorPoint PointX="3.75" PointY="3.75" /> - </AssociationConnector></Diagram></edmx:Diagrams> + </AssociationConnector> + <EntityTypeShape EntityType="DatabaseModel.SymmetricCryptoKey" Width="1.875" PointX="7.5" PointY="0.75" Height="1.9802864583333317" /></Diagram></edmx:Diagrams> </edmx:Designer> </edmx:Edmx>
\ No newline at end of file diff --git a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs index 2e791ff..69757be 100644 --- a/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs +++ b/projecttemplates/RelyingPartyLogic/OAuthAuthorizationServer.cs @@ -9,9 +9,9 @@ namespace RelyingPartyLogic { using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web; - using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth2; using DotNetOpenAuth.OAuth2.ChannelElements; @@ -23,75 +23,18 @@ namespace RelyingPartyLogic { public class OAuthAuthorizationServer : IAuthorizationServer { private static readonly RSAParameters AsymmetricKey = CreateRSAKey(); - private static readonly byte[] secret = CreateSecret(); - private readonly INonceStore nonceStore = new NonceDbStore(); /// <summary> - /// Creates a symmetric secret used to sign and encrypt authorization server refresh tokens. - /// </summary> - /// <returns>A cryptographically strong symmetric key.</returns> - private static byte[] CreateSecret() { - // TODO: Replace this sample code with real code. - // For this sample, we just generate random secrets. - RandomNumberGenerator crypto = new RNGCryptoServiceProvider(); - var secret = new byte[16]; - crypto.GetBytes(secret); - return secret; - } - - /// <summary> - /// Creates the RSA key used by all the crypto service provider instances we create. - /// </summary> - /// <returns>RSA data that includes the private key.</returns> - private static RSAParameters CreateRSAKey() { - // As we generate a new random key, we need to set the UseMachineKeyStore flag so that this doesn't - // crash on IIS. For more information: - // http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ea48fd0-8d6b-43ed-b272-1a0249ae490f?prof=required - var cspParameters = new CspParameters(); - cspParameters.Flags = CspProviderFlags.UseArchivableKey | CspProviderFlags.UseMachineKeyStore; - var asymmetricKey = new RSACryptoServiceProvider(cspParameters); - return asymmetricKey.ExportParameters(true); - } - - /// <summary> - /// Creates the asymmetric crypto service provider. - /// </summary> - /// <returns>An RSA crypto service provider.</returns> - /// <remarks> - /// Since <see cref="RSACryptoServiceProvider"/> are not thread-safe, one must be created for each thread. - /// In this sample we just create one for each incoming request. Be sure to call Dispose on them to release native handles. - /// </remarks> - internal static RSACryptoServiceProvider CreateAsymmetricKeyServiceProvider() { - var serviceProvider = new RSACryptoServiceProvider(); - serviceProvider.ImportParameters(AsymmetricKey); - return serviceProvider; - } - - /// <summary> /// Initializes a new instance of the <see cref="OAuthAuthorizationServer"/> class. /// </summary> public OAuthAuthorizationServer() { + this.CryptoKeyStore = new RelyingPartyApplicationDbStore(); } #region IAuthorizationServer Members - /// <summary> - /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens. - /// </summary> - /// <value></value> - /// <remarks> - /// This secret should be kept strictly confidential in the authorization server(s) - /// and NOT shared with the resource server. Anyone with this secret can mint - /// tokens to essentially grant themselves access to anything they want. - /// </remarks> - public byte[] Secret { - get { return secret; } - } - - public RSACryptoServiceProvider CreateAccessTokenSigningCryptoServiceProvider() { - return CreateAsymmetricKeyServiceProvider(); - } + public ICryptoKeyStore CryptoKeyStore { get; private set; } /// <summary> /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. @@ -101,6 +44,10 @@ namespace RelyingPartyLogic { get { return this.nonceStore; } } + public RSACryptoServiceProvider CreateAccessTokenSigningCryptoServiceProvider() { + return CreateAsymmetricKeyServiceProvider(); + } + /// <summary> /// Gets the client with a given identifier. /// </summary> @@ -151,7 +98,7 @@ namespace RelyingPartyLogic { // NEVER issue an auto-approval to a client that would end up getting an access token immediately // (without a client secret), as that would allow ANY client to spoof an approved client's identity // and obtain unauthorized access to user data. - if (authorizationRequest.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) { + if (EndUserAuthorizationRequest.ResponseType == EndUserAuthorizationResponseTypes.AuthorizationCode) { // Never issue auto-approval if the client secret is blank, since that too makes it easy to spoof // a client's identity and obtain unauthorized access. var requestingClient = Database.DataContext.Clients.First(c => c.ClientIdentifier == authorizationRequest.ClientIdentifier); @@ -168,6 +115,34 @@ namespace RelyingPartyLogic { return false; } + /// <summary> + /// Creates the asymmetric crypto service provider. + /// </summary> + /// <returns>An RSA crypto service provider.</returns> + /// <remarks> + /// Since <see cref="RSACryptoServiceProvider"/> are not thread-safe, one must be created for each thread. + /// In this sample we just create one for each incoming request. Be sure to call Dispose on them to release native handles. + /// </remarks> + internal static RSACryptoServiceProvider CreateAsymmetricKeyServiceProvider() { + var serviceProvider = new RSACryptoServiceProvider(); + serviceProvider.ImportParameters(AsymmetricKey); + return serviceProvider; + } + + /// <summary> + /// Creates the RSA key used by all the crypto service provider instances we create. + /// </summary> + /// <returns>RSA data that includes the private key.</returns> + private static RSAParameters CreateRSAKey() { + // As we generate a new random key, we need to set the UseMachineKeyStore flag so that this doesn't + // crash on IIS. For more information: + // http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ea48fd0-8d6b-43ed-b272-1a0249ae490f?prof=required + var cspParameters = new CspParameters(); + cspParameters.Flags = CspProviderFlags.UseArchivableKey | CspProviderFlags.UseMachineKeyStore; + var asymmetricKey = new RSACryptoServiceProvider(cspParameters); + return asymmetricKey.ExportParameters(true); + } + private bool IsAuthorizationValid(HashSet<string> requestedScopes, string clientIdentifier, DateTime issuedUtc, string username) { var grantedScopeStrings = from auth in Database.DataContext.ClientAuthorizations where diff --git a/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs b/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs index e13633a..90a5544 100644 --- a/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs +++ b/projecttemplates/RelyingPartyLogic/RelyingPartyApplicationDbStore.cs @@ -9,120 +9,73 @@ namespace RelyingPartyLogic { using System.Collections.Generic; using System.Data; using System.Linq; - using System.Text; + using DotNetOpenAuth; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// A database-backed state store for OpenID relying parties. /// </summary> - public class RelyingPartyApplicationDbStore : NonceDbStore, IRelyingPartyApplicationStore { + public class RelyingPartyApplicationDbStore : NonceDbStore, IOpenIdApplicationStore { /// <summary> /// Initializes a new instance of the <see cref="RelyingPartyApplicationDbStore"/> class. /// </summary> public RelyingPartyApplicationDbStore() { } - #region IAssociationStore<Uri> Members + #region ICryptoStore Members - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - /// <remarks> - /// TODO: what should implementations do on association handle conflict? - /// </remarks> - public void StoreAssociation(Uri distinguishingFactor, Association association) { + public CryptoKey GetKey(string bucket, string handle) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { - var sharedAssociation = new OpenIdAssociation { - DistinguishingFactor = distinguishingFactor.AbsoluteUri, - AssociationHandle = association.Handle, - ExpirationUtc = association.Expires, - PrivateData = association.SerializePrivateData(), - }; - - dataContext.AddToOpenIdAssociations(sharedAssociation); + var associations = from assoc in dataContext.SymmetricCryptoKeys + where assoc.Bucket == bucket + where assoc.Handle == handle + where assoc.ExpirationUtc > DateTime.UtcNow + select assoc; + return associations.AsEnumerable() + .Select(assoc => new CryptoKey(assoc.Secret, assoc.ExpirationUtc.AsUtc())) + .FirstOrDefault(); } } - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="distinguishingFactor"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. - /// </remarks> - public Association GetAssociation(Uri distinguishingFactor, SecuritySettings securityRequirements) { + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { - var relevantAssociations = from assoc in dataContext.OpenIdAssociations - where assoc.DistinguishingFactor == distinguishingFactor.AbsoluteUri + var relevantAssociations = from assoc in dataContext.SymmetricCryptoKeys + where assoc.Bucket == bucket where assoc.ExpirationUtc > DateTime.UtcNow - where assoc.PrivateDataLength * 8 >= securityRequirements.MinimumHashBitLength - where assoc.PrivateDataLength * 8 <= securityRequirements.MaximumHashBitLength orderby assoc.ExpirationUtc descending select assoc; var qualifyingAssociations = relevantAssociations.AsEnumerable() - .Select(assoc => DeserializeAssociation(assoc)); - return qualifyingAssociations.FirstOrDefault(); + .Select(assoc => new KeyValuePair<string, CryptoKey>(assoc.Handle, new CryptoKey(assoc.Secret, assoc.ExpirationUtc.AsUtc()))); + return qualifyingAssociations.ToList(); // the data context is closing, so we must cache the result. } } - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - /// </returns> - public Association GetAssociation(Uri distinguishingFactor, string handle) { + public void StoreKey(string bucket, string handle, CryptoKey key) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { - var associations = from assoc in dataContext.OpenIdAssociations - where assoc.DistinguishingFactor == distinguishingFactor.AbsoluteUri - where assoc.AssociationHandle == handle - where assoc.ExpirationUtc > DateTime.UtcNow - select assoc; - return associations.AsEnumerable() - .Select(assoc => DeserializeAssociation(assoc)) - .FirstOrDefault(); + var sharedAssociation = new SymmetricCryptoKey { + Bucket = bucket, + Handle = handle, + ExpirationUtc = key.ExpiresUtc, + Secret = key.Key, + }; + + dataContext.AddToSymmetricCryptoKeys(sharedAssociation); } } - /// <summary> - /// Removes a specified handle that may exist in the store. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// True if the association existed in this store previous to this call. - /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - public bool RemoveAssociation(Uri distinguishingFactor, string handle) { + public void RemoveKey(string bucket, string handle) { using (var dataContext = new TransactedDatabaseEntities(System.Data.IsolationLevel.ReadCommitted)) { - var association = dataContext.OpenIdAssociations.FirstOrDefault(a => a.DistinguishingFactor == distinguishingFactor.AbsoluteUri && a.AssociationHandle == handle); + var association = dataContext.SymmetricCryptoKeys.FirstOrDefault(a => a.Bucket == bucket && a.Handle == handle); if (association != null) { dataContext.DeleteObject(association); - return true; } else { - return false; } } } + #endregion + /// <summary> /// Clears all expired associations from the store. /// </summary> @@ -132,27 +85,10 @@ namespace RelyingPartyLogic { /// This should be done frequently enough to avoid a memory leak, but sparingly enough /// to not be a performance drain. /// </remarks> - public void ClearExpiredAssociations() { + internal void ClearExpiredCryptoKeys() { using (var dataContext = new TransactedDatabaseEntities(IsolationLevel.ReadCommitted)) { - dataContext.ClearExpiredAssociations(dataContext.Transaction); + dataContext.ClearExpiredCryptoKeys(dataContext.Transaction); } } - - #endregion - - /// <summary> - /// Deserializes an association from the database. - /// </summary> - /// <param name="association">The association from the database.</param> - /// <returns>The deserialized association.</returns> - private static Association DeserializeAssociation(OpenIdAssociation association) { - if (association == null) { - throw new ArgumentNullException("association"); - } - - byte[] privateData = new byte[association.PrivateDataLength]; - Array.Copy(association.PrivateData, privateData, association.PrivateDataLength); - return Association.Deserialize(association.AssociationHandle, association.ExpirationUtc, privateData); - } } } diff --git a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj index 549cad5..6ff2740 100644 --- a/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj +++ b/projecttemplates/RelyingPartyLogic/RelyingPartyLogic.csproj @@ -117,7 +117,6 @@ <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> </Compile> - <Compile Include="Model.OpenIdAssociation.cs" /> <Compile Include="Model.User.cs" /> <Compile Include="NonceDbStore.cs" /> <Compile Include="OAuthAuthorizationServer.cs" /> diff --git a/projecttemplates/RelyingPartyLogic/Utilities.cs b/projecttemplates/RelyingPartyLogic/Utilities.cs index 47212e6..1f07a9e 100644 --- a/projecttemplates/RelyingPartyLogic/Utilities.cs +++ b/projecttemplates/RelyingPartyLogic/Utilities.cs @@ -142,5 +142,18 @@ GO" }; throw new ArgumentException("DateTime must be given in UTC time but was " + value.Kind.ToString()); } } + + /// <summary> + /// Ensures that local times are converted to UTC times. Unspecified kinds are recast to UTC with no conversion. + /// </summary> + /// <param name="value">The date-time to convert.</param> + /// <returns>The date-time in UTC time.</returns> + internal static DateTime AsUtc(this DateTime value) { + if (value.Kind == DateTimeKind.Unspecified) { + return new DateTime(value.Ticks, DateTimeKind.Utc); + } + + return value.ToUniversalTime(); + } } } diff --git a/samples/OAuthAuthorizationServer/Code/DataClasses.dbml b/samples/OAuthAuthorizationServer/Code/DataClasses.dbml index 33e6eda..0ef987d 100644 --- a/samples/OAuthAuthorizationServer/Code/DataClasses.dbml +++ b/samples/OAuthAuthorizationServer/Code/DataClasses.dbml @@ -37,4 +37,12 @@ <Column Name="Timestamp" Type="System.DateTime" IsPrimaryKey="true" CanBeNull="false" /> </Type> </Table> + <Table Name="" Member="SymmetricCryptoKeys"> + <Type Name="SymmetricCryptoKey"> + <Column Name="Bucket" Type="System.String" IsPrimaryKey="true" CanBeNull="false" UpdateCheck="Never" /> + <Column Name="Handle" Type="System.String" IsPrimaryKey="true" CanBeNull="false" UpdateCheck="Never" /> + <Column Name="ExpiresUtc" Type="System.DateTime" CanBeNull="false" UpdateCheck="Never" /> + <Column Name="Secret" Type="System.Byte[]" CanBeNull="false" UpdateCheck="Never" /> + </Type> + </Table> </Database>
\ No newline at end of file diff --git a/samples/OAuthAuthorizationServer/Code/DataClasses.dbml.layout b/samples/OAuthAuthorizationServer/Code/DataClasses.dbml.layout index e2982ce..f4de725 100644 --- a/samples/OAuthAuthorizationServer/Code/DataClasses.dbml.layout +++ b/samples/OAuthAuthorizationServer/Code/DataClasses.dbml.layout @@ -40,5 +40,11 @@ <classShapeMoniker Id="895ebbc8-8352-4c04-9e53-b8e6c8302d36" /> </nodes> </associationConnector> + <classShape Id="93df6fa9-cc66-44a9-8885-960b1e670dd7" absoluteBounds="4.125, 6.25, 2, 1.5785953776041666"> + <DataClassMoniker Name="/DataClassesDataContext/SymmetricCryptoKey" /> + <nestedChildShapes> + <elementListCompartment Id="0b486eb8-31a4-4f11-b58f-09540c56319b" absoluteBounds="4.14, 6.71, 1.9700000000000002, 1.0185953776041665" name="DataPropertiesCompartment" titleTextColor="Black" itemTextColor="Black" /> + </nestedChildShapes> + </classShape> </nestedChildShapes> </ordesignerObjectsDiagram>
\ No newline at end of file diff --git a/samples/OAuthAuthorizationServer/Code/DataClasses.designer.cs b/samples/OAuthAuthorizationServer/Code/DataClasses.designer.cs index b6d070d..c8d1b19 100644 --- a/samples/OAuthAuthorizationServer/Code/DataClasses.designer.cs +++ b/samples/OAuthAuthorizationServer/Code/DataClasses.designer.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.1 +// Runtime Version:4.0.30319.225 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -42,6 +42,9 @@ namespace OAuthAuthorizationServer.Code partial void InsertNonce(Nonce instance); partial void UpdateNonce(Nonce instance); partial void DeleteNonce(Nonce instance); + partial void InsertSymmetricCryptoKey(SymmetricCryptoKey instance); + partial void UpdateSymmetricCryptoKey(SymmetricCryptoKey instance); + partial void DeleteSymmetricCryptoKey(SymmetricCryptoKey instance); #endregion public DataClassesDataContext() : @@ -105,6 +108,14 @@ namespace OAuthAuthorizationServer.Code return this.GetTable<Nonce>(); } } + + public System.Data.Linq.Table<SymmetricCryptoKey> SymmetricCryptoKeys + { + get + { + return this.GetTable<SymmetricCryptoKey>(); + } + } } [global::System.Data.Linq.Mapping.TableAttribute(Name="dbo.[User]")] @@ -804,5 +815,139 @@ namespace OAuthAuthorizationServer.Code } } } + + [global::System.Data.Linq.Mapping.TableAttribute(Name="")] + public partial class SymmetricCryptoKey : INotifyPropertyChanging, INotifyPropertyChanged + { + + private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); + + private string _Bucket; + + private string _Handle; + + private System.DateTime _ExpiresUtc; + + private byte[] _Secret; + + #region Extensibility Method Definitions + partial void OnLoaded(); + partial void OnValidate(System.Data.Linq.ChangeAction action); + partial void OnCreated(); + partial void OnBucketChanging(string value); + partial void OnBucketChanged(); + partial void OnHandleChanging(string value); + partial void OnHandleChanged(); + partial void OnExpiresUtcChanging(System.DateTime value); + partial void OnExpiresUtcChanged(); + partial void OnSecretChanging(byte[] value); + partial void OnSecretChanged(); + #endregion + + public SymmetricCryptoKey() + { + OnCreated(); + } + + [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Bucket", CanBeNull=false, IsPrimaryKey=true, UpdateCheck=UpdateCheck.Never)] + public string Bucket + { + get + { + return this._Bucket; + } + set + { + if ((this._Bucket != value)) + { + this.OnBucketChanging(value); + this.SendPropertyChanging(); + this._Bucket = value; + this.SendPropertyChanged("Bucket"); + this.OnBucketChanged(); + } + } + } + + [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Handle", CanBeNull=false, IsPrimaryKey=true, UpdateCheck=UpdateCheck.Never)] + public string Handle + { + get + { + return this._Handle; + } + set + { + if ((this._Handle != value)) + { + this.OnHandleChanging(value); + this.SendPropertyChanging(); + this._Handle = value; + this.SendPropertyChanged("Handle"); + this.OnHandleChanged(); + } + } + } + + [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_ExpiresUtc", UpdateCheck=UpdateCheck.Never)] + public System.DateTime ExpiresUtc + { + get + { + return this._ExpiresUtc; + } + set + { + if ((this._ExpiresUtc != value)) + { + this.OnExpiresUtcChanging(value); + this.SendPropertyChanging(); + this._ExpiresUtc = value; + this.SendPropertyChanged("ExpiresUtc"); + this.OnExpiresUtcChanged(); + } + } + } + + [global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_Secret", CanBeNull=false, UpdateCheck=UpdateCheck.Never)] + public byte[] Secret + { + get + { + return this._Secret; + } + set + { + if ((this._Secret != value)) + { + this.OnSecretChanging(value); + this.SendPropertyChanging(); + this._Secret = value; + this.SendPropertyChanged("Secret"); + this.OnSecretChanged(); + } + } + } + + public event PropertyChangingEventHandler PropertyChanging; + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void SendPropertyChanging() + { + if ((this.PropertyChanging != null)) + { + this.PropertyChanging(this, emptyChangingEventArgs); + } + } + + protected virtual void SendPropertyChanged(String propertyName) + { + if ((this.PropertyChanged != null)) + { + this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } } #pragma warning restore 1591 diff --git a/samples/OAuthAuthorizationServer/Code/DatabaseNonceStore.cs b/samples/OAuthAuthorizationServer/Code/DatabaseKeyNonceStore.cs index a0ce19e..0a1d8af 100644 --- a/samples/OAuthAuthorizationServer/Code/DatabaseNonceStore.cs +++ b/samples/OAuthAuthorizationServer/Code/DatabaseKeyNonceStore.cs @@ -1,16 +1,18 @@ namespace OAuthAuthorizationServer.Code { using System; + using System.Collections.Generic; using System.Data.SqlClient; + using System.Linq; using DotNetOpenAuth.Messaging.Bindings; /// <summary> /// A database-persisted nonce store. /// </summary> - public class DatabaseNonceStore : INonceStore { + public class DatabaseKeyNonceStore : INonceStore, ICryptoKeyStore { /// <summary> - /// Initializes a new instance of the <see cref="DatabaseNonceStore"/> class. + /// Initializes a new instance of the <see cref="DatabaseKeyNonceStore"/> class. /// </summary> - public DatabaseNonceStore() { + public DatabaseKeyNonceStore() { } #region INonceStore Members @@ -51,5 +53,44 @@ } #endregion + + #region ICryptoKeyStore Members + + public CryptoKey GetKey(string bucket, string handle) { + // It is critical that this lookup be case-sensitive, which can only be configured at the database. + var matches = from key in MvcApplication.DataContext.SymmetricCryptoKeys + where key.Bucket == bucket && key.Handle == handle + select new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc()); + + return matches.FirstOrDefault(); + } + + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return from key in MvcApplication.DataContext.SymmetricCryptoKeys + where key.Bucket == bucket + orderby key.ExpiresUtc descending + select new KeyValuePair<string, CryptoKey>(key.Handle, new CryptoKey(key.Secret, key.ExpiresUtc.AsUtc())); + } + + public void StoreKey(string bucket, string handle, CryptoKey key) { + var keyRow = new SymmetricCryptoKey() { + Bucket = bucket, + Handle = handle, + Secret = key.Key, + ExpiresUtc = key.ExpiresUtc, + }; + + MvcApplication.DataContext.SymmetricCryptoKeys.InsertOnSubmit(keyRow); + MvcApplication.DataContext.SubmitChanges(); + } + + public void RemoveKey(string bucket, string handle) { + var match = MvcApplication.DataContext.SymmetricCryptoKeys.FirstOrDefault(k => k.Bucket == bucket && k.Handle == handle); + if (match != null) { + MvcApplication.DataContext.SymmetricCryptoKeys.DeleteOnSubmit(match); + } + } + + #endregion } }
\ No newline at end of file diff --git a/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs b/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs index 7e4dba6..d2583a2 100644 --- a/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs +++ b/samples/OAuthAuthorizationServer/Code/OAuth2AuthorizationServer.cs @@ -13,68 +13,14 @@ internal class OAuth2AuthorizationServer : IAuthorizationServer { private static readonly RSAParameters AsymmetricTokenSigningPrivateKey = CreateRSAKey(); - private static readonly byte[] secret = CreateSecret(); - - private readonly INonceStore nonceStore = new DatabaseNonceStore(); - - /// <summary> - /// Creates a symmetric secret used to sign and encrypt authorization server refresh tokens. - /// </summary> - /// <returns>A cryptographically strong symmetric key.</returns> - private static byte[] CreateSecret() { - // TODO: Replace this sample code with real code. - // For this sample, we just generate random secrets. - RandomNumberGenerator crypto = new RNGCryptoServiceProvider(); - var secret = new byte[16]; - crypto.GetBytes(secret); - return secret; - } - - /// <summary> - /// Creates the RSA key used by all the crypto service provider instances we create. - /// </summary> - /// <returns>RSA data that includes the private key.</returns> - private static RSAParameters CreateRSAKey() { -#if SAMPLESONLY - // Since the sample authorization server and the sample resource server must work together, - // we hard-code a FOR SAMPLE USE ONLY key pair. The matching public key information is hard-coded into the OAuthResourceServer sample. - // In a real app, the RSA parameters would typically come from a certificate that may already exist. It may simply be the HTTPS certificate for the auth server. - return new RSAParameters { - Exponent = new byte[] { 1, 0, 1 }, - Modulus = new byte[] { 210, 95, 53, 12, 203, 114, 150, 23, 23, 88, 4, 200, 47, 219, 73, 54, 146, 253, 126, 121, 105, 91, 118, 217, 182, 167, 140, 6, 67, 112, 97, 183, 66, 112, 245, 103, 136, 222, 205, 28, 196, 45, 6, 223, 192, 76, 56, 180, 90, 120, 144, 19, 31, 193, 37, 129, 186, 214, 36, 53, 204, 53, 108, 133, 112, 17, 133, 244, 3, 12, 230, 29, 243, 51, 79, 253, 10, 111, 185, 23, 74, 230, 99, 94, 78, 49, 209, 39, 95, 213, 248, 212, 22, 4, 222, 145, 77, 190, 136, 230, 134, 70, 228, 241, 194, 216, 163, 234, 52, 1, 64, 181, 139, 128, 90, 255, 214, 60, 168, 233, 254, 110, 31, 102, 58, 67, 201, 33 }, - P = new byte[] { 237, 238, 79, 75, 29, 57, 145, 201, 57, 177, 215, 108, 40, 77, 232, 237, 113, 38, 157, 195, 174, 134, 188, 175, 121, 28, 11, 236, 80, 146, 12, 38, 8, 12, 104, 46, 6, 247, 14, 149, 196, 23, 130, 116, 141, 137, 225, 74, 84, 111, 44, 163, 55, 10, 246, 154, 195, 158, 186, 241, 162, 11, 217, 77 }, - Q = new byte[] { 226, 89, 29, 67, 178, 205, 30, 152, 184, 165, 15, 152, 131, 245, 141, 80, 150, 3, 224, 136, 188, 248, 149, 36, 200, 250, 207, 156, 224, 79, 150, 191, 84, 214, 233, 173, 95, 192, 55, 123, 124, 255, 53, 85, 11, 233, 156, 66, 14, 27, 27, 163, 108, 199, 90, 37, 118, 38, 78, 171, 80, 26, 101, 37 }, - DP = new byte[] { 108, 176, 122, 132, 131, 187, 50, 191, 203, 157, 84, 29, 82, 100, 20, 205, 178, 236, 195, 17, 10, 254, 253, 222, 226, 226, 79, 8, 10, 222, 76, 178, 106, 230, 208, 8, 134, 162, 1, 133, 164, 232, 96, 109, 193, 226, 132, 138, 33, 252, 15, 86, 23, 228, 232, 54, 86, 186, 130, 7, 179, 208, 217, 217 }, - DQ = new byte[] { 175, 63, 252, 46, 140, 99, 208, 138, 194, 123, 218, 101, 101, 214, 91, 65, 199, 196, 220, 182, 66, 73, 221, 128, 11, 180, 85, 198, 202, 206, 20, 147, 179, 102, 106, 170, 247, 245, 229, 127, 81, 58, 111, 218, 151, 76, 154, 213, 114, 2, 127, 21, 187, 133, 102, 64, 151, 7, 245, 229, 34, 50, 45, 153 }, - InverseQ = new byte[] { 137, 156, 11, 248, 118, 201, 135, 145, 134, 121, 14, 162, 149, 14, 98, 84, 108, 160, 27, 91, 230, 116, 216, 181, 200, 49, 34, 254, 119, 153, 179, 52, 231, 234, 36, 148, 71, 161, 182, 171, 35, 182, 46, 164, 179, 100, 226, 71, 119, 23, 0, 16, 240, 4, 30, 57, 76, 109, 89, 131, 56, 219, 71, 206 }, - D = new byte[] { 108, 15, 123, 176, 150, 208, 197, 72, 23, 53, 159, 63, 53, 85, 238, 197, 153, 187, 156, 187, 192, 226, 186, 170, 26, 168, 245, 196, 65, 223, 248, 81, 170, 79, 91, 191, 83, 15, 31, 77, 39, 119, 249, 143, 245, 183, 49, 105, 115, 15, 122, 242, 87, 221, 94, 230, 196, 146, 59, 7, 103, 94, 9, 223, 146, 180, 189, 86, 190, 94, 242, 59, 32, 54, 23, 181, 124, 170, 63, 172, 90, 158, 169, 140, 6, 102, 170, 0, 135, 199, 35, 196, 212, 238, 196, 56, 14, 0, 140, 197, 169, 240, 156, 43, 182, 123, 102, 79, 89, 20, 120, 171, 43, 223, 58, 190, 230, 166, 185, 162, 186, 226, 31, 206, 196, 188, 104, 1 }, - }; -#else - // This is how you could generate your own public/private key pair. - // As we generate a new random key, we need to set the UseMachineKeyStore flag so that this doesn't - // crash on IIS. For more information: - // http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ea48fd0-8d6b-43ed-b272-1a0249ae490f?prof=required - var cspParameters = new CspParameters(); - cspParameters.Flags = CspProviderFlags.UseArchivableKey | CspProviderFlags.UseMachineKeyStore; - var keyPair = new RSACryptoServiceProvider(cspParameters); - - // After exporting the private/public key information, read the information out and store it somewhere - var privateKey = keyPair.ExportParameters(true); - var publicKey = keyPair.ExportParameters(false); - - // Ultimately the private key information must be what is returned through the AccessTokenSigningPrivateKey property. - return privateKey; -#endif - } - #region Implementation of IAuthorizationServer - public byte[] Secret { - get { return secret; } + public ICryptoKeyStore CryptoKeyStore { + get { return MvcApplication.KeyNonceStore; } } public INonceStore VerificationCodeNonceStore { - get { return this.nonceStore; } + get { return MvcApplication.KeyNonceStore; } } public RSACryptoServiceProvider CreateAccessTokenSigningCryptoServiceProvider() { @@ -107,7 +53,7 @@ // NEVER issue an auto-approval to a client that would end up getting an access token immediately // (without a client secret), as that would allow ANY client to spoof an approved client's identity // and obtain unauthorized access to user data. - if (authorizationRequest.ResponseType == EndUserAuthorizationResponseType.AuthorizationCode) { + if (EndUserAuthorizationRequest.ResponseType == EndUserAuthorizationResponseTypes.AuthorizationCode) { // Never issue auto-approval if the client secret is blank, since that too makes it easy to spoof // a client's identity and obtain unauthorized access. var requestingClient = MvcApplication.DataContext.Clients.First(c => c.ClientIdentifier == authorizationRequest.ClientIdentifier); @@ -124,14 +70,55 @@ return false; } + /// <summary> + /// Creates the RSA key used by all the crypto service provider instances we create. + /// </summary> + /// <returns>RSA data that includes the private key.</returns> + private static RSAParameters CreateRSAKey() { +#if SAMPLESONLY + // Since the sample authorization server and the sample resource server must work together, + // we hard-code a FOR SAMPLE USE ONLY key pair. The matching public key information is hard-coded into the OAuthResourceServer sample. + // In a real app, the RSA parameters would typically come from a certificate that may already exist. It may simply be the HTTPS certificate for the auth server. + return new RSAParameters { + Exponent = new byte[] { 1, 0, 1 }, + Modulus = new byte[] { 210, 95, 53, 12, 203, 114, 150, 23, 23, 88, 4, 200, 47, 219, 73, 54, 146, 253, 126, 121, 105, 91, 118, 217, 182, 167, 140, 6, 67, 112, 97, 183, 66, 112, 245, 103, 136, 222, 205, 28, 196, 45, 6, 223, 192, 76, 56, 180, 90, 120, 144, 19, 31, 193, 37, 129, 186, 214, 36, 53, 204, 53, 108, 133, 112, 17, 133, 244, 3, 12, 230, 29, 243, 51, 79, 253, 10, 111, 185, 23, 74, 230, 99, 94, 78, 49, 209, 39, 95, 213, 248, 212, 22, 4, 222, 145, 77, 190, 136, 230, 134, 70, 228, 241, 194, 216, 163, 234, 52, 1, 64, 181, 139, 128, 90, 255, 214, 60, 168, 233, 254, 110, 31, 102, 58, 67, 201, 33 }, + P = new byte[] { 237, 238, 79, 75, 29, 57, 145, 201, 57, 177, 215, 108, 40, 77, 232, 237, 113, 38, 157, 195, 174, 134, 188, 175, 121, 28, 11, 236, 80, 146, 12, 38, 8, 12, 104, 46, 6, 247, 14, 149, 196, 23, 130, 116, 141, 137, 225, 74, 84, 111, 44, 163, 55, 10, 246, 154, 195, 158, 186, 241, 162, 11, 217, 77 }, + Q = new byte[] { 226, 89, 29, 67, 178, 205, 30, 152, 184, 165, 15, 152, 131, 245, 141, 80, 150, 3, 224, 136, 188, 248, 149, 36, 200, 250, 207, 156, 224, 79, 150, 191, 84, 214, 233, 173, 95, 192, 55, 123, 124, 255, 53, 85, 11, 233, 156, 66, 14, 27, 27, 163, 108, 199, 90, 37, 118, 38, 78, 171, 80, 26, 101, 37 }, + DP = new byte[] { 108, 176, 122, 132, 131, 187, 50, 191, 203, 157, 84, 29, 82, 100, 20, 205, 178, 236, 195, 17, 10, 254, 253, 222, 226, 226, 79, 8, 10, 222, 76, 178, 106, 230, 208, 8, 134, 162, 1, 133, 164, 232, 96, 109, 193, 226, 132, 138, 33, 252, 15, 86, 23, 228, 232, 54, 86, 186, 130, 7, 179, 208, 217, 217 }, + DQ = new byte[] { 175, 63, 252, 46, 140, 99, 208, 138, 194, 123, 218, 101, 101, 214, 91, 65, 199, 196, 220, 182, 66, 73, 221, 128, 11, 180, 85, 198, 202, 206, 20, 147, 179, 102, 106, 170, 247, 245, 229, 127, 81, 58, 111, 218, 151, 76, 154, 213, 114, 2, 127, 21, 187, 133, 102, 64, 151, 7, 245, 229, 34, 50, 45, 153 }, + InverseQ = new byte[] { 137, 156, 11, 248, 118, 201, 135, 145, 134, 121, 14, 162, 149, 14, 98, 84, 108, 160, 27, 91, 230, 116, 216, 181, 200, 49, 34, 254, 119, 153, 179, 52, 231, 234, 36, 148, 71, 161, 182, 171, 35, 182, 46, 164, 179, 100, 226, 71, 119, 23, 0, 16, 240, 4, 30, 57, 76, 109, 89, 131, 56, 219, 71, 206 }, + D = new byte[] { 108, 15, 123, 176, 150, 208, 197, 72, 23, 53, 159, 63, 53, 85, 238, 197, 153, 187, 156, 187, 192, 226, 186, 170, 26, 168, 245, 196, 65, 223, 248, 81, 170, 79, 91, 191, 83, 15, 31, 77, 39, 119, 249, 143, 245, 183, 49, 105, 115, 15, 122, 242, 87, 221, 94, 230, 196, 146, 59, 7, 103, 94, 9, 223, 146, 180, 189, 86, 190, 94, 242, 59, 32, 54, 23, 181, 124, 170, 63, 172, 90, 158, 169, 140, 6, 102, 170, 0, 135, 199, 35, 196, 212, 238, 196, 56, 14, 0, 140, 197, 169, 240, 156, 43, 182, 123, 102, 79, 89, 20, 120, 171, 43, 223, 58, 190, 230, 166, 185, 162, 186, 226, 31, 206, 196, 188, 104, 1 }, + }; +#else + // This is how you could generate your own public/private key pair. + // As we generate a new random key, we need to set the UseMachineKeyStore flag so that this doesn't + // crash on IIS. For more information: + // http://social.msdn.microsoft.com/Forums/en-US/clr/thread/7ea48fd0-8d6b-43ed-b272-1a0249ae490f?prof=required + var cspParameters = new CspParameters(); + cspParameters.Flags = CspProviderFlags.UseArchivableKey | CspProviderFlags.UseMachineKeyStore; + var keyPair = new RSACryptoServiceProvider(cspParameters); + + // After exporting the private/public key information, read the information out and store it somewhere + var privateKey = keyPair.ExportParameters(true); + var publicKey = keyPair.ExportParameters(false); + + // Ultimately the private key information must be what is returned through the AccessTokenSigningPrivateKey property. + return privateKey; +#endif + } + private bool IsAuthorizationValid(HashSet<string> requestedScopes, string clientIdentifier, DateTime issuedUtc, string username) { + // If db precision exceeds token time precision (which is common), the following query would + // often disregard a token that is minted immediately after the authorization record is stored in the db. + // To compensate for this, we'll increase the timestamp on the token's issue date by 1 second. + issuedUtc += TimeSpan.FromSeconds(1); var grantedScopeStrings = from auth in MvcApplication.DataContext.ClientAuthorizations - where - auth.Client.ClientIdentifier == clientIdentifier && - auth.CreatedOnUtc <= issuedUtc && - (!auth.ExpirationDateUtc.HasValue || auth.ExpirationDateUtc.Value >= DateTime.UtcNow) && - auth.User.OpenIDClaimedIdentifier == username - select auth.Scope; + where + auth.Client.ClientIdentifier == clientIdentifier && + auth.CreatedOnUtc <= issuedUtc && + (!auth.ExpirationDateUtc.HasValue || auth.ExpirationDateUtc.Value >= DateTime.UtcNow) && + auth.User.OpenIDClaimedIdentifier == username + select auth.Scope; if (!grantedScopeStrings.Any()) { // No granted authorizations prior to the issuance of this token, so it must have been revoked. diff --git a/samples/OAuthAuthorizationServer/Code/Utilities.cs b/samples/OAuthAuthorizationServer/Code/Utilities.cs new file mode 100644 index 0000000..c9109bd --- /dev/null +++ b/samples/OAuthAuthorizationServer/Code/Utilities.cs @@ -0,0 +1,21 @@ +namespace OAuthAuthorizationServer.Code { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Web; + + internal static class Utilities { + /// <summary> + /// Ensures that local times are converted to UTC times. Unspecified kinds are recast to UTC with no conversion. + /// </summary> + /// <param name="value">The date-time to convert.</param> + /// <returns>The date-time in UTC time.</returns> + internal static DateTime AsUtc(this DateTime value) { + if (value.Kind == DateTimeKind.Unspecified) { + return new DateTime(value.Ticks, DateTimeKind.Utc); + } + + return value.ToUniversalTime(); + } + } +}
\ No newline at end of file diff --git a/samples/OAuthAuthorizationServer/Controllers/HomeController.cs b/samples/OAuthAuthorizationServer/Controllers/HomeController.cs index 1887576..1311caa 100644 --- a/samples/OAuthAuthorizationServer/Controllers/HomeController.cs +++ b/samples/OAuthAuthorizationServer/Controllers/HomeController.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; using System.Web.Mvc; - + using System.Web.Security; using OAuthAuthorizationServer.Code; [HandleError] @@ -39,6 +39,10 @@ }); dc.SubmitChanges(); + + // Force the user to log out because a new database warrants a new row in the users table, which we create + // when the user logs in. + FormsAuthentication.SignOut(); ViewData["Success"] = true; } catch (SqlException ex) { ViewData["Error"] = string.Join("<br>", ex.Errors.OfType<SqlError>().Select(er => er.Message).ToArray()); diff --git a/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs b/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs index 47c1977..fb836a6 100644 --- a/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs +++ b/samples/OAuthAuthorizationServer/Controllers/OAuthController.cs @@ -35,16 +35,6 @@ #endif
/// <summary>
- /// Creates the resource server's encryption service provider with private key.
- /// </summary>
- /// <returns>An RSA crypto service provider.</returns>
- internal static RSACryptoServiceProvider CreateResourceServerEncryptionServiceProvider() {
- var resourceServerEncryptionServiceProvider = new RSACryptoServiceProvider();
- resourceServerEncryptionServiceProvider.ImportParameters(ResourceServerEncryptionPublicKey);
- return resourceServerEncryptionServiceProvider;
- }
-
- /// <summary>
/// The OAuth 2.0 token endpoint.
/// </summary>
/// <returns>The response to the Client.</returns>
@@ -123,6 +113,7 @@ User = MvcApplication.LoggedInUser,
CreatedOnUtc = DateTime.UtcNow,
});
+ MvcApplication.DataContext.SubmitChanges(); // submit now so that this new row can be retrieved later in this same HTTP request
// In this simple sample, the user either agrees to the entire scope requested by the client or none of it.
// But in a real app, you could grant a reduced scope of access to the client by passing a scope parameter to this method.
@@ -133,5 +124,15 @@ return this.authorizationServer.Channel.PrepareResponse(response).AsActionResult();
}
+
+ /// <summary>
+ /// Creates the resource server's encryption service provider with private key.
+ /// </summary>
+ /// <returns>An RSA crypto service provider.</returns>
+ internal static RSACryptoServiceProvider CreateResourceServerEncryptionServiceProvider() {
+ var resourceServerEncryptionServiceProvider = new RSACryptoServiceProvider();
+ resourceServerEncryptionServiceProvider.ImportParameters(ResourceServerEncryptionPublicKey);
+ return resourceServerEncryptionServiceProvider;
+ }
}
}
diff --git a/samples/OAuthAuthorizationServer/Global.asax.cs b/samples/OAuthAuthorizationServer/Global.asax.cs index 2c23ec0..d878ea6 100644 --- a/samples/OAuthAuthorizationServer/Global.asax.cs +++ b/samples/OAuthAuthorizationServer/Global.asax.cs @@ -26,7 +26,7 @@ /// </summary>
public static log4net.ILog Logger = log4net.LogManager.GetLogger("DotNetOpenAuth.OAuthAuthorizationServer");
- public static DatabaseNonceStore NonceStore { get; set; }
+ public static DatabaseKeyNonceStore KeyNonceStore { get; set; }
/// <summary>
/// Gets the transaction-protected database connection for the current request.
@@ -81,7 +81,7 @@ RegisterRoutes(RouteTable.Routes);
- NonceStore = new DatabaseNonceStore();
+ KeyNonceStore = new DatabaseKeyNonceStore();
log4net.Config.XmlConfigurator.Configure();
Logger.Info("Sample starting...");
diff --git a/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj b/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj index 5bb1daf..d00cef4 100644 --- a/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj +++ b/samples/OAuthAuthorizationServer/OAuthAuthorizationServer.csproj @@ -70,13 +70,14 @@ </ItemGroup> <ItemGroup> <Compile Include="Code\Client.cs" /> - <Compile Include="Code\DatabaseNonceStore.cs" /> + <Compile Include="Code\DatabaseKeyNonceStore.cs" /> <Compile Include="Code\DataClasses.designer.cs"> <DependentUpon>DataClasses.dbml</DependentUpon> <DesignTime>True</DesignTime> <AutoGen>True</AutoGen> </Compile> <Compile Include="Code\OAuth2AuthorizationServer.cs" /> + <Compile Include="Code\Utilities.cs" /> <Compile Include="Controllers\AccountController.cs" /> <Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\OAuthController.cs" /> diff --git a/samples/OAuthClient/SampleWcf2.aspx.cs b/samples/OAuthClient/SampleWcf2.aspx.cs index 058ba47..78b46bc 100644 --- a/samples/OAuthClient/SampleWcf2.aspx.cs +++ b/samples/OAuthClient/SampleWcf2.aspx.cs @@ -1,137 +1,137 @@ -namespace OAuthClient { - using System; - using System.Collections.Generic; - using System.Globalization; - using System.Linq; - using System.Net; - using System.ServiceModel; - using System.ServiceModel.Channels; - using System.ServiceModel.Security; - using System.Web; - using System.Web.UI; - using System.Web.UI.WebControls; - using DotNetOpenAuth.OAuth2; - - using SampleResourceServer; - - public partial class SampleWcf2 : System.Web.UI.Page { - /// <summary> - /// The OAuth 2.0 client object to use to obtain authorization and authorize outgoing HTTP requests. - /// </summary> - private static readonly WebServerClient Client; - - /// <summary> - /// The details about the sample OAuth-enabled WCF service that this sample client calls into. - /// </summary> - private static AuthorizationServerDescription authServerDescription = new AuthorizationServerDescription { - TokenEndpoint = new Uri("http://localhost:50172/OAuth/Token"), - AuthorizationEndpoint = new Uri("http://localhost:50172/OAuth/Authorize"), - }; - - /// <summary> - /// Initializes static members of the <see cref="SampleWcf2"/> class. - /// </summary> - static SampleWcf2() { - Client = new WebServerClient(authServerDescription, "sampleconsumer", "samplesecret"); - } - - /// <summary> - /// Gets or sets the authorization details for the logged in user. - /// </summary> - /// <value>The authorization details.</value> - /// <remarks> - /// Because this is a sample, we simply store the authorization information in memory with the user session. - /// A real web app should store at least the access and refresh tokens in this object in a database associated with the user. - /// </remarks> - private static IAuthorizationState Authorization { - get { return (AuthorizationState)HttpContext.Current.Session["Authorization"]; } - set { HttpContext.Current.Session["Authorization"] = value; } - } - - protected void Page_Load(object sender, EventArgs e) { - if (!IsPostBack) { - // Check to see if we're receiving a end user authorization response. - var authorization = Client.ProcessUserAuthorization(); - if (authorization != null) { - // We are receiving an authorization response. Store it and associate it with this user. - Authorization = authorization; - Response.Redirect(Request.Path); // get rid of the /?code= parameter - } - } - - if (Authorization != null) { - // Indicate to the user that we have already obtained authorization on some of these. - foreach (var li in this.scopeList.Items.OfType<ListItem>().Where(li => Authorization.Scope.Contains(li.Value))) { - li.Selected = true; - } - this.authorizationLabel.Text = "Authorization received!"; - if (Authorization.AccessTokenExpirationUtc.HasValue) { +namespace OAuthClient {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Net;
+ using System.ServiceModel;
+ using System.ServiceModel.Channels;
+ using System.ServiceModel.Security;
+ using System.Web;
+ using System.Web.UI;
+ using System.Web.UI.WebControls;
+ using DotNetOpenAuth.OAuth2;
+
+ using SampleResourceServer;
+
+ public partial class SampleWcf2 : System.Web.UI.Page {
+ /// <summary>
+ /// The OAuth 2.0 client object to use to obtain authorization and authorize outgoing HTTP requests.
+ /// </summary>
+ private static readonly WebServerClient Client;
+
+ /// <summary>
+ /// The details about the sample OAuth-enabled WCF service that this sample client calls into.
+ /// </summary>
+ private static AuthorizationServerDescription authServerDescription = new AuthorizationServerDescription {
+ TokenEndpoint = new Uri("http://localhost:50172/OAuth/Token"),
+ AuthorizationEndpoint = new Uri("http://localhost:50172/OAuth/Authorize"),
+ };
+
+ /// <summary>
+ /// Initializes static members of the <see cref="SampleWcf2"/> class.
+ /// </summary>
+ static SampleWcf2() {
+ Client = new WebServerClient(authServerDescription, "sampleconsumer", "samplesecret");
+ }
+
+ /// <summary>
+ /// Gets or sets the authorization details for the logged in user.
+ /// </summary>
+ /// <value>The authorization details.</value>
+ /// <remarks>
+ /// Because this is a sample, we simply store the authorization information in memory with the user session.
+ /// A real web app should store at least the access and refresh tokens in this object in a database associated with the user.
+ /// </remarks>
+ private static IAuthorizationState Authorization {
+ get { return (AuthorizationState)HttpContext.Current.Session["Authorization"]; }
+ set { HttpContext.Current.Session["Authorization"] = value; }
+ }
+
+ protected void Page_Load(object sender, EventArgs e) {
+ if (!IsPostBack) {
+ // Check to see if we're receiving a end user authorization response.
+ var authorization = Client.ProcessUserAuthorization();
+ if (authorization != null) {
+ // We are receiving an authorization response. Store it and associate it with this user.
+ Authorization = authorization;
+ Response.Redirect(Request.Path); // get rid of the /?code= parameter
+ }
+ }
+
+ if (Authorization != null) {
+ // Indicate to the user that we have already obtained authorization on some of these.
+ foreach (var li in this.scopeList.Items.OfType<ListItem>().Where(li => Authorization.Scope.Contains(li.Value))) {
+ li.Selected = true;
+ }
+ this.authorizationLabel.Text = "Authorization received!";
+ if (Authorization.AccessTokenExpirationUtc.HasValue) {
TimeSpan timeLeft = Authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
- this.authorizationLabel.Text += string.Format(CultureInfo.CurrentCulture, " (access token expires in {0} minutes)", Math.Round(timeLeft.TotalMinutes, 1)); - } - } - - this.getNameButton.Enabled = this.getAgeButton.Enabled = this.getFavoriteSites.Enabled = Authorization != null; - } - - protected void getAuthorizationButton_Click(object sender, EventArgs e) { - string[] scopes = (from item in this.scopeList.Items.OfType<ListItem>() - where item.Selected - select item.Value).ToArray(); - - Client.RequestUserAuthorization(scopes); - } - - protected void getNameButton_Click(object sender, EventArgs e) { - try { - this.nameLabel.Text = CallService(client => client.GetName()); - } catch (SecurityAccessDeniedException) { - this.nameLabel.Text = "Access denied!"; - } - } - - protected void getAgeButton_Click(object sender, EventArgs e) { - try { - int? age = CallService(client => client.GetAge()); - this.ageLabel.Text = age.HasValue ? age.Value.ToString(CultureInfo.CurrentCulture) : "not available"; - } catch (SecurityAccessDeniedException) { - this.ageLabel.Text = "Access denied!"; - } - } - - protected void getFavoriteSites_Click(object sender, EventArgs e) { - try { - string[] favoriteSites = CallService(client => client.GetFavoriteSites()); - this.favoriteSitesLabel.Text = string.Join(", ", favoriteSites); - } catch (SecurityAccessDeniedException) { - this.favoriteSitesLabel.Text = "Access denied!"; - } - } - - private T CallService<T>(Func<DataApiClient, T> predicate) { - if (Authorization == null) { - throw new InvalidOperationException("No access token!"); - } - - var wcfClient = new DataApiClient(); - - // Refresh the access token if it expires and if its lifetime is too short to be of use. - if (Authorization.AccessTokenExpirationUtc.HasValue) { - if (Client.RefreshToken(Authorization, TimeSpan.FromSeconds(30))) { + this.authorizationLabel.Text += string.Format(CultureInfo.CurrentCulture, " (access token expires in {0} minutes)", Math.Round(timeLeft.TotalMinutes, 1));
+ }
+ }
+
+ this.getNameButton.Enabled = this.getAgeButton.Enabled = this.getFavoriteSites.Enabled = Authorization != null;
+ }
+
+ protected void getAuthorizationButton_Click(object sender, EventArgs e) {
+ string[] scopes = (from item in this.scopeList.Items.OfType<ListItem>()
+ where item.Selected
+ select item.Value).ToArray();
+
+ Client.RequestUserAuthorization(scopes);
+ }
+
+ protected void getNameButton_Click(object sender, EventArgs e) {
+ try {
+ this.nameLabel.Text = CallService(client => client.GetName());
+ } catch (SecurityAccessDeniedException) {
+ this.nameLabel.Text = "Access denied!";
+ }
+ }
+
+ protected void getAgeButton_Click(object sender, EventArgs e) {
+ try {
+ int? age = CallService(client => client.GetAge());
+ this.ageLabel.Text = age.HasValue ? age.Value.ToString(CultureInfo.CurrentCulture) : "not available";
+ } catch (SecurityAccessDeniedException) {
+ this.ageLabel.Text = "Access denied!";
+ }
+ }
+
+ protected void getFavoriteSites_Click(object sender, EventArgs e) {
+ try {
+ string[] favoriteSites = CallService(client => client.GetFavoriteSites());
+ this.favoriteSitesLabel.Text = string.Join(", ", favoriteSites);
+ } catch (SecurityAccessDeniedException) {
+ this.favoriteSitesLabel.Text = "Access denied!";
+ }
+ }
+
+ private T CallService<T>(Func<DataApiClient, T> predicate) {
+ if (Authorization == null) {
+ throw new InvalidOperationException("No access token!");
+ }
+
+ var wcfClient = new DataApiClient();
+
+ // Refresh the access token if it expires and if its lifetime is too short to be of use.
+ if (Authorization.AccessTokenExpirationUtc.HasValue) {
+ if (Client.RefreshToken(Authorization, TimeSpan.FromSeconds(30))) {
TimeSpan timeLeft = Authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
- this.authorizationLabel.Text += string.Format(CultureInfo.CurrentCulture, " - just renewed for {0} more minutes)", Math.Round(timeLeft.TotalMinutes, 1)); - } - } - - var httpRequest = (HttpWebRequest)WebRequest.Create(wcfClient.Endpoint.Address.Uri); - Client.AuthorizeRequest(httpRequest, Authorization.AccessToken); - - var httpDetails = new HttpRequestMessageProperty(); - httpDetails.Headers[HttpRequestHeader.Authorization] = httpRequest.Headers[HttpRequestHeader.Authorization]; - using (var scope = new OperationContextScope(wcfClient.InnerChannel)) { - OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpDetails; - return predicate(wcfClient); - } - } - } + this.authorizationLabel.Text += string.Format(CultureInfo.CurrentCulture, " - just renewed for {0} more minutes)", Math.Round(timeLeft.TotalMinutes, 1));
+ }
+ }
+
+ var httpRequest = (HttpWebRequest)WebRequest.Create(wcfClient.Endpoint.Address.Uri);
+ ClientBase.AuthorizeRequest(httpRequest, Authorization.AccessToken);
+
+ var httpDetails = new HttpRequestMessageProperty();
+ httpDetails.Headers[HttpRequestHeader.Authorization] = httpRequest.Headers[HttpRequestHeader.Authorization];
+ using (var scope = new OperationContextScope(wcfClient.InnerChannel)) {
+ OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpDetails;
+ return predicate(wcfClient);
+ }
+ }
+ }
}
\ No newline at end of file diff --git a/samples/OpenIdProviderWebForms/Code/CustomStore.cs b/samples/OpenIdProviderWebForms/Code/CustomStore.cs index 7face0b..b2316a4 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStore.cs +++ b/samples/OpenIdProviderWebForms/Code/CustomStore.cs @@ -6,10 +6,13 @@ namespace OpenIdProviderWebForms.Code { using System; + using System.Collections.Generic; using System.Data; using System.Globalization; + using DotNetOpenAuth; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.Provider; /// <summary> /// This custom store serializes all elements to demonstrate peristent and/or shared storage. @@ -21,59 +24,9 @@ namespace OpenIdProviderWebForms.Code { /// But we "persist" all associations and nonces into a DataTable to demonstrate /// that using a database is possible. /// </remarks> - public class CustomStore : IProviderApplicationStore { + public class CustomStore : IOpenIdApplicationStore { private static CustomStoreDataSet dataSet = new CustomStoreDataSet(); - #region IAssociationStore<AssociationRelyingPartyType> Members - - public void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association assoc) { - var assocRow = dataSet.Association.NewAssociationRow(); - assocRow.DistinguishingFactor = distinguishingFactor.ToString(); - assocRow.Handle = assoc.Handle; - assocRow.Expires = assoc.Expires.ToLocalTime(); - assocRow.PrivateData = assoc.SerializePrivateData(); - dataSet.Association.AddAssociationRow(assocRow); - } - - public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, SecuritySettings securitySettings) { - // TODO: properly consider the securitySettings when picking an association to return. - // properly escape the URL to prevent injection attacks. - string value = distinguishingFactor.ToString(); - string filter = string.Format( - CultureInfo.InvariantCulture, - "{0} = '{1}'", - dataSet.Association.DistinguishingFactorColumn.ColumnName, - value); - string sort = dataSet.Association.ExpiresColumn.ColumnName + " DESC"; - DataView view = new DataView(dataSet.Association, filter, sort, DataViewRowState.CurrentRows); - if (view.Count == 0) { - return null; - } - var row = (CustomStoreDataSet.AssociationRow)view[0].Row; - return Association.Deserialize(row.Handle, row.Expires.ToUniversalTime(), row.PrivateData); - } - - public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle) { - var assocRow = dataSet.Association.FindByDistinguishingFactorHandle(distinguishingFactor.ToString(), handle); - return Association.Deserialize(assocRow.Handle, assocRow.Expires, assocRow.PrivateData); - } - - public bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle) { - var row = dataSet.Association.FindByDistinguishingFactorHandle(distinguishingFactor.ToString(), handle); - if (row != null) { - dataSet.Association.RemoveAssociationRow(row); - return true; - } else { - return false; - } - } - - public void ClearExpiredAssociations() { - this.removeExpiredRows(dataSet.Association, dataSet.Association.ExpiresColumn.ColumnName); - } - - #endregion - #region INonceStore Members /// <summary> @@ -84,7 +37,7 @@ namespace OpenIdProviderWebForms.Code { /// The context SHOULD be treated as case-sensitive. /// The value will never be <c>null</c> but may be the empty string.</param> /// <param name="nonce">A series of random characters.</param> - /// <param name="timestamp">The timestamp that together with the nonce string make it unique. + /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. /// The timestamp may also be used by the data store to clear out old nonces.</param> /// <returns> /// True if the nonce+timestamp (combination) was not previously in the database. @@ -97,7 +50,7 @@ namespace OpenIdProviderWebForms.Code { /// is retrieved or set using the /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. /// </remarks> - public bool StoreNonce(string context, string nonce, DateTime timestamp) { + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { // IMPORTANT: If actually persisting to a database that can be reached from // different servers/instances of this class at once, it is vitally important // to protect against race condition attacks by one or more of these: @@ -109,30 +62,73 @@ namespace OpenIdProviderWebForms.Code { // at you in the result of a race condition somewhere in your web site UI code // and display some message to have the user try to log in again, and possibly // warn them about a replay attack. - timestamp = timestamp.ToLocalTime(); lock (this) { - if (dataSet.Nonce.FindByIssuedCodeContext(timestamp, nonce, context) != null) { + if (dataSet.Nonce.FindByIssuedUtcCodeContext(timestampUtc, nonce, context) != null) { return false; } - TimeSpan maxMessageAge = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; - dataSet.Nonce.AddNonceRow(context, nonce, timestamp, timestamp + maxMessageAge); + TimeSpan maxMessageAge = DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; + dataSet.Nonce.AddNonceRow(context, nonce, timestampUtc, timestampUtc + maxMessageAge); return true; } } public void ClearExpiredNonces() { - this.removeExpiredRows(dataSet.Nonce, dataSet.Nonce.ExpiresColumn.ColumnName); + this.removeExpiredRows(dataSet.Nonce, dataSet.Nonce.ExpiresUtcColumn.ColumnName); } #endregion - private void removeExpiredRows(DataTable table, string expiredColumnName) { + #region ICryptoKeyStore Members + + public CryptoKey GetKey(string bucket, string handle) { + var assocRow = dataSet.CryptoKey.FindByBucketHandle(bucket, handle); + return new CryptoKey(assocRow.Secret, assocRow.ExpiresUtc); + } + + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + // properly escape the URL to prevent injection attacks. + string value = bucket.Replace("'", "''"); string filter = string.Format( CultureInfo.InvariantCulture, - "{0} < #{1}#", - expiredColumnName, - DateTime.Now); + "{0} = '{1}'", + dataSet.CryptoKey.BucketColumn.ColumnName, + value); + string sort = dataSet.CryptoKey.ExpiresUtcColumn.ColumnName + " DESC"; + DataView view = new DataView(dataSet.CryptoKey, filter, sort, DataViewRowState.CurrentRows); + if (view.Count == 0) { + yield break; + } + + foreach (CustomStoreDataSet.CryptoKeyRow row in view) { + yield return new KeyValuePair<string, CryptoKey>(row.Handle, new CryptoKey(row.Secret, row.ExpiresUtc)); + } + } + + public void StoreKey(string bucket, string handle, CryptoKey key) { + var cryptoKeyRow = dataSet.CryptoKey.NewCryptoKeyRow(); + cryptoKeyRow.Bucket = bucket; + cryptoKeyRow.Handle = handle; + cryptoKeyRow.ExpiresUtc = key.ExpiresUtc; + cryptoKeyRow.Secret = key.Key; + dataSet.CryptoKey.AddCryptoKeyRow(cryptoKeyRow); + } + + public void RemoveKey(string bucket, string handle) { + var row = dataSet.CryptoKey.FindByBucketHandle(bucket, handle); + if (row != null) { + dataSet.CryptoKey.RemoveCryptoKeyRow(row); + } + } + + #endregion + + internal void ClearExpiredSecrets() { + this.removeExpiredRows(dataSet.CryptoKey, dataSet.CryptoKey.ExpiresUtcColumn.ColumnName); + } + + private void removeExpiredRows(DataTable table, string expiredColumnName) { + string filter = string.Format(CultureInfo.InvariantCulture, "{0} < #{1}#", expiredColumnName, DateTime.UtcNow); DataView view = new DataView(table, filter, null, DataViewRowState.CurrentRows); for (int i = view.Count - 1; i >= 0; i--) { view.Delete(i); diff --git a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs index b8d98ec..19ac88f 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs +++ b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30104.0 +// Runtime Version:4.0.30319.225 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -24,7 +24,7 @@ namespace OpenIdProviderWebForms.Code { [global::System.ComponentModel.Design.HelpKeywordAttribute("vs.data.DataSet")] public partial class CustomStoreDataSet : global::System.Data.DataSet { - private AssociationDataTable tableAssociation; + private CryptoKeyDataTable tableCryptoKey; private NonceDataTable tableNonce; @@ -56,8 +56,8 @@ namespace OpenIdProviderWebForms.Code { if ((this.DetermineSchemaSerializationMode(info, context) == global::System.Data.SchemaSerializationMode.IncludeSchema)) { global::System.Data.DataSet ds = new global::System.Data.DataSet(); ds.ReadXmlSchema(new global::System.Xml.XmlTextReader(new global::System.IO.StringReader(strSchema))); - if ((ds.Tables["Association"] != null)) { - base.Tables.Add(new AssociationDataTable(ds.Tables["Association"])); + if ((ds.Tables["CryptoKey"] != null)) { + base.Tables.Add(new CryptoKeyDataTable(ds.Tables["CryptoKey"])); } if ((ds.Tables["Nonce"] != null)) { base.Tables.Add(new NonceDataTable(ds.Tables["Nonce"])); @@ -84,9 +84,9 @@ namespace OpenIdProviderWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] [global::System.ComponentModel.Browsable(false)] [global::System.ComponentModel.DesignerSerializationVisibility(global::System.ComponentModel.DesignerSerializationVisibility.Content)] - public AssociationDataTable Association { + public CryptoKeyDataTable CryptoKey { get { - return this.tableAssociation; + return this.tableCryptoKey; } } @@ -167,8 +167,8 @@ namespace OpenIdProviderWebForms.Code { this.Reset(); global::System.Data.DataSet ds = new global::System.Data.DataSet(); ds.ReadXml(reader); - if ((ds.Tables["Association"] != null)) { - base.Tables.Add(new AssociationDataTable(ds.Tables["Association"])); + if ((ds.Tables["CryptoKey"] != null)) { + base.Tables.Add(new CryptoKeyDataTable(ds.Tables["CryptoKey"])); } if ((ds.Tables["Nonce"] != null)) { base.Tables.Add(new NonceDataTable(ds.Tables["Nonce"])); @@ -206,10 +206,10 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] internal void InitVars(bool initTable) { - this.tableAssociation = ((AssociationDataTable)(base.Tables["Association"])); + this.tableCryptoKey = ((CryptoKeyDataTable)(base.Tables["CryptoKey"])); if ((initTable == true)) { - if ((this.tableAssociation != null)) { - this.tableAssociation.InitVars(); + if ((this.tableCryptoKey != null)) { + this.tableCryptoKey.InitVars(); } } this.tableNonce = ((NonceDataTable)(base.Tables["Nonce"])); @@ -228,15 +228,15 @@ namespace OpenIdProviderWebForms.Code { this.Namespace = "http://tempuri.org/CustomStoreDataSet.xsd"; this.EnforceConstraints = true; this.SchemaSerializationMode = global::System.Data.SchemaSerializationMode.IncludeSchema; - this.tableAssociation = new AssociationDataTable(); - base.Tables.Add(this.tableAssociation); + this.tableCryptoKey = new CryptoKeyDataTable(); + base.Tables.Add(this.tableCryptoKey); this.tableNonce = new NonceDataTable(); base.Tables.Add(this.tableNonce); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - private bool ShouldSerializeAssociation() { + private bool ShouldSerializeCryptoKey() { return false; } @@ -302,7 +302,7 @@ namespace OpenIdProviderWebForms.Code { } [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public delegate void AssociationRowChangeEventHandler(object sender, AssociationRowChangeEvent e); + public delegate void CryptoKeyRowChangeEventHandler(object sender, CryptoKeyRowChangeEvent e); [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] public delegate void NonceRowChangeEventHandler(object sender, NonceRowChangeEvent e); @@ -312,20 +312,20 @@ namespace OpenIdProviderWebForms.Code { ///</summary> [global::System.Serializable()] [global::System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedTableSchema")] - public partial class AssociationDataTable : global::System.Data.TypedTableBase<AssociationRow> { + public partial class CryptoKeyDataTable : global::System.Data.TypedTableBase<CryptoKeyRow> { - private global::System.Data.DataColumn columnDistinguishingFactor; + private global::System.Data.DataColumn columnBucket; private global::System.Data.DataColumn columnHandle; - private global::System.Data.DataColumn columnExpires; + private global::System.Data.DataColumn columnExpiresUtc; - private global::System.Data.DataColumn columnPrivateData; + private global::System.Data.DataColumn columnSecret; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationDataTable() { - this.TableName = "Association"; + public CryptoKeyDataTable() { + this.TableName = "CryptoKey"; this.BeginInit(); this.InitClass(); this.EndInit(); @@ -333,7 +333,7 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - internal AssociationDataTable(global::System.Data.DataTable table) { + internal CryptoKeyDataTable(global::System.Data.DataTable table) { this.TableName = table.TableName; if ((table.CaseSensitive != table.DataSet.CaseSensitive)) { this.CaseSensitive = table.CaseSensitive; @@ -350,16 +350,16 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - protected AssociationDataTable(global::System.Runtime.Serialization.SerializationInfo info, global::System.Runtime.Serialization.StreamingContext context) : + protected CryptoKeyDataTable(global::System.Runtime.Serialization.SerializationInfo info, global::System.Runtime.Serialization.StreamingContext context) : base(info, context) { this.InitVars(); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn DistinguishingFactorColumn { + public global::System.Data.DataColumn BucketColumn { get { - return this.columnDistinguishingFactor; + return this.columnBucket; } } @@ -373,17 +373,17 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn ExpiresColumn { + public global::System.Data.DataColumn ExpiresUtcColumn { get { - return this.columnExpires; + return this.columnExpiresUtc; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn PrivateDataColumn { + public global::System.Data.DataColumn SecretColumn { get { - return this.columnPrivateData; + return this.columnSecret; } } @@ -398,56 +398,56 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow this[int index] { + public CryptoKeyRow this[int index] { get { - return ((AssociationRow)(this.Rows[index])); + return ((CryptoKeyRow)(this.Rows[index])); } } [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowChanging; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowChanging; [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowChanged; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowChanged; [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowDeleting; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowDeleting; [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowDeleted; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowDeleted; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public void AddAssociationRow(AssociationRow row) { + public void AddCryptoKeyRow(CryptoKeyRow row) { this.Rows.Add(row); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow AddAssociationRow(string DistinguishingFactor, string Handle, System.DateTime Expires, byte[] PrivateData) { - AssociationRow rowAssociationRow = ((AssociationRow)(this.NewRow())); + public CryptoKeyRow AddCryptoKeyRow(string Bucket, string Handle, System.DateTime ExpiresUtc, byte[] Secret) { + CryptoKeyRow rowCryptoKeyRow = ((CryptoKeyRow)(this.NewRow())); object[] columnValuesArray = new object[] { - DistinguishingFactor, + Bucket, Handle, - Expires, - PrivateData}; - rowAssociationRow.ItemArray = columnValuesArray; - this.Rows.Add(rowAssociationRow); - return rowAssociationRow; + ExpiresUtc, + Secret}; + rowCryptoKeyRow.ItemArray = columnValuesArray; + this.Rows.Add(rowCryptoKeyRow); + return rowCryptoKeyRow; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow FindByDistinguishingFactorHandle(string DistinguishingFactor, string Handle) { - return ((AssociationRow)(this.Rows.Find(new object[] { - DistinguishingFactor, + public CryptoKeyRow FindByBucketHandle(string Bucket, string Handle) { + return ((CryptoKeyRow)(this.Rows.Find(new object[] { + Bucket, Handle}))); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] public override global::System.Data.DataTable Clone() { - AssociationDataTable cln = ((AssociationDataTable)(base.Clone())); + CryptoKeyDataTable cln = ((CryptoKeyDataTable)(base.Clone())); cln.InitVars(); return cln; } @@ -455,62 +455,65 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override global::System.Data.DataTable CreateInstance() { - return new AssociationDataTable(); + return new CryptoKeyDataTable(); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] internal void InitVars() { - this.columnDistinguishingFactor = base.Columns["DistinguishingFactor"]; + this.columnBucket = base.Columns["Bucket"]; this.columnHandle = base.Columns["Handle"]; - this.columnExpires = base.Columns["Expires"]; - this.columnPrivateData = base.Columns["PrivateData"]; + this.columnExpiresUtc = base.Columns["ExpiresUtc"]; + this.columnSecret = base.Columns["Secret"]; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] private void InitClass() { - this.columnDistinguishingFactor = new global::System.Data.DataColumn("DistinguishingFactor", typeof(string), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnDistinguishingFactor); + this.columnBucket = new global::System.Data.DataColumn("Bucket", typeof(string), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnBucket); this.columnHandle = new global::System.Data.DataColumn("Handle", typeof(string), null, global::System.Data.MappingType.Element); base.Columns.Add(this.columnHandle); - this.columnExpires = new global::System.Data.DataColumn("Expires", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnExpires); - this.columnPrivateData = new global::System.Data.DataColumn("PrivateData", typeof(byte[]), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnPrivateData); + this.columnExpiresUtc = new global::System.Data.DataColumn("ExpiresUtc", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnExpiresUtc); + this.columnSecret = new global::System.Data.DataColumn("Secret", typeof(byte[]), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnSecret); this.Constraints.Add(new global::System.Data.UniqueConstraint("PrimaryKey", new global::System.Data.DataColumn[] { - this.columnDistinguishingFactor, + this.columnBucket, this.columnHandle}, true)); - this.columnDistinguishingFactor.AllowDBNull = false; + this.columnBucket.AllowDBNull = false; + this.columnBucket.ReadOnly = true; this.columnHandle.AllowDBNull = false; - this.columnExpires.AllowDBNull = false; - this.columnPrivateData.AllowDBNull = false; + this.columnHandle.ReadOnly = true; + this.columnExpiresUtc.AllowDBNull = false; + this.columnExpiresUtc.DateTimeMode = global::System.Data.DataSetDateTime.Utc; + this.columnSecret.AllowDBNull = false; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow NewAssociationRow() { - return ((AssociationRow)(this.NewRow())); + public CryptoKeyRow NewCryptoKeyRow() { + return ((CryptoKeyRow)(this.NewRow())); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override global::System.Data.DataRow NewRowFromBuilder(global::System.Data.DataRowBuilder builder) { - return new AssociationRow(builder); + return new CryptoKeyRow(builder); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override global::System.Type GetRowType() { - return typeof(AssociationRow); + return typeof(CryptoKeyRow); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowChanged(global::System.Data.DataRowChangeEventArgs e) { base.OnRowChanged(e); - if ((this.AssociationRowChanged != null)) { - this.AssociationRowChanged(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowChanged != null)) { + this.CryptoKeyRowChanged(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } @@ -518,8 +521,8 @@ namespace OpenIdProviderWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowChanging(global::System.Data.DataRowChangeEventArgs e) { base.OnRowChanging(e); - if ((this.AssociationRowChanging != null)) { - this.AssociationRowChanging(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowChanging != null)) { + this.CryptoKeyRowChanging(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } @@ -527,8 +530,8 @@ namespace OpenIdProviderWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowDeleted(global::System.Data.DataRowChangeEventArgs e) { base.OnRowDeleted(e); - if ((this.AssociationRowDeleted != null)) { - this.AssociationRowDeleted(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowDeleted != null)) { + this.CryptoKeyRowDeleted(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } @@ -536,14 +539,14 @@ namespace OpenIdProviderWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowDeleting(global::System.Data.DataRowChangeEventArgs e) { base.OnRowDeleting(e); - if ((this.AssociationRowDeleting != null)) { - this.AssociationRowDeleting(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowDeleting != null)) { + this.CryptoKeyRowDeleting(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public void RemoveAssociationRow(AssociationRow row) { + public void RemoveCryptoKeyRow(CryptoKeyRow row) { this.Rows.Remove(row); } @@ -570,7 +573,7 @@ namespace OpenIdProviderWebForms.Code { type.Attributes.Add(attribute1); global::System.Xml.Schema.XmlSchemaAttribute attribute2 = new global::System.Xml.Schema.XmlSchemaAttribute(); attribute2.Name = "tableTypeName"; - attribute2.FixedValue = "AssociationDataTable"; + attribute2.FixedValue = "CryptoKeyDataTable"; type.Attributes.Add(attribute2); type.Particle = sequence; global::System.Xml.Schema.XmlSchema dsSchema = ds.GetSchemaSerializable(); @@ -622,9 +625,9 @@ namespace OpenIdProviderWebForms.Code { private global::System.Data.DataColumn columnCode; - private global::System.Data.DataColumn columnIssued; + private global::System.Data.DataColumn columnIssuedUtc; - private global::System.Data.DataColumn columnExpires; + private global::System.Data.DataColumn columnExpiresUtc; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] @@ -677,17 +680,17 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn IssuedColumn { + public global::System.Data.DataColumn IssuedUtcColumn { get { - return this.columnIssued; + return this.columnIssuedUtc; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn ExpiresColumn { + public global::System.Data.DataColumn ExpiresUtcColumn { get { - return this.columnExpires; + return this.columnExpiresUtc; } } @@ -728,13 +731,13 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public NonceRow AddNonceRow(string Context, string Code, System.DateTime Issued, System.DateTime Expires) { + public NonceRow AddNonceRow(string Context, string Code, System.DateTime IssuedUtc, System.DateTime ExpiresUtc) { NonceRow rowNonceRow = ((NonceRow)(this.NewRow())); object[] columnValuesArray = new object[] { Context, Code, - Issued, - Expires}; + IssuedUtc, + ExpiresUtc}; rowNonceRow.ItemArray = columnValuesArray; this.Rows.Add(rowNonceRow); return rowNonceRow; @@ -742,9 +745,9 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public NonceRow FindByIssuedCodeContext(System.DateTime Issued, string Code, string Context) { + public NonceRow FindByIssuedUtcCodeContext(System.DateTime IssuedUtc, string Code, string Context) { return ((NonceRow)(this.Rows.Find(new object[] { - Issued, + IssuedUtc, Code, Context}))); } @@ -768,8 +771,8 @@ namespace OpenIdProviderWebForms.Code { internal void InitVars() { this.columnContext = base.Columns["Context"]; this.columnCode = base.Columns["Code"]; - this.columnIssued = base.Columns["Issued"]; - this.columnExpires = base.Columns["Expires"]; + this.columnIssuedUtc = base.Columns["IssuedUtc"]; + this.columnExpiresUtc = base.Columns["ExpiresUtc"]; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] @@ -779,18 +782,20 @@ namespace OpenIdProviderWebForms.Code { base.Columns.Add(this.columnContext); this.columnCode = new global::System.Data.DataColumn("Code", typeof(string), null, global::System.Data.MappingType.Element); base.Columns.Add(this.columnCode); - this.columnIssued = new global::System.Data.DataColumn("Issued", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnIssued); - this.columnExpires = new global::System.Data.DataColumn("Expires", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnExpires); + this.columnIssuedUtc = new global::System.Data.DataColumn("IssuedUtc", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnIssuedUtc); + this.columnExpiresUtc = new global::System.Data.DataColumn("ExpiresUtc", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnExpiresUtc); this.Constraints.Add(new global::System.Data.UniqueConstraint("Constraint1", new global::System.Data.DataColumn[] { - this.columnIssued, + this.columnIssuedUtc, this.columnCode, this.columnContext}, true)); this.columnContext.AllowDBNull = false; this.columnCode.AllowDBNull = false; - this.columnIssued.AllowDBNull = false; - this.columnExpires.AllowDBNull = false; + this.columnIssuedUtc.AllowDBNull = false; + this.columnIssuedUtc.DateTimeMode = global::System.Data.DataSetDateTime.Utc; + this.columnExpiresUtc.AllowDBNull = false; + this.columnExpiresUtc.DateTimeMode = global::System.Data.DataSetDateTime.Utc; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] @@ -920,25 +925,25 @@ namespace OpenIdProviderWebForms.Code { /// <summary> ///Represents strongly named DataRow class. ///</summary> - public partial class AssociationRow : global::System.Data.DataRow { + public partial class CryptoKeyRow : global::System.Data.DataRow { - private AssociationDataTable tableAssociation; + private CryptoKeyDataTable tableCryptoKey; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - internal AssociationRow(global::System.Data.DataRowBuilder rb) : + internal CryptoKeyRow(global::System.Data.DataRowBuilder rb) : base(rb) { - this.tableAssociation = ((AssociationDataTable)(this.Table)); + this.tableCryptoKey = ((CryptoKeyDataTable)(this.Table)); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public string DistinguishingFactor { + public string Bucket { get { - return ((string)(this[this.tableAssociation.DistinguishingFactorColumn])); + return ((string)(this[this.tableCryptoKey.BucketColumn])); } set { - this[this.tableAssociation.DistinguishingFactorColumn] = value; + this[this.tableCryptoKey.BucketColumn] = value; } } @@ -946,32 +951,32 @@ namespace OpenIdProviderWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] public string Handle { get { - return ((string)(this[this.tableAssociation.HandleColumn])); + return ((string)(this[this.tableCryptoKey.HandleColumn])); } set { - this[this.tableAssociation.HandleColumn] = value; + this[this.tableCryptoKey.HandleColumn] = value; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public System.DateTime Expires { + public System.DateTime ExpiresUtc { get { - return ((global::System.DateTime)(this[this.tableAssociation.ExpiresColumn])); + return ((global::System.DateTime)(this[this.tableCryptoKey.ExpiresUtcColumn])); } set { - this[this.tableAssociation.ExpiresColumn] = value; + this[this.tableCryptoKey.ExpiresUtcColumn] = value; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public byte[] PrivateData { + public byte[] Secret { get { - return ((byte[])(this[this.tableAssociation.PrivateDataColumn])); + return ((byte[])(this[this.tableCryptoKey.SecretColumn])); } set { - this[this.tableAssociation.PrivateDataColumn] = value; + this[this.tableCryptoKey.SecretColumn] = value; } } } @@ -1014,23 +1019,23 @@ namespace OpenIdProviderWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public System.DateTime Issued { + public System.DateTime IssuedUtc { get { - return ((global::System.DateTime)(this[this.tableNonce.IssuedColumn])); + return ((global::System.DateTime)(this[this.tableNonce.IssuedUtcColumn])); } set { - this[this.tableNonce.IssuedColumn] = value; + this[this.tableNonce.IssuedUtcColumn] = value; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public System.DateTime Expires { + public System.DateTime ExpiresUtc { get { - return ((global::System.DateTime)(this[this.tableNonce.ExpiresColumn])); + return ((global::System.DateTime)(this[this.tableNonce.ExpiresUtcColumn])); } set { - this[this.tableNonce.ExpiresColumn] = value; + this[this.tableNonce.ExpiresUtcColumn] = value; } } } @@ -1039,22 +1044,22 @@ namespace OpenIdProviderWebForms.Code { ///Row event argument class ///</summary> [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public class AssociationRowChangeEvent : global::System.EventArgs { + public class CryptoKeyRowChangeEvent : global::System.EventArgs { - private AssociationRow eventRow; + private CryptoKeyRow eventRow; private global::System.Data.DataRowAction eventAction; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRowChangeEvent(AssociationRow row, global::System.Data.DataRowAction action) { + public CryptoKeyRowChangeEvent(CryptoKeyRow row, global::System.Data.DataRowAction action) { this.eventRow = row; this.eventAction = action; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow Row { + public CryptoKeyRow Row { get { return this.eventRow; } diff --git a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd index 04a96eb..cf3b62e 100644 --- a/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd +++ b/samples/OpenIdProviderWebForms/Code/CustomStoreDataSet.xsd @@ -9,39 +9,39 @@ </DataSource> </xs:appinfo> </xs:annotation> - <xs:element name="CustomStoreDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" msprop:Generator_UserDSName="CustomStoreDataSet" msprop:Generator_DataSetName="CustomStoreDataSet" msprop:EnableTableAdapterManager="true"> + <xs:element name="CustomStoreDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" msprop:EnableTableAdapterManager="true" msprop:Generator_DataSetName="CustomStoreDataSet" msprop:Generator_UserDSName="CustomStoreDataSet"> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="Association" msprop:Generator_UserTableName="Association" msprop:Generator_RowDeletedName="AssociationRowDeleted" msprop:Generator_RowChangedName="AssociationRowChanged" msprop:Generator_RowClassName="AssociationRow" msprop:Generator_RowChangingName="AssociationRowChanging" msprop:Generator_RowEvArgName="AssociationRowChangeEvent" msprop:Generator_RowEvHandlerName="AssociationRowChangeEventHandler" msprop:Generator_TableClassName="AssociationDataTable" msprop:Generator_TableVarName="tableAssociation" msprop:Generator_RowDeletingName="AssociationRowDeleting" msprop:Generator_TablePropName="Association"> + <xs:element name="CryptoKey" msprop:Generator_UserTableName="CryptoKey" msprop:Generator_RowEvArgName="CryptoKeyRowChangeEvent" msprop:Generator_TableVarName="tableCryptoKey" msprop:Generator_TablePropName="CryptoKey" msprop:Generator_RowDeletingName="CryptoKeyRowDeleting" msprop:Generator_RowChangingName="CryptoKeyRowChanging" msprop:Generator_RowDeletedName="CryptoKeyRowDeleted" msprop:Generator_RowEvHandlerName="CryptoKeyRowChangeEventHandler" msprop:Generator_RowChangedName="CryptoKeyRowChanged" msprop:Generator_TableClassName="CryptoKeyDataTable" msprop:Generator_RowClassName="CryptoKeyRow"> <xs:complexType> <xs:sequence> - <xs:element name="DistinguishingFactor" msprop:Generator_UserColumnName="DistinguishingFactor" msprop:Generator_ColumnVarNameInTable="columnDistinguishingFactor" msprop:Generator_ColumnPropNameInRow="DistinguishingFactor" msprop:Generator_ColumnPropNameInTable="DistinguishingFactorColumn" type="xs:string" /> - <xs:element name="Handle" msprop:Generator_UserColumnName="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnPropNameInTable="HandleColumn" type="xs:string" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> - <xs:element name="PrivateData" msprop:Generator_UserColumnName="PrivateData" msprop:Generator_ColumnVarNameInTable="columnPrivateData" msprop:Generator_ColumnPropNameInRow="PrivateData" msprop:Generator_ColumnPropNameInTable="PrivateDataColumn" type="xs:base64Binary" /> + <xs:element name="Bucket" msdata:ReadOnly="true" msprop:Generator_ColumnVarNameInTable="columnBucket" msprop:Generator_ColumnPropNameInRow="Bucket" msprop:Generator_ColumnPropNameInTable="BucketColumn" msprop:Generator_UserColumnName="Bucket" type="xs:string" /> + <xs:element name="Handle" msdata:ReadOnly="true" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnPropNameInTable="HandleColumn" msprop:Generator_UserColumnName="Handle" type="xs:string" /> + <xs:element name="ExpiresUtc" msdata:DateTimeMode="Utc" msprop:Generator_ColumnVarNameInTable="columnExpiresUtc" msprop:Generator_ColumnPropNameInRow="ExpiresUtc" msprop:Generator_ColumnPropNameInTable="ExpiresUtcColumn" msprop:Generator_UserColumnName="ExpiresUtc" type="xs:dateTime" /> + <xs:element name="Secret" msprop:Generator_ColumnVarNameInTable="columnSecret" msprop:Generator_ColumnPropNameInRow="Secret" msprop:Generator_ColumnPropNameInTable="SecretColumn" msprop:Generator_UserColumnName="Secret" type="xs:base64Binary" /> </xs:sequence> </xs:complexType> </xs:element> - <xs:element name="Nonce" msprop:Generator_UserTableName="Nonce" msprop:Generator_RowDeletedName="NonceRowDeleted" msprop:Generator_RowChangedName="NonceRowChanged" msprop:Generator_RowClassName="NonceRow" msprop:Generator_RowChangingName="NonceRowChanging" msprop:Generator_RowEvArgName="NonceRowChangeEvent" msprop:Generator_RowEvHandlerName="NonceRowChangeEventHandler" msprop:Generator_TableClassName="NonceDataTable" msprop:Generator_TableVarName="tableNonce" msprop:Generator_RowDeletingName="NonceRowDeleting" msprop:Generator_TablePropName="Nonce"> + <xs:element name="Nonce" msprop:Generator_UserTableName="Nonce" msprop:Generator_RowEvArgName="NonceRowChangeEvent" msprop:Generator_TableVarName="tableNonce" msprop:Generator_TablePropName="Nonce" msprop:Generator_RowDeletingName="NonceRowDeleting" msprop:Generator_RowChangingName="NonceRowChanging" msprop:Generator_RowDeletedName="NonceRowDeleted" msprop:Generator_RowEvHandlerName="NonceRowChangeEventHandler" msprop:Generator_RowChangedName="NonceRowChanged" msprop:Generator_TableClassName="NonceDataTable" msprop:Generator_RowClassName="NonceRow"> <xs:complexType> <xs:sequence> - <xs:element name="Context" msprop:Generator_UserColumnName="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnPropNameInTable="ContextColumn" type="xs:string" /> - <xs:element name="Code" msprop:Generator_UserColumnName="Code" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInTable="CodeColumn" type="xs:string" /> - <xs:element name="Issued" msprop:Generator_UserColumnName="Issued" msprop:Generator_ColumnPropNameInRow="Issued" msprop:Generator_ColumnVarNameInTable="columnIssued" msprop:Generator_ColumnPropNameInTable="IssuedColumn" type="xs:dateTime" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> + <xs:element name="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnPropNameInTable="ContextColumn" msprop:Generator_UserColumnName="Context" type="xs:string" /> + <xs:element name="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnPropNameInTable="CodeColumn" msprop:Generator_UserColumnName="Code" type="xs:string" /> + <xs:element name="IssuedUtc" msdata:DateTimeMode="Utc" msprop:Generator_ColumnVarNameInTable="columnIssuedUtc" msprop:Generator_ColumnPropNameInRow="IssuedUtc" msprop:Generator_ColumnPropNameInTable="IssuedUtcColumn" msprop:Generator_UserColumnName="IssuedUtc" type="xs:dateTime" /> + <xs:element name="ExpiresUtc" msdata:DateTimeMode="Utc" msprop:Generator_ColumnVarNameInTable="columnExpiresUtc" msprop:Generator_ColumnPropNameInRow="ExpiresUtc" msprop:Generator_ColumnPropNameInTable="ExpiresUtcColumn" msprop:Generator_UserColumnName="ExpiresUtc" type="xs:dateTime" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> <xs:unique name="PrimaryKey" msdata:PrimaryKey="true"> - <xs:selector xpath=".//mstns:Association" /> - <xs:field xpath="mstns:DistinguishingFactor" /> + <xs:selector xpath=".//mstns:CryptoKey" /> + <xs:field xpath="mstns:Bucket" /> <xs:field xpath="mstns:Handle" /> </xs:unique> <xs:unique name="Constraint1" msdata:PrimaryKey="true"> <xs:selector xpath=".//mstns:Nonce" /> - <xs:field xpath="mstns:Issued" /> + <xs:field xpath="mstns:IssuedUtc" /> <xs:field xpath="mstns:Code" /> <xs:field xpath="mstns:Context" /> </xs:unique> diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs index c4a3982..3ab6292 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStore.cs @@ -1,10 +1,19 @@ -namespace OpenIdRelyingPartyWebForms.Code { +//----------------------------------------------------------------------- +// <copyright file="CustomStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace OpenIdRelyingPartyWebForms.Code { using System; + using System.Collections.Generic; using System.Data; using System.Globalization; - using System.Security.Cryptography; + using System.Linq; + using DotNetOpenAuth; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; - using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// This custom store serializes all elements to demonstrate peristent and/or shared storage. @@ -16,7 +25,7 @@ /// But we "persist" all associations and nonces into a DataTable to demonstrate /// that using a database is possible. /// </remarks> - public class CustomStore : IRelyingPartyApplicationStore { + public class CustomStore : IOpenIdApplicationStore { private static CustomStoreDataSet dataSet = new CustomStoreDataSet(); #region INonceStore Members @@ -29,7 +38,7 @@ /// The context SHOULD be treated as case-sensitive. /// The value will never be <c>null</c> but may be the empty string.</param> /// <param name="nonce">A series of random characters.</param> - /// <param name="timestamp">The timestamp that together with the nonce string make it unique. + /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. /// The timestamp may also be used by the data store to clear out old nonces.</param> /// <returns> /// True if the nonce+timestamp (combination) was not previously in the database. @@ -42,7 +51,7 @@ /// is retrieved or set using the /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. /// </remarks> - public bool StoreNonce(string context, string nonce, DateTime timestamp) { + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { // IMPORTANT: If actually persisting to a database that can be reached from // different servers/instances of this class at once, it is vitally important // to protect against race condition attacks by one or more of these: @@ -54,76 +63,73 @@ // at you in the result of a race condition somewhere in your web site UI code // and display some message to have the user try to log in again, and possibly // warn them about a replay attack. - timestamp = timestamp.ToLocalTime(); lock (this) { - if (dataSet.Nonce.FindByIssuedCodeContext(timestamp, nonce, context) != null) { + if (dataSet.Nonce.FindByIssuedUtcCodeContext(timestampUtc, nonce, context) != null) { return false; } - TimeSpan maxMessageAge = DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; - dataSet.Nonce.AddNonceRow(context, nonce, timestamp, timestamp + maxMessageAge); + TimeSpan maxMessageAge = DotNetOpenAuthSection.Configuration.Messaging.MaximumMessageLifetime; + dataSet.Nonce.AddNonceRow(context, nonce, timestampUtc, timestampUtc + maxMessageAge); return true; } } public void ClearExpiredNonces() { - this.removeExpiredRows(dataSet.Nonce, dataSet.Nonce.ExpiresColumn.ColumnName); + this.removeExpiredRows(dataSet.Nonce, dataSet.Nonce.ExpiresUtcColumn.ColumnName); } #endregion - #region IAssociationStore<Uri> Members + #region ICryptoKeyStore Members - public void StoreAssociation(Uri distinguishingFactor, Association assoc) { - var assocRow = dataSet.Association.NewAssociationRow(); - assocRow.DistinguishingFactor = distinguishingFactor.AbsoluteUri; - assocRow.Handle = assoc.Handle; - assocRow.Expires = assoc.Expires.ToLocalTime(); - assocRow.PrivateData = assoc.SerializePrivateData(); - dataSet.Association.AddAssociationRow(assocRow); + public CryptoKey GetKey(string bucket, string handle) { + var assocRow = dataSet.CryptoKey.FindByBucketHandle(bucket, handle); + return new CryptoKey(assocRow.Secret, assocRow.ExpiresUtc); } - public Association GetAssociation(Uri distinguishingFactor, SecuritySettings securitySettings) { - // TODO: properly consider the securitySettings when picking an association to return. + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { // properly escape the URL to prevent injection attacks. - string value = distinguishingFactor.AbsoluteUri.Replace("'", "''"); + string value = bucket.Replace("'", "''"); string filter = string.Format( CultureInfo.InvariantCulture, "{0} = '{1}'", - dataSet.Association.DistinguishingFactorColumn.ColumnName, + dataSet.CryptoKey.BucketColumn.ColumnName, value); - string sort = dataSet.Association.ExpiresColumn.ColumnName + " DESC"; - DataView view = new DataView(dataSet.Association, filter, sort, DataViewRowState.CurrentRows); + string sort = dataSet.CryptoKey.ExpiresUtcColumn.ColumnName + " DESC"; + DataView view = new DataView(dataSet.CryptoKey, filter, sort, DataViewRowState.CurrentRows); if (view.Count == 0) { - return null; + yield break; + } + + foreach (CustomStoreDataSet.CryptoKeyRow row in view.Cast<DataRowView>().Select(rv => rv.Row)) { + yield return new KeyValuePair<string, CryptoKey>(row.Handle, new CryptoKey(row.Secret, row.ExpiresUtc)); } - var row = (CustomStoreDataSet.AssociationRow)view[0].Row; - return Association.Deserialize(row.Handle, row.Expires.ToUniversalTime(), row.PrivateData); } - public Association GetAssociation(Uri distinguishingFactor, string handle) { - var assocRow = dataSet.Association.FindByDistinguishingFactorHandle(distinguishingFactor.AbsoluteUri, handle); - return Association.Deserialize(assocRow.Handle, assocRow.Expires, assocRow.PrivateData); + public void StoreKey(string bucket, string handle, CryptoKey key) { + var cryptoKeyRow = dataSet.CryptoKey.NewCryptoKeyRow(); + cryptoKeyRow.Bucket = bucket; + cryptoKeyRow.Handle = handle; + cryptoKeyRow.ExpiresUtc = key.ExpiresUtc; + cryptoKeyRow.Secret = key.Key; + dataSet.CryptoKey.AddCryptoKeyRow(cryptoKeyRow); } - public bool RemoveAssociation(Uri distinguishingFactor, string handle) { - var row = dataSet.Association.FindByDistinguishingFactorHandle(distinguishingFactor.AbsoluteUri, handle); + public void RemoveKey(string bucket, string handle) { + var row = dataSet.CryptoKey.FindByBucketHandle(bucket, handle); if (row != null) { - dataSet.Association.RemoveAssociationRow(row); - return true; - } else { - return false; + dataSet.CryptoKey.RemoveCryptoKeyRow(row); } } - public void ClearExpiredAssociations() { - this.removeExpiredRows(dataSet.Association, dataSet.Association.ExpiresColumn.ColumnName); - } - #endregion + internal void ClearExpiredSecrets() { + this.removeExpiredRows(dataSet.CryptoKey, dataSet.CryptoKey.ExpiresUtcColumn.ColumnName); + } + private void removeExpiredRows(DataTable table, string expiredColumnName) { - string filter = string.Format(CultureInfo.InvariantCulture, "{0} < #{1}#", expiredColumnName, DateTime.Now); + string filter = string.Format(CultureInfo.InvariantCulture, "{0} < #{1}#", expiredColumnName, DateTime.UtcNow); DataView view = new DataView(table, filter, null, DataViewRowState.CurrentRows); for (int i = view.Count - 1; i >= 0; i--) { view.Delete(i); diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd index fa161fd..f3270f6 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xsd @@ -9,39 +9,39 @@ </DataSource> </xs:appinfo> </xs:annotation> - <xs:element name="CustomStoreDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" msprop:Generator_UserDSName="CustomStoreDataSet" msprop:Generator_DataSetName="CustomStoreDataSet" msprop:EnableTableAdapterManager="true"> + <xs:element name="CustomStoreDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true" msprop:EnableTableAdapterManager="true" msprop:Generator_DataSetName="CustomStoreDataSet" msprop:Generator_UserDSName="CustomStoreDataSet"> <xs:complexType> <xs:choice minOccurs="0" maxOccurs="unbounded"> - <xs:element name="Association" msprop:Generator_UserTableName="Association" msprop:Generator_RowDeletedName="AssociationRowDeleted" msprop:Generator_RowChangedName="AssociationRowChanged" msprop:Generator_RowClassName="AssociationRow" msprop:Generator_RowChangingName="AssociationRowChanging" msprop:Generator_RowEvArgName="AssociationRowChangeEvent" msprop:Generator_RowEvHandlerName="AssociationRowChangeEventHandler" msprop:Generator_TableClassName="AssociationDataTable" msprop:Generator_TableVarName="tableAssociation" msprop:Generator_RowDeletingName="AssociationRowDeleting" msprop:Generator_TablePropName="Association"> + <xs:element name="CryptoKey" msprop:Generator_UserTableName="CryptoKey" msprop:Generator_RowEvArgName="CryptoKeyRowChangeEvent" msprop:Generator_TableVarName="tableCryptoKey" msprop:Generator_TablePropName="CryptoKey" msprop:Generator_RowDeletingName="CryptoKeyRowDeleting" msprop:Generator_RowChangingName="CryptoKeyRowChanging" msprop:Generator_RowDeletedName="CryptoKeyRowDeleted" msprop:Generator_TableClassName="CryptoKeyDataTable" msprop:Generator_RowChangedName="CryptoKeyRowChanged" msprop:Generator_RowEvHandlerName="CryptoKeyRowChangeEventHandler" msprop:Generator_RowClassName="CryptoKeyRow"> <xs:complexType> <xs:sequence> - <xs:element name="DistinguishingFactor" msprop:Generator_UserColumnName="DistinguishingFactor" msprop:Generator_ColumnPropNameInRow="DistinguishingFactor" msprop:Generator_ColumnVarNameInTable="columnDistinguishingFactor" msprop:Generator_ColumnPropNameInTable="DistinguishingFactorColumn" type="xs:string" /> - <xs:element name="Handle" msprop:Generator_UserColumnName="Handle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInTable="HandleColumn" type="xs:string" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> - <xs:element name="PrivateData" msprop:Generator_UserColumnName="PrivateData" msprop:Generator_ColumnPropNameInRow="PrivateData" msprop:Generator_ColumnVarNameInTable="columnPrivateData" msprop:Generator_ColumnPropNameInTable="PrivateDataColumn" type="xs:base64Binary" /> + <xs:element name="Bucket" msprop:Generator_ColumnVarNameInTable="columnBucket" msprop:Generator_ColumnPropNameInRow="Bucket" msprop:Generator_ColumnPropNameInTable="BucketColumn" msprop:Generator_UserColumnName="Bucket" type="xs:string" /> + <xs:element name="Handle" msprop:Generator_ColumnVarNameInTable="columnHandle" msprop:Generator_ColumnPropNameInRow="Handle" msprop:Generator_ColumnPropNameInTable="HandleColumn" msprop:Generator_UserColumnName="Handle" type="xs:string" /> + <xs:element name="ExpiresUtc" msdata:DateTimeMode="Utc" msprop:Generator_ColumnVarNameInTable="columnExpiresUtc" msprop:Generator_ColumnPropNameInRow="ExpiresUtc" msprop:Generator_ColumnPropNameInTable="ExpiresUtcColumn" msprop:Generator_UserColumnName="ExpiresUtc" type="xs:dateTime" /> + <xs:element name="Secret" msprop:Generator_ColumnVarNameInTable="columnSecret" msprop:Generator_ColumnPropNameInRow="Secret" msprop:Generator_ColumnPropNameInTable="SecretColumn" msprop:Generator_UserColumnName="Secret" type="xs:base64Binary" /> </xs:sequence> </xs:complexType> </xs:element> - <xs:element name="Nonce" msprop:Generator_UserTableName="Nonce" msprop:Generator_RowDeletedName="NonceRowDeleted" msprop:Generator_RowChangedName="NonceRowChanged" msprop:Generator_RowClassName="NonceRow" msprop:Generator_RowChangingName="NonceRowChanging" msprop:Generator_RowEvArgName="NonceRowChangeEvent" msprop:Generator_RowEvHandlerName="NonceRowChangeEventHandler" msprop:Generator_TableClassName="NonceDataTable" msprop:Generator_TableVarName="tableNonce" msprop:Generator_RowDeletingName="NonceRowDeleting" msprop:Generator_TablePropName="Nonce"> + <xs:element name="Nonce" msprop:Generator_UserTableName="Nonce" msprop:Generator_RowEvArgName="NonceRowChangeEvent" msprop:Generator_TableVarName="tableNonce" msprop:Generator_TablePropName="Nonce" msprop:Generator_RowDeletingName="NonceRowDeleting" msprop:Generator_RowChangingName="NonceRowChanging" msprop:Generator_RowDeletedName="NonceRowDeleted" msprop:Generator_TableClassName="NonceDataTable" msprop:Generator_RowChangedName="NonceRowChanged" msprop:Generator_RowEvHandlerName="NonceRowChangeEventHandler" msprop:Generator_RowClassName="NonceRow"> <xs:complexType> <xs:sequence> - <xs:element name="Context" msprop:Generator_UserColumnName="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnPropNameInTable="ContextColumn" type="xs:string" /> - <xs:element name="Code" msprop:Generator_UserColumnName="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnPropNameInTable="CodeColumn" type="xs:string" /> - <xs:element name="Issued" msprop:Generator_UserColumnName="Issued" msprop:Generator_ColumnVarNameInTable="columnIssued" msprop:Generator_ColumnPropNameInRow="Issued" msprop:Generator_ColumnPropNameInTable="IssuedColumn" type="xs:dateTime" /> - <xs:element name="Expires" msprop:Generator_UserColumnName="Expires" msprop:Generator_ColumnPropNameInRow="Expires" msprop:Generator_ColumnVarNameInTable="columnExpires" msprop:Generator_ColumnPropNameInTable="ExpiresColumn" type="xs:dateTime" /> + <xs:element name="Context" msprop:Generator_ColumnVarNameInTable="columnContext" msprop:Generator_ColumnPropNameInRow="Context" msprop:Generator_ColumnPropNameInTable="ContextColumn" msprop:Generator_UserColumnName="Context" type="xs:string" /> + <xs:element name="Code" msprop:Generator_ColumnVarNameInTable="columnCode" msprop:Generator_ColumnPropNameInRow="Code" msprop:Generator_ColumnPropNameInTable="CodeColumn" msprop:Generator_UserColumnName="Code" type="xs:string" /> + <xs:element name="IssuedUtc" msdata:DateTimeMode="Utc" msprop:Generator_ColumnVarNameInTable="columnIssuedUtc" msprop:Generator_ColumnPropNameInRow="IssuedUtc" msprop:Generator_ColumnPropNameInTable="IssuedUtcColumn" msprop:Generator_UserColumnName="IssuedUtc" type="xs:dateTime" /> + <xs:element name="ExpiresUtc" msdata:DateTimeMode="Utc" msprop:Generator_ColumnVarNameInTable="columnExpiresUtc" msprop:Generator_ColumnPropNameInRow="ExpiresUtc" msprop:Generator_ColumnPropNameInTable="ExpiresUtcColumn" msprop:Generator_UserColumnName="ExpiresUtc" type="xs:dateTime" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> <xs:unique name="PrimaryKey" msdata:PrimaryKey="true"> - <xs:selector xpath=".//mstns:Association" /> - <xs:field xpath="mstns:DistinguishingFactor" /> + <xs:selector xpath=".//mstns:CryptoKey" /> + <xs:field xpath="mstns:Bucket" /> <xs:field xpath="mstns:Handle" /> </xs:unique> <xs:unique name="Constraint1" msdata:PrimaryKey="true"> <xs:selector xpath=".//mstns:Nonce" /> - <xs:field xpath="mstns:Issued" /> + <xs:field xpath="mstns:IssuedUtc" /> <xs:field xpath="mstns:Code" /> <xs:field xpath="mstns:Context" /> </xs:unique> diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss index ab8f226..631148e 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet.xss @@ -4,9 +4,9 @@ Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. </autogenerated>--> -<DiagramLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ex:showrelationlabel="False" ViewPortX="0" ViewPortY="0" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout"> +<DiagramLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ex:showrelationlabel="False" ViewPortX="-10" ViewPortY="-10" xmlns:ex="urn:schemas-microsoft-com:xml-msdatasource-layout-extended" xmlns="urn:schemas-microsoft-com:xml-msdatasource-layout"> <Shapes> - <Shape ID="DesignTable:Association" ZOrder="2" X="349" Y="83" Height="105" Width="154" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="101" /> + <Shape ID="DesignTable:CryptoKey" ZOrder="2" X="349" Y="83" Height="105" Width="154" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="101" /> <Shape ID="DesignTable:Nonce" ZOrder="1" X="604" Y="86" Height="125" Width="150" AdapterExpanded="true" DataTableExpanded="true" OldAdapterHeight="0" OldDataTableHeight="0" SplitterPosition="121" /> </Shapes> <Connectors /> diff --git a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs index 9922a4d..fa28b9c 100644 --- a/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs +++ b/samples/OpenIdRelyingPartyWebForms/Code/CustomStoreDataSet1.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30104.0 +// Runtime Version:4.0.30319.225 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -24,7 +24,7 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.ComponentModel.Design.HelpKeywordAttribute("vs.data.DataSet")] public partial class CustomStoreDataSet : global::System.Data.DataSet { - private AssociationDataTable tableAssociation; + private CryptoKeyDataTable tableCryptoKey; private NonceDataTable tableNonce; @@ -56,8 +56,8 @@ namespace OpenIdRelyingPartyWebForms.Code { if ((this.DetermineSchemaSerializationMode(info, context) == global::System.Data.SchemaSerializationMode.IncludeSchema)) { global::System.Data.DataSet ds = new global::System.Data.DataSet(); ds.ReadXmlSchema(new global::System.Xml.XmlTextReader(new global::System.IO.StringReader(strSchema))); - if ((ds.Tables["Association"] != null)) { - base.Tables.Add(new AssociationDataTable(ds.Tables["Association"])); + if ((ds.Tables["CryptoKey"] != null)) { + base.Tables.Add(new CryptoKeyDataTable(ds.Tables["CryptoKey"])); } if ((ds.Tables["Nonce"] != null)) { base.Tables.Add(new NonceDataTable(ds.Tables["Nonce"])); @@ -84,9 +84,9 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] [global::System.ComponentModel.Browsable(false)] [global::System.ComponentModel.DesignerSerializationVisibility(global::System.ComponentModel.DesignerSerializationVisibility.Content)] - public AssociationDataTable Association { + public CryptoKeyDataTable CryptoKey { get { - return this.tableAssociation; + return this.tableCryptoKey; } } @@ -167,8 +167,8 @@ namespace OpenIdRelyingPartyWebForms.Code { this.Reset(); global::System.Data.DataSet ds = new global::System.Data.DataSet(); ds.ReadXml(reader); - if ((ds.Tables["Association"] != null)) { - base.Tables.Add(new AssociationDataTable(ds.Tables["Association"])); + if ((ds.Tables["CryptoKey"] != null)) { + base.Tables.Add(new CryptoKeyDataTable(ds.Tables["CryptoKey"])); } if ((ds.Tables["Nonce"] != null)) { base.Tables.Add(new NonceDataTable(ds.Tables["Nonce"])); @@ -206,10 +206,10 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] internal void InitVars(bool initTable) { - this.tableAssociation = ((AssociationDataTable)(base.Tables["Association"])); + this.tableCryptoKey = ((CryptoKeyDataTable)(base.Tables["CryptoKey"])); if ((initTable == true)) { - if ((this.tableAssociation != null)) { - this.tableAssociation.InitVars(); + if ((this.tableCryptoKey != null)) { + this.tableCryptoKey.InitVars(); } } this.tableNonce = ((NonceDataTable)(base.Tables["Nonce"])); @@ -228,15 +228,15 @@ namespace OpenIdRelyingPartyWebForms.Code { this.Namespace = "http://tempuri.org/CustomStoreDataSet.xsd"; this.EnforceConstraints = true; this.SchemaSerializationMode = global::System.Data.SchemaSerializationMode.IncludeSchema; - this.tableAssociation = new AssociationDataTable(); - base.Tables.Add(this.tableAssociation); + this.tableCryptoKey = new CryptoKeyDataTable(); + base.Tables.Add(this.tableCryptoKey); this.tableNonce = new NonceDataTable(); base.Tables.Add(this.tableNonce); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - private bool ShouldSerializeAssociation() { + private bool ShouldSerializeCryptoKey() { return false; } @@ -302,7 +302,7 @@ namespace OpenIdRelyingPartyWebForms.Code { } [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public delegate void AssociationRowChangeEventHandler(object sender, AssociationRowChangeEvent e); + public delegate void CryptoKeyRowChangeEventHandler(object sender, CryptoKeyRowChangeEvent e); [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] public delegate void NonceRowChangeEventHandler(object sender, NonceRowChangeEvent e); @@ -312,20 +312,20 @@ namespace OpenIdRelyingPartyWebForms.Code { ///</summary> [global::System.Serializable()] [global::System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedTableSchema")] - public partial class AssociationDataTable : global::System.Data.TypedTableBase<AssociationRow> { + public partial class CryptoKeyDataTable : global::System.Data.TypedTableBase<CryptoKeyRow> { - private global::System.Data.DataColumn columnDistinguishingFactor; + private global::System.Data.DataColumn columnBucket; private global::System.Data.DataColumn columnHandle; - private global::System.Data.DataColumn columnExpires; + private global::System.Data.DataColumn columnExpiresUtc; - private global::System.Data.DataColumn columnPrivateData; + private global::System.Data.DataColumn columnSecret; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationDataTable() { - this.TableName = "Association"; + public CryptoKeyDataTable() { + this.TableName = "CryptoKey"; this.BeginInit(); this.InitClass(); this.EndInit(); @@ -333,7 +333,7 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - internal AssociationDataTable(global::System.Data.DataTable table) { + internal CryptoKeyDataTable(global::System.Data.DataTable table) { this.TableName = table.TableName; if ((table.CaseSensitive != table.DataSet.CaseSensitive)) { this.CaseSensitive = table.CaseSensitive; @@ -350,16 +350,16 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - protected AssociationDataTable(global::System.Runtime.Serialization.SerializationInfo info, global::System.Runtime.Serialization.StreamingContext context) : + protected CryptoKeyDataTable(global::System.Runtime.Serialization.SerializationInfo info, global::System.Runtime.Serialization.StreamingContext context) : base(info, context) { this.InitVars(); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn DistinguishingFactorColumn { + public global::System.Data.DataColumn BucketColumn { get { - return this.columnDistinguishingFactor; + return this.columnBucket; } } @@ -373,17 +373,17 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn ExpiresColumn { + public global::System.Data.DataColumn ExpiresUtcColumn { get { - return this.columnExpires; + return this.columnExpiresUtc; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn PrivateDataColumn { + public global::System.Data.DataColumn SecretColumn { get { - return this.columnPrivateData; + return this.columnSecret; } } @@ -398,56 +398,56 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow this[int index] { + public CryptoKeyRow this[int index] { get { - return ((AssociationRow)(this.Rows[index])); + return ((CryptoKeyRow)(this.Rows[index])); } } [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowChanging; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowChanging; [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowChanged; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowChanged; [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowDeleting; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowDeleting; [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public event AssociationRowChangeEventHandler AssociationRowDeleted; + public event CryptoKeyRowChangeEventHandler CryptoKeyRowDeleted; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public void AddAssociationRow(AssociationRow row) { + public void AddCryptoKeyRow(CryptoKeyRow row) { this.Rows.Add(row); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow AddAssociationRow(string DistinguishingFactor, string Handle, System.DateTime Expires, byte[] PrivateData) { - AssociationRow rowAssociationRow = ((AssociationRow)(this.NewRow())); + public CryptoKeyRow AddCryptoKeyRow(string Bucket, string Handle, System.DateTime ExpiresUtc, byte[] Secret) { + CryptoKeyRow rowCryptoKeyRow = ((CryptoKeyRow)(this.NewRow())); object[] columnValuesArray = new object[] { - DistinguishingFactor, + Bucket, Handle, - Expires, - PrivateData}; - rowAssociationRow.ItemArray = columnValuesArray; - this.Rows.Add(rowAssociationRow); - return rowAssociationRow; + ExpiresUtc, + Secret}; + rowCryptoKeyRow.ItemArray = columnValuesArray; + this.Rows.Add(rowCryptoKeyRow); + return rowCryptoKeyRow; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow FindByDistinguishingFactorHandle(string DistinguishingFactor, string Handle) { - return ((AssociationRow)(this.Rows.Find(new object[] { - DistinguishingFactor, + public CryptoKeyRow FindByBucketHandle(string Bucket, string Handle) { + return ((CryptoKeyRow)(this.Rows.Find(new object[] { + Bucket, Handle}))); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] public override global::System.Data.DataTable Clone() { - AssociationDataTable cln = ((AssociationDataTable)(base.Clone())); + CryptoKeyDataTable cln = ((CryptoKeyDataTable)(base.Clone())); cln.InitVars(); return cln; } @@ -455,62 +455,63 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override global::System.Data.DataTable CreateInstance() { - return new AssociationDataTable(); + return new CryptoKeyDataTable(); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] internal void InitVars() { - this.columnDistinguishingFactor = base.Columns["DistinguishingFactor"]; + this.columnBucket = base.Columns["Bucket"]; this.columnHandle = base.Columns["Handle"]; - this.columnExpires = base.Columns["Expires"]; - this.columnPrivateData = base.Columns["PrivateData"]; + this.columnExpiresUtc = base.Columns["ExpiresUtc"]; + this.columnSecret = base.Columns["Secret"]; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] private void InitClass() { - this.columnDistinguishingFactor = new global::System.Data.DataColumn("DistinguishingFactor", typeof(string), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnDistinguishingFactor); + this.columnBucket = new global::System.Data.DataColumn("Bucket", typeof(string), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnBucket); this.columnHandle = new global::System.Data.DataColumn("Handle", typeof(string), null, global::System.Data.MappingType.Element); base.Columns.Add(this.columnHandle); - this.columnExpires = new global::System.Data.DataColumn("Expires", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnExpires); - this.columnPrivateData = new global::System.Data.DataColumn("PrivateData", typeof(byte[]), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnPrivateData); + this.columnExpiresUtc = new global::System.Data.DataColumn("ExpiresUtc", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnExpiresUtc); + this.columnSecret = new global::System.Data.DataColumn("Secret", typeof(byte[]), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnSecret); this.Constraints.Add(new global::System.Data.UniqueConstraint("PrimaryKey", new global::System.Data.DataColumn[] { - this.columnDistinguishingFactor, + this.columnBucket, this.columnHandle}, true)); - this.columnDistinguishingFactor.AllowDBNull = false; + this.columnBucket.AllowDBNull = false; this.columnHandle.AllowDBNull = false; - this.columnExpires.AllowDBNull = false; - this.columnPrivateData.AllowDBNull = false; + this.columnExpiresUtc.AllowDBNull = false; + this.columnExpiresUtc.DateTimeMode = global::System.Data.DataSetDateTime.Utc; + this.columnSecret.AllowDBNull = false; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow NewAssociationRow() { - return ((AssociationRow)(this.NewRow())); + public CryptoKeyRow NewCryptoKeyRow() { + return ((CryptoKeyRow)(this.NewRow())); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override global::System.Data.DataRow NewRowFromBuilder(global::System.Data.DataRowBuilder builder) { - return new AssociationRow(builder); + return new CryptoKeyRow(builder); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override global::System.Type GetRowType() { - return typeof(AssociationRow); + return typeof(CryptoKeyRow); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowChanged(global::System.Data.DataRowChangeEventArgs e) { base.OnRowChanged(e); - if ((this.AssociationRowChanged != null)) { - this.AssociationRowChanged(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowChanged != null)) { + this.CryptoKeyRowChanged(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } @@ -518,8 +519,8 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowChanging(global::System.Data.DataRowChangeEventArgs e) { base.OnRowChanging(e); - if ((this.AssociationRowChanging != null)) { - this.AssociationRowChanging(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowChanging != null)) { + this.CryptoKeyRowChanging(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } @@ -527,8 +528,8 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowDeleted(global::System.Data.DataRowChangeEventArgs e) { base.OnRowDeleted(e); - if ((this.AssociationRowDeleted != null)) { - this.AssociationRowDeleted(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowDeleted != null)) { + this.CryptoKeyRowDeleted(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } @@ -536,14 +537,14 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] protected override void OnRowDeleting(global::System.Data.DataRowChangeEventArgs e) { base.OnRowDeleting(e); - if ((this.AssociationRowDeleting != null)) { - this.AssociationRowDeleting(this, new AssociationRowChangeEvent(((AssociationRow)(e.Row)), e.Action)); + if ((this.CryptoKeyRowDeleting != null)) { + this.CryptoKeyRowDeleting(this, new CryptoKeyRowChangeEvent(((CryptoKeyRow)(e.Row)), e.Action)); } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public void RemoveAssociationRow(AssociationRow row) { + public void RemoveCryptoKeyRow(CryptoKeyRow row) { this.Rows.Remove(row); } @@ -570,7 +571,7 @@ namespace OpenIdRelyingPartyWebForms.Code { type.Attributes.Add(attribute1); global::System.Xml.Schema.XmlSchemaAttribute attribute2 = new global::System.Xml.Schema.XmlSchemaAttribute(); attribute2.Name = "tableTypeName"; - attribute2.FixedValue = "AssociationDataTable"; + attribute2.FixedValue = "CryptoKeyDataTable"; type.Attributes.Add(attribute2); type.Particle = sequence; global::System.Xml.Schema.XmlSchema dsSchema = ds.GetSchemaSerializable(); @@ -622,9 +623,9 @@ namespace OpenIdRelyingPartyWebForms.Code { private global::System.Data.DataColumn columnCode; - private global::System.Data.DataColumn columnIssued; + private global::System.Data.DataColumn columnIssuedUtc; - private global::System.Data.DataColumn columnExpires; + private global::System.Data.DataColumn columnExpiresUtc; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] @@ -677,17 +678,17 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn IssuedColumn { + public global::System.Data.DataColumn IssuedUtcColumn { get { - return this.columnIssued; + return this.columnIssuedUtc; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public global::System.Data.DataColumn ExpiresColumn { + public global::System.Data.DataColumn ExpiresUtcColumn { get { - return this.columnExpires; + return this.columnExpiresUtc; } } @@ -728,13 +729,13 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public NonceRow AddNonceRow(string Context, string Code, System.DateTime Issued, System.DateTime Expires) { + public NonceRow AddNonceRow(string Context, string Code, System.DateTime IssuedUtc, System.DateTime ExpiresUtc) { NonceRow rowNonceRow = ((NonceRow)(this.NewRow())); object[] columnValuesArray = new object[] { Context, Code, - Issued, - Expires}; + IssuedUtc, + ExpiresUtc}; rowNonceRow.ItemArray = columnValuesArray; this.Rows.Add(rowNonceRow); return rowNonceRow; @@ -742,9 +743,9 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public NonceRow FindByIssuedCodeContext(System.DateTime Issued, string Code, string Context) { + public NonceRow FindByIssuedUtcCodeContext(System.DateTime IssuedUtc, string Code, string Context) { return ((NonceRow)(this.Rows.Find(new object[] { - Issued, + IssuedUtc, Code, Context}))); } @@ -768,8 +769,8 @@ namespace OpenIdRelyingPartyWebForms.Code { internal void InitVars() { this.columnContext = base.Columns["Context"]; this.columnCode = base.Columns["Code"]; - this.columnIssued = base.Columns["Issued"]; - this.columnExpires = base.Columns["Expires"]; + this.columnIssuedUtc = base.Columns["IssuedUtc"]; + this.columnExpiresUtc = base.Columns["ExpiresUtc"]; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] @@ -779,18 +780,20 @@ namespace OpenIdRelyingPartyWebForms.Code { base.Columns.Add(this.columnContext); this.columnCode = new global::System.Data.DataColumn("Code", typeof(string), null, global::System.Data.MappingType.Element); base.Columns.Add(this.columnCode); - this.columnIssued = new global::System.Data.DataColumn("Issued", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnIssued); - this.columnExpires = new global::System.Data.DataColumn("Expires", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); - base.Columns.Add(this.columnExpires); + this.columnIssuedUtc = new global::System.Data.DataColumn("IssuedUtc", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnIssuedUtc); + this.columnExpiresUtc = new global::System.Data.DataColumn("ExpiresUtc", typeof(global::System.DateTime), null, global::System.Data.MappingType.Element); + base.Columns.Add(this.columnExpiresUtc); this.Constraints.Add(new global::System.Data.UniqueConstraint("Constraint1", new global::System.Data.DataColumn[] { - this.columnIssued, + this.columnIssuedUtc, this.columnCode, this.columnContext}, true)); this.columnContext.AllowDBNull = false; this.columnCode.AllowDBNull = false; - this.columnIssued.AllowDBNull = false; - this.columnExpires.AllowDBNull = false; + this.columnIssuedUtc.AllowDBNull = false; + this.columnIssuedUtc.DateTimeMode = global::System.Data.DataSetDateTime.Utc; + this.columnExpiresUtc.AllowDBNull = false; + this.columnExpiresUtc.DateTimeMode = global::System.Data.DataSetDateTime.Utc; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] @@ -920,25 +923,25 @@ namespace OpenIdRelyingPartyWebForms.Code { /// <summary> ///Represents strongly named DataRow class. ///</summary> - public partial class AssociationRow : global::System.Data.DataRow { + public partial class CryptoKeyRow : global::System.Data.DataRow { - private AssociationDataTable tableAssociation; + private CryptoKeyDataTable tableCryptoKey; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - internal AssociationRow(global::System.Data.DataRowBuilder rb) : + internal CryptoKeyRow(global::System.Data.DataRowBuilder rb) : base(rb) { - this.tableAssociation = ((AssociationDataTable)(this.Table)); + this.tableCryptoKey = ((CryptoKeyDataTable)(this.Table)); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public string DistinguishingFactor { + public string Bucket { get { - return ((string)(this[this.tableAssociation.DistinguishingFactorColumn])); + return ((string)(this[this.tableCryptoKey.BucketColumn])); } set { - this[this.tableAssociation.DistinguishingFactorColumn] = value; + this[this.tableCryptoKey.BucketColumn] = value; } } @@ -946,32 +949,32 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] public string Handle { get { - return ((string)(this[this.tableAssociation.HandleColumn])); + return ((string)(this[this.tableCryptoKey.HandleColumn])); } set { - this[this.tableAssociation.HandleColumn] = value; + this[this.tableCryptoKey.HandleColumn] = value; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public System.DateTime Expires { + public System.DateTime ExpiresUtc { get { - return ((global::System.DateTime)(this[this.tableAssociation.ExpiresColumn])); + return ((global::System.DateTime)(this[this.tableCryptoKey.ExpiresUtcColumn])); } set { - this[this.tableAssociation.ExpiresColumn] = value; + this[this.tableCryptoKey.ExpiresUtcColumn] = value; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public byte[] PrivateData { + public byte[] Secret { get { - return ((byte[])(this[this.tableAssociation.PrivateDataColumn])); + return ((byte[])(this[this.tableCryptoKey.SecretColumn])); } set { - this[this.tableAssociation.PrivateDataColumn] = value; + this[this.tableCryptoKey.SecretColumn] = value; } } } @@ -1014,23 +1017,23 @@ namespace OpenIdRelyingPartyWebForms.Code { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public System.DateTime Issued { + public System.DateTime IssuedUtc { get { - return ((global::System.DateTime)(this[this.tableNonce.IssuedColumn])); + return ((global::System.DateTime)(this[this.tableNonce.IssuedUtcColumn])); } set { - this[this.tableNonce.IssuedColumn] = value; + this[this.tableNonce.IssuedUtcColumn] = value; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public System.DateTime Expires { + public System.DateTime ExpiresUtc { get { - return ((global::System.DateTime)(this[this.tableNonce.ExpiresColumn])); + return ((global::System.DateTime)(this[this.tableNonce.ExpiresUtcColumn])); } set { - this[this.tableNonce.ExpiresColumn] = value; + this[this.tableNonce.ExpiresUtcColumn] = value; } } } @@ -1039,22 +1042,22 @@ namespace OpenIdRelyingPartyWebForms.Code { ///Row event argument class ///</summary> [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public class AssociationRowChangeEvent : global::System.EventArgs { + public class CryptoKeyRowChangeEvent : global::System.EventArgs { - private AssociationRow eventRow; + private CryptoKeyRow eventRow; private global::System.Data.DataRowAction eventAction; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRowChangeEvent(AssociationRow row, global::System.Data.DataRowAction action) { + public CryptoKeyRowChangeEvent(CryptoKeyRow row, global::System.Data.DataRowAction action) { this.eventRow = row; this.eventAction = action; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")] - public AssociationRow Row { + public CryptoKeyRow Row { get { return this.eventRow; } diff --git a/samples/OpenIdRelyingPartyWebForms/Web.config b/samples/OpenIdRelyingPartyWebForms/Web.config index 1f1842b..485f8dc 100644 --- a/samples/OpenIdRelyingPartyWebForms/Web.config +++ b/samples/OpenIdRelyingPartyWebForms/Web.config @@ -41,7 +41,7 @@ <!--<add type="DotNetOpenAuth.OpenId.Behaviors.GsaIcamProfile, DotNetOpenAuth" />--> </behaviors> <!-- Uncomment the following to activate the sample custom store. --> - <!--<store type="OpenIdRelyingPartyWebForms.CustomStore, OpenIdRelyingPartyWebForms" />--> + <!--<store type="OpenIdRelyingPartyWebForms.Code.CustomStore, OpenIdRelyingPartyWebForms" />--> </relyingParty> </openid> <messaging> diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 941857d..9bae939 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -243,7 +243,7 @@ <Compile Include="OAuth\ProtocolTests.cs" /> <Compile Include="OAuth\ServiceProviderDescriptionTests.cs" /> <Compile Include="OAuth\ServiceProviderTests.cs" /> - <Compile Include="OpenId\AssociationsTests.cs" /> + <Compile Include="OpenId\RelyingParty\AssociationsTests.cs" /> <Compile Include="OpenId\AssociationTests.cs" /> <Compile Include="OpenId\AuthenticationTests.cs" /> <Compile Include="OpenId\ChannelElements\ExtensionsBindingElementTests.cs" /> diff --git a/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs b/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs index d07cf32..07743e1 100644 --- a/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/MessageSerializerTests.cs @@ -65,7 +65,7 @@ namespace DotNetOpenAuth.Test.Messaging { var ms = new MemoryStream(); var writer = JsonReaderWriterFactory.CreateJsonWriter(ms, Encoding.UTF8); - serializer.Serialize(this.MessageDescriptions.GetAccessor(message), writer); + MessageSerializer.Serialize(this.MessageDescriptions.GetAccessor(message), writer); writer.Flush(); string actual = Encoding.UTF8.GetString(ms.ToArray()); @@ -75,7 +75,7 @@ namespace DotNetOpenAuth.Test.Messaging { ms.Position = 0; var deserialized = new Mocks.TestDirectedMessage(); var reader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max); - serializer.Deserialize(this.MessageDescriptions.GetAccessor(deserialized), reader); + MessageSerializer.Deserialize(this.MessageDescriptions.GetAccessor(deserialized), reader); Assert.AreEqual(message.Age, deserialized.Age); Assert.AreEqual(message.EmptyMember, deserialized.EmptyMember); Assert.AreEqual(message.Location, deserialized.Location); @@ -86,7 +86,7 @@ namespace DotNetOpenAuth.Test.Messaging { [TestCase, ExpectedException(typeof(ArgumentNullException))] public void DeserializeNull() { var serializer = MessageSerializer.Get(typeof(Mocks.TestMessage)); - serializer.Deserialize(null, null); + MessageSerializer.Deserialize(null, null); } [TestCase] diff --git a/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs index 439acbb..515e69e 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestBadChannel.cs @@ -17,8 +17,8 @@ namespace DotNetOpenAuth.Test.Mocks { : base(badConstructorParam ? null : new TestMessageFactory()) { } - internal new void Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { - base.Create301RedirectResponse(message, fields); + internal new void Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields, bool payloadInFragment = false) { + base.Create301RedirectResponse(message, fields, payloadInFragment); } internal new void CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) { diff --git a/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs b/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs index 390a5f1..0c52b98 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AssociationHandshakeTests.cs @@ -352,9 +352,10 @@ namespace DotNetOpenAuth.Test.OpenId { if (expectSuccess) { Assert.IsNotNull(rpAssociation); - Assert.AreSame(rpAssociation, coordinator.RelyingParty.AssociationManager.AssociationStoreTestHook.GetAssociation(opDescription.Uri, rpAssociation.Handle)); - opAssociation = coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, rpAssociation.Handle); - Assert.IsNotNull(opAssociation, "The Provider should have stored the association."); + Association actual = coordinator.RelyingParty.AssociationManager.AssociationStoreTestHook.GetAssociation(opDescription.Uri, rpAssociation.Handle); + Assert.AreEqual(rpAssociation, actual); + opAssociation = coordinator.Provider.AssociationStore.Deserialize(new TestSignedDirectedMessage(), false, rpAssociation.Handle); + Assert.IsNotNull(opAssociation, "The Provider could not decode the association handle."); Assert.AreEqual(opAssociation.Handle, rpAssociation.Handle); Assert.AreEqual(expectedAssociationType, rpAssociation.GetAssociationType(protocol)); @@ -372,7 +373,6 @@ namespace DotNetOpenAuth.Test.OpenId { } } else { Assert.IsNull(coordinator.RelyingParty.AssociationManager.AssociationStoreTestHook.GetAssociation(opDescription.Uri, new RelyingPartySecuritySettings())); - Assert.IsNull(coordinator.Provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, new ProviderSecuritySettings())); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs index 27db93e..2814506 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/AuthenticationTests.cs @@ -138,8 +138,10 @@ namespace DotNetOpenAuth.Test.OpenId { private void ParameterizedAuthenticationTest(Protocol protocol, bool statelessRP, bool sharedAssociation, bool positive, bool immediate, bool tamper) { Contract.Requires<ArgumentException>(!statelessRP || !sharedAssociation, "The RP cannot be stateless while sharing an association with the OP."); Contract.Requires<ArgumentException>(positive || !tamper, "Cannot tamper with a negative response."); - ProviderSecuritySettings securitySettings = new ProviderSecuritySettings(); - Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, securitySettings) : null; + var securitySettings = new ProviderSecuritySettings(); + var cryptoKeyStore = new MemoryCryptoKeyStore(); + var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore); + Association association = sharedAssociation ? HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings) : null; var coordinator = new OpenIdCoordinator( rp => { var request = new CheckIdRequest(protocol.Version, OPUri, immediate ? AuthenticationRequestMode.Immediate : AuthenticationRequestMode.Setup); @@ -197,7 +199,8 @@ namespace DotNetOpenAuth.Test.OpenId { }, op => { if (association != null) { - op.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); + var key = cryptoKeyStore.GetCurrentKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1)); + op.CryptoKeyStore.StoreKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value); } var request = op.Channel.ReadFromRequest<CheckIdRequest>(); diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs index eaaef34..5e0ccf5 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/OpenIdChannelTests.cs @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { [SetUp] public void Setup() { this.webHandler = new Mocks.TestWebRequestHandler(); - this.channel = new OpenIdChannel(new AssociationMemoryStore<Uri>(), new NonceMemoryStore(maximumMessageAge), new RelyingPartySecuritySettings()); + this.channel = new OpenIdChannel(new MemoryCryptoKeyStore(), new NonceMemoryStore(maximumMessageAge), new RelyingPartySecuritySettings()); this.channel.WebRequestHandler = this.webHandler; } diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs index 6160680..e6f3e6e 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { using System; using System.Linq; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Messages; @@ -23,10 +24,12 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { public void SignaturesMatchKnownGood() { Protocol protocol = Protocol.V20; var settings = new ProviderSecuritySettings(); - var store = new AssociationMemoryStore<AssociationRelyingPartyType>(); + var cryptoStore = new MemoryCryptoKeyStore(); byte[] associationSecret = Convert.FromBase64String("rsSwv1zPWfjPRQU80hciu8FPDC+GONAMJQ/AvSo1a2M="); - Association association = HmacShaAssociation.Create("mock", associationSecret, TimeSpan.FromDays(1)); - store.StoreAssociation(AssociationRelyingPartyType.Smart, association); + string handle = "mock"; + cryptoStore.StoreKey(ProviderAssociationKeyStorage.SharedAssociationBucket, handle, new CryptoKey(associationSecret, DateTime.UtcNow.AddDays(1))); + + var store = new ProviderAssociationKeyStorage(cryptoStore); SigningBindingElement signer = new SigningBindingElement(store, settings); signer.Channel = new TestChannel(this.MessageDescriptions); @@ -34,7 +37,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { ITamperResistantOpenIdMessage signedMessage = message; message.ProviderEndpoint = new Uri("http://provider"); signedMessage.UtcCreationDate = DateTime.Parse("1/1/2009"); - signedMessage.AssociationHandle = association.Handle; + signedMessage.AssociationHandle = handle; Assert.IsNotNull(signer.ProcessOutgoingMessage(message)); Assert.AreEqual("o9+uN7qTaUS9v0otbHTuNAtbkpBm14+es9QnNo6IHD4=", signedMessage.Signature); } @@ -45,7 +48,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { [TestCase] public void SignedResponsesIncludeExtraDataInSignature() { Protocol protocol = Protocol.Default; - SigningBindingElement sbe = new SigningBindingElement(new AssociationMemoryStore<AssociationRelyingPartyType>(), new ProviderSecuritySettings()); + SigningBindingElement sbe = new SigningBindingElement(new ProviderAssociationHandleEncoder(new MemoryCryptoKeyStore()), new ProviderSecuritySettings()); sbe.Channel = new TestChannel(this.MessageDescriptions); IndirectSignedResponse response = new IndirectSignedResponse(protocol.Version, RPUri); response.ReturnTo = RPUri; diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs index 334fc93..9be806b 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionTestUtilities.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { using System.Diagnostics.Contracts; using System.Linq; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.ChannelElements; using DotNetOpenAuth.OpenId.Extensions; @@ -33,8 +34,10 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { Protocol protocol, IEnumerable<IOpenIdMessageExtension> requests, IEnumerable<IOpenIdMessageExtension> responses) { - ProviderSecuritySettings securitySettings = new ProviderSecuritySettings(); - Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, securitySettings); + var securitySettings = new ProviderSecuritySettings(); + var cryptoKeyStore = new MemoryCryptoKeyStore(); + var associationStore = new ProviderAssociationHandleEncoder(cryptoKeyStore); + Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.Best, AssociationRelyingPartyType.Smart, associationStore, securitySettings); var coordinator = new OpenIdCoordinator( rp => { RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory); @@ -57,7 +60,8 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions { }, op => { RegisterExtension(op.Channel, Mocks.MockOpenIdExtension.Factory); - op.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); + var key = cryptoKeyStore.GetCurrentKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, TimeSpan.FromSeconds(1)); + op.CryptoKeyStore.StoreKey(ProviderAssociationHandleEncoder.AssociationHandleEncodingSecretBucket, key.Key, key.Value); var request = op.Channel.ReadFromRequest<CheckIdRequest>(); var response = new PositiveAssertionResponse(request); var receivedRequests = request.Extensions.Cast<IOpenIdMessageExtension>(); diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs index 4530982..365c5c5 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs @@ -78,8 +78,8 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { protocol, assocType, AssociationRelyingPartyType.Smart, + this.provider.AssociationStore, this.provider.SecuritySettings); - this.provider.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, assoc); var checkidRequest = this.CreateCheckIdRequest(true); MeasurePerformance( () => { diff --git a/src/DotNetOpenAuth.Test/OpenId/AssociationsTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/AssociationsTests.cs index b3d7e4d..531fb45 100644 --- a/src/DotNetOpenAuth.Test/OpenId/AssociationsTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/AssociationsTests.cs @@ -4,13 +4,14 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.Test.OpenId { +namespace DotNetOpenAuth.Test.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.RelyingParty; using NUnit.Framework; [TestFixture] diff --git a/src/DotNetOpenAuth/CodeAnalysisDictionary.xml b/src/DotNetOpenAuth/CodeAnalysisDictionary.xml new file mode 100644 index 0000000..8c90df3 --- /dev/null +++ b/src/DotNetOpenAuth/CodeAnalysisDictionary.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8" ?> +<Dictionary> + <Words> + <!-- + This is a list of case-insensitive words that exist in the dictionary + but you do not want to be recognized by IdentifiersShouldBeSpelledCorrectly. + Do not add deprecated terms to this list, instead add these to the + <Deprecated> section below. + --> + <Unrecognized> + <!--<Word>cb</Word>--> + </Unrecognized> + <!-- + This is a list of case-insensitive words that do not exist in the dictionary + but you still want to be considered as recognized by + IdentifiersShouldBeSpelledCorrectly. Do not add compound words (e.g. 'FileName') + to this list as this will cause CompoundWordsShouldBeBeCasedCorrectly to fire on + usages of the compound word stating that they should be changed to their discrete equivalent + (for example 'FileName' -> 'Filename'). + --> + <Recognized> + <Word>OAuth</Word> + <!--<Word>cryptoKeyStore</Word> + <Word>containingMessage</Word> + <Word>httpRequestInfo</Word> + <Word>faultedMessage</Word> + <Word>keyStore</Word> + <Word>authorizationServer</Word> + <Word>bytesToSign</Word> + <Word>clientCallback</Word>--> + </Recognized> + <Deprecated> + <!-- + This is a list of deprecated terms with their preferred alternates and is + used by UsePreferredTerms. The deprecated terms are case-insensitive, + however, make sure to pascal-case the preferred alternates. If a word + does not have a preferred alternate, simply leave it blank. + --> + <!--<Term PreferredAlternate="EnterpriseServices">complus</Term>--> + </Deprecated> + <Compound> + <!-- + This is a list of discrete terms with their compound alternates and is used by + CompoundWordsShouldBeCasedCorrectly. These are words that exist in the + dictionary as discrete terms, however, should actually be cased as compound words. + For example, 'Filename' exists in the dictionary and hence the spelling rules will + not see it as unrecognized but its actual preferred usage is 'FileName'; adding it + below causes CompoundWordsShouldBeCasedCorrectly to fire. The discrete terms are + case-insensitive, however, be sure to pascal-case the compound alternates. + Any discrete terms added below automatically get added to the list of discrete + exceptions to prevent CompoundWordsShouldBeCasedCorrectly from firing both on the + compound word (for example 'WhiteSpace') and its discrete alternate (for example + 'Whitespace'). + --> + <Term CompoundAlternate="OAuth">oauth</Term> + <!--<Term CompoundAlternate="DataBind">databind</Term>--> + </Compound> + <DiscreteExceptions> + <!-- + This is a list of case-insensitive exceptions to the CompoundWordsShouldBeCasedCorrectly + discrete term check. As this check works solely on the basis of whether two consecutive + tokens exists in the dictionary, it can have a high false positive rate. For example, + 'onset' exists in the dictionary but the user probably intended it to be 'OnSet'. + Adding this word below prevents this rule from firing telling the user to change 'OnSet' + to 'Onset'. + --> + <Term>oauth</Term> + <!--<Term>onset</Term>--> + </DiscreteExceptions> + </Words> +</Dictionary>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd index 689ad2e..065b5ee 100644 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd @@ -236,6 +236,13 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute name="privateSecretMaximumAge" type="xs:string" default="28.00:00:00"> + <xs:annotation> + <xs:documentation> + The maximum age of a secret used for private signing or encryption before it is renewed. + </xs:documentation> + </xs:annotation> + </xs:attribute> </xs:complexType> </xs:element> <xs:element name="openid"> @@ -338,13 +345,6 @@ </xs:documentation> </xs:annotation> </xs:attribute> - <xs:attribute name="privateSecretMaximumAge" type="xs:string"> - <xs:annotation> - <xs:documentation> - The maximum age of a secret used for private signing before it is renewed. - </xs:documentation> - </xs:annotation> - </xs:attribute> <xs:attribute name="requireDirectedIdentity" type="xs:boolean"> <xs:annotation> <xs:documentation> @@ -581,6 +581,15 @@ </xs:documentation> </xs:annotation> </xs:attribute> + <xs:attribute name="encodeAssociationSecretsInHandles" type="xs:boolean" default="true"> + <xs:annotation> + <xs:documentation> + Whether the Provider should ease the burden of storing associations + by encoding their secrets (in signed, encrypted form) into the association handles themselves, storing only + a few rotating, private symmetric keys in the Provider's store instead. + </xs:documentation> + </xs:annotation> + </xs:attribute> <xs:attribute name="unsolicitedAssertionVerification"> <xs:annotation> <xs:documentation> diff --git a/src/DotNetOpenAuth/Configuration/MessagingElement.cs b/src/DotNetOpenAuth/Configuration/MessagingElement.cs index 24c0953..1c46bcf 100644 --- a/src/DotNetOpenAuth/Configuration/MessagingElement.cs +++ b/src/DotNetOpenAuth/Configuration/MessagingElement.cs @@ -56,6 +56,11 @@ namespace DotNetOpenAuth.Configuration { private const string MaximumIndirectMessageUrlLengthConfigName = "maximumIndirectMessageUrlLength"; /// <summary> + /// Gets the name of the @privateSecretMaximumAge attribute. + /// </summary> + private const string PrivateSecretMaximumAgeConfigName = "privateSecretMaximumAge"; + + /// <summary> /// Gets the actual maximum message lifetime that a program should allow. /// </summary> /// <value>The sum of the <see cref="MaximumMessageLifetime"/> and @@ -65,6 +70,17 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the maximum lifetime of a private symmetric secret, + /// that may be used for signing or encryption. + /// </summary> + /// <value>The default value is 28 days (twice the age of the longest association).</value> + [ConfigurationProperty(PrivateSecretMaximumAgeConfigName, DefaultValue = "28.00:00:00")] + public TimeSpan PrivateSecretMaximumAge { + get { return (TimeSpan)this[PrivateSecretMaximumAgeConfigName]; } + set { this[PrivateSecretMaximumAgeConfigName] = value; } + } + + /// <summary> /// Gets or sets the time between a message's creation and its receipt /// before it is considered expired. /// </summary> diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs index b51ccfb..a594e86 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderElement.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Configuration { using System.Configuration; using System.Diagnostics.Contracts; + using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Provider; /// <summary> @@ -58,8 +59,8 @@ namespace DotNetOpenAuth.Configuration { /// Gets or sets the type to use for storing application state. /// </summary> [ConfigurationProperty(StoreConfigName)] - public TypeConfigurationElement<IProviderApplicationStore> ApplicationStore { - get { return (TypeConfigurationElement<IProviderApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IProviderApplicationStore>(); } + public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore { + get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); } set { this[StoreConfigName] = value; } } } diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs index 3545fc5..0d8e8b4 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs @@ -36,6 +36,11 @@ namespace DotNetOpenAuth.Configuration { private const string AssociationsConfigName = "associations"; /// <summary> + /// The name of the @encodeAssociationSecretsInHandles attribute. + /// </summary> + private const string EncodeAssociationSecretsInHandlesConfigName = "encodeAssociationSecretsInHandles"; + + /// <summary> /// Gets the name of the @requireSsl attribute. /// </summary> private const string RequireSslConfigName = "requireSsl"; @@ -116,6 +121,17 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets a value indicating whether the Provider should ease the burden of storing associations + /// by encoding their secrets (in signed, encrypted form) into the association handles themselves, storing only + /// a few rotating, private symmetric keys in the Provider's store instead. + /// </summary> + [ConfigurationProperty(EncodeAssociationSecretsInHandlesConfigName, DefaultValue = ProviderSecuritySettings.EncodeAssociationSecretsInHandlesDefault)] + public bool EncodeAssociationSecretsInHandles { + get { return (bool)this[EncodeAssociationSecretsInHandlesConfigName]; } + set { this[EncodeAssociationSecretsInHandlesConfigName] = 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> @@ -126,6 +142,7 @@ namespace DotNetOpenAuth.Configuration { settings.MaximumHashBitLength = this.MaximumHashBitLength; settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; settings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification; + settings.EncodeAssociationSecretsInHandles = this.EncodeAssociationSecretsInHandles; foreach (AssociationTypeElement element in this.AssociationLifetimes) { Contract.Assume(element != null); settings.AssociationLifetimes.Add(element.AssociationType, element.MaximumLifetime); diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs index 915776c..17b890f 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartyElement.cs @@ -87,8 +87,8 @@ namespace DotNetOpenAuth.Configuration { /// Gets or sets the type to use for storing application state. /// </summary> [ConfigurationProperty(StoreConfigName)] - public TypeConfigurationElement<IRelyingPartyApplicationStore> ApplicationStore { - get { return (TypeConfigurationElement<IRelyingPartyApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IRelyingPartyApplicationStore>(); } + public TypeConfigurationElement<IOpenIdApplicationStore> ApplicationStore { + get { return (TypeConfigurationElement<IOpenIdApplicationStore>)this[StoreConfigName] ?? new TypeConfigurationElement<IOpenIdApplicationStore>(); } set { this[StoreConfigName] = value; } } diff --git a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs index 4347e2c..e116f52 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdRelyingPartySecuritySettingsElement.cs @@ -61,11 +61,6 @@ namespace DotNetOpenAuth.Configuration { private const string IgnoreUnsignedExtensionsConfigName = "ignoreUnsignedExtensions"; /// <summary> - /// Gets the name of the @privateSecretMaximumAge attribute. - /// </summary> - private const string PrivateSecretMaximumAgeConfigName = "privateSecretMaximumAge"; - - /// <summary> /// Gets the name of the @allowDualPurposeIdentifiers attribute. /// </summary> private const string AllowDualPurposeIdentifiersConfigName = "allowDualPurposeIdentifiers"; @@ -152,17 +147,6 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> - /// Gets or sets the maximum allowable age of the secret a Relying Party - /// uses to its return_to URLs and nonces with 1.0 Providers. - /// </summary> - /// <value>The default value is 7 days.</value> - [ConfigurationProperty(PrivateSecretMaximumAgeConfigName, DefaultValue = "07:00:00")] - public TimeSpan PrivateSecretMaximumAge { - get { return (TimeSpan)this[PrivateSecretMaximumAgeConfigName]; } - set { this[PrivateSecretMaximumAgeConfigName] = value; } - } - - /// <summary> /// Gets or sets a value indicating whether all unsolicited assertions should be ignored. /// </summary> /// <value>The default value is <c>false</c>.</value> @@ -263,7 +247,7 @@ namespace DotNetOpenAuth.Configuration { settings.MinimumRequiredOpenIdVersion = this.MinimumRequiredOpenIdVersion; settings.MinimumHashBitLength = this.MinimumHashBitLength; settings.MaximumHashBitLength = this.MaximumHashBitLength; - settings.PrivateSecretMaximumAge = this.PrivateSecretMaximumAge; + settings.PrivateSecretMaximumAge = DotNetOpenAuthSection.Configuration.Messaging.PrivateSecretMaximumAge; settings.RejectUnsolicitedAssertions = this.RejectUnsolicitedAssertions; settings.RejectDelegatingIdentifiers = this.RejectDelegatingIdentifiers; settings.IgnoreUnsignedExtensions = this.IgnoreUnsignedExtensions; diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index cbd3b43..0c3d87f 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -275,6 +275,7 @@ http://opensource.org/licenses/ms-pl.html <Reference Include="System.Web.Mvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" /> </ItemGroup> <ItemGroup> + <Compile Include="Messaging\Bindings\AsymmetricCryptoKeyStoreWrapper.cs" /> <Compile Include="ComponentModel\ClaimTypeSuggestions.cs" /> <Compile Include="ComponentModel\ConverterBase.cs" /> <Compile Include="ComponentModel\SuggestedStringsConverterContract.cs" /> @@ -305,6 +306,9 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="Configuration\HostNameOrRegexCollection.cs" /> <Compile Include="Configuration\HostNameElement.cs" /> <Compile Include="Configuration\XriResolverElement.cs" /> + <Compile Include="Messaging\Bindings\CryptoKey.cs" /> + <Compile Include="Messaging\Bindings\CryptoKeyCollisionException.cs" /> + <Compile Include="Messaging\Bindings\ICryptoKeyStore.cs" /> <Compile Include="IEmbeddedResourceRetrieval.cs" /> <Compile Include="InfoCard\ClaimType.cs" /> <Compile Include="InfoCard\InfoCardImage.cs" /> @@ -318,8 +322,11 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="InfoCard\Token\TokenUtility.cs" /> <Compile Include="InfoCard\Token\TokenDecryptor.cs" /> <Compile Include="InfoCard\WellKnownIssuers.cs" /> + <Compile Include="Messaging\Bindings\MemoryCryptoKeyStore.cs" /> + <Compile Include="Messaging\BinaryDataBagFormatter.cs" /> <Compile Include="Messaging\CachedDirectWebResponse.cs" /> <Compile Include="Messaging\ChannelContract.cs" /> + <Compile Include="Messaging\DataBagFormatterBase.cs" /> <Compile Include="Messaging\IHttpIndirectResponse.cs" /> <Compile Include="Messaging\IMessageOriginalPayload.cs" /> <Compile Include="Messaging\DirectWebRequestOptions.cs" /> @@ -339,6 +346,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="Messaging\IncomingWebResponseContract.cs" /> <Compile Include="Messaging\IProtocolMessageWithExtensions.cs" /> <Compile Include="Messaging\InternalErrorException.cs" /> + <Compile Include="Messaging\IStreamSerializingDataBag.cs" /> <Compile Include="Messaging\KeyedCollectionDelegate.cs" /> <Compile Include="Messaging\MultipartPostPart.cs" /> <Compile Include="Messaging\NetworkDirectWebResponse.cs" /> @@ -360,7 +368,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuth2\ChannelElements\OAuth2ChannelBase.cs" /> <Compile Include="OAuth2\ChannelElements\OAuth2ClientChannel.cs" /> <Compile Include="OAuth2\ChannelElements\ScopeEncoder.cs" /> - <Compile Include="OAuth2\ChannelElements\UriStyleMessageFormatter.cs" /> + <Compile Include="Messaging\UriStyleMessageFormatter.cs" /> <Compile Include="OAuth2\ChannelElements\IAuthorizationDescription.cs" /> <Compile Include="OAuth2\ChannelElements\ITokenCarryingRequest.cs" /> <Compile Include="OAuth2\ChannelElements\OAuth2ResourceServerChannel.cs" /> @@ -492,8 +500,13 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuth\ChannelElements\OAuthServiceProviderMessageFactory.cs" /> <Compile Include="Messaging\ProtocolException.cs" /> <Compile Include="OpenId\Association.cs" /> - <Compile Include="OpenId\AssociationMemoryStore.cs" /> - <Compile Include="OpenId\Associations.cs" /> + <Compile Include="OpenId\Provider\AssociationDataBag.cs" /> + <Compile Include="OpenId\Provider\IProviderAssociationStore.cs" /> + <Compile Include="OpenId\Provider\ProviderAssociationHandleEncoder.cs" /> + <Compile Include="OpenId\Provider\ProviderAssociationKeyStorage.cs" /> + <Compile Include="OpenId\RelyingParty\CryptoKeyStoreAsRelyingPartyAssociationStore.cs" /> + <Compile Include="OpenId\RelyingParty\IRelyingPartyAssociationStore.cs" /> + <Compile Include="OpenId\RelyingParty\Associations.cs" /> <Compile Include="OpenId\Behaviors\AXFetchAsSregTransform.cs" /> <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> <AutoGen>True</AutoGen> @@ -573,6 +586,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\Messages\SignedResponseRequest.cs" /> <Compile Include="OpenId\NoDiscoveryIdentifier.cs" /> <Compile Include="OpenId\OpenIdUtilities.cs" /> + <Compile Include="OpenId\Provider\AssociationRelyingPartyType.cs" /> <Compile Include="OpenId\Provider\PrivatePersonalIdentifierProviderBase.cs" /> <Compile Include="OpenId\Provider\AnonymousRequest.cs" /> <Compile Include="OpenId\Provider\AnonymousRequestEventArgs.cs" /> @@ -585,7 +599,6 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\Provider\IDirectedIdentityIdentifierProvider.cs" /> <Compile Include="OpenId\Provider\IHostProcessedRequest.cs" /> <Compile Include="OpenId\Provider\IErrorReporting.cs" /> - <Compile Include="OpenId\Provider\IProviderApplicationStore.cs" /> <Compile Include="OpenId\Provider\IProviderBehavior.cs" /> <Compile Include="OpenId\Provider\IRequest.cs" /> <Compile Include="OpenId\Provider\ProviderEndpoint.cs" /> @@ -607,7 +620,6 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\DiffieHellman\mono\PrimeGeneratorBase.cs" /> <Compile Include="OpenId\DiffieHellman\mono\SequentialSearchPrimeGeneratorBase.cs" /> <Compile Include="OpenId\HmacShaAssociation.cs" /> - <Compile Include="OpenId\IAssociationStore.cs" /> <Compile Include="OpenId\Messages\AssociateUnencryptedRequest.cs" /> <Compile Include="OpenId\Provider\OpenIdProvider.cs" /> <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequest.cs" /> @@ -647,9 +659,8 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OpenId\Protocol.cs" /> <Compile Include="OpenId\ProviderEndpointDescription.cs" /> <Compile Include="OpenId\Provider\ProviderSecuritySettings.cs" /> - <Compile Include="OpenId\RelyingParty\IRelyingPartyApplicationStore.cs" /> + <Compile Include="OpenId\IOpenIdApplicationStore.cs" /> <Compile Include="OpenId\RelyingParty\PositiveAuthenticationResponseSnapshot.cs" /> - <Compile Include="OpenId\RelyingParty\PrivateSecretManager.cs" /> <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" /> <Compile Include="OpenId\IdentifierDiscoveryResult.cs" /> <Compile Include="OpenId\OpenIdXrdsHelper.cs" /> @@ -849,6 +860,7 @@ http://opensource.org/licenses/ms-pl.html <ProductName>Windows Installer 3.1</ProductName> <Install>true</Install> </BootstrapperPackage> + <CodeAnalysisDictionary Include="CodeAnalysisDictionary.xml" /> <Content Include="DotNetOpenAuth.ico" /> </ItemGroup> <ItemGroup> @@ -865,16 +877,9 @@ http://opensource.org/licenses/ms-pl.html <ILMergeTargetPlatformDirectory Condition=" '$(ClrVersion)' == '4' ">"$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"</ILMergeTargetPlatformDirectory> </PropertyGroup> <MakeDir Directories="$(ILMergeOutputAssemblyDirectory)" /> - <ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" - InputAssemblies="@(ILMergeInputAssemblies)" - OutputFile="$(ILMergeOutputAssembly)" - KeyFile="$(PublicKeyFile)" - DelaySign="true" - ToolPath="$(ProjectRoot)tools\ILMerge" - TargetPlatformVersion="$(ClrVersion).0" - TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> + <ILMerge ExcludeFile="$(ProjectRoot)ILMergeInternalizeExceptions.txt" InputAssemblies="@(ILMergeInputAssemblies)" OutputFile="$(ILMergeOutputAssembly)" KeyFile="$(PublicKeyFile)" DelaySign="true" ToolPath="$(ProjectRoot)tools\ILMerge" TargetPlatformVersion="$(ClrVersion).0" TargetPlatformDirectory="$(ILMergeTargetPlatformDirectory)" /> </Target> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(ProjectRoot)tools\DotNetOpenAuth.targets" /> <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))\EnlistmentInfo.targets" Condition=" '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), EnlistmentInfo.targets))' != '' " /> -</Project> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs index 8539422..2bc6c04 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth/GlobalSuppressions.cs @@ -67,3 +67,4 @@ using System.Diagnostics.CodeAnalysis; [assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "iframe", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] [assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Sig", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] [assembly: SuppressMessage("Microsoft.Naming", "CA1701:ResourceStringCompoundWordsShouldBeCasedCorrectly", MessageId = "DSig", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OAuth2.ChannelElements")] diff --git a/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs b/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs new file mode 100644 index 0000000..591adc3 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------- +// <copyright file="BinaryDataBagFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A compact binary <see cref="DataBag"/> serialization class. + /// </summary> + /// <typeparam name="T">The <see cref="DataBag"/>-derived type to serialize/deserialize.</typeparam> + internal class BinaryDataBagFormatter<T> : DataBagFormatterBase<T> where T : DataBag, IStreamSerializingDataBag, new() { + /// <summary> + /// Initializes a new instance of the <see cref="BinaryDataBagFormatter<T>"/> class. + /// </summary> + /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> + /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal BinaryDataBagFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="BinaryDataBagFormatter<T>"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="minimumAge">The minimum age.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal BinaryDataBagFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { + Contract.Requires<ArgumentException>((cryptoKeyStore != null && bucket != null) || (!signed && !encrypted)); + } + + /// <summary> + /// Serializes the <see cref="DataBag"/> instance to a buffer. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The buffer containing the serialized data.</returns> + protected override byte[] SerializeCore(T message) { + using (var stream = new MemoryStream()) { + message.Serialize(stream); + return stream.ToArray(); + } + } + + /// <summary> + /// Deserializes the <see cref="DataBag"/> instance from a buffer. + /// </summary> + /// <param name="message">The message instance to initialize with data from the buffer.</param> + /// <param name="data">The data buffer.</param> + protected override void DeserializeCore(T message, byte[] data) { + using (var stream = new MemoryStream(data)) { + message.Deserialize(stream); + } + + // Perform basic validation on message that the MessageSerializer would have normally performed. + var messageDescription = MessageDescriptions.Get(message); + var dictionary = messageDescription.GetDictionary(message); + messageDescription.EnsureMessagePartsPassBasicValidation(dictionary); + IMessage m = message; + m.EnsureValidMessage(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs b/src/DotNetOpenAuth/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs new file mode 100644 index 0000000..0f2ac05 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Bindings/AsymmetricCryptoKeyStoreWrapper.cs @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------- +// <copyright file="AsymmetricCryptoKeyStoreWrapper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Provides RSA encryption of symmetric keys to protect them from a theft of + /// the persistent store. + /// </summary> + public class AsymmetricCryptoKeyStoreWrapper : ICryptoKeyStore { + /// <summary> + /// The persistent store for asymmetrically encrypted symmetric keys. + /// </summary> + private readonly ICryptoKeyStore dataStore; + + /// <summary> + /// The memory cache of decrypted keys. + /// </summary> + private readonly MemoryCryptoKeyStore cache = new MemoryCryptoKeyStore(); + + /// <summary> + /// The asymmetric algorithm to use encrypting/decrypting the symmetric keys. + /// </summary> + private readonly RSACryptoServiceProvider asymmetricCrypto; + + /// <summary> + /// Initializes a new instance of the <see cref="AsymmetricCryptoKeyStoreWrapper"/> class. + /// </summary> + /// <param name="dataStore">The data store.</param> + /// <param name="asymmetricCrypto">The asymmetric protection to apply to symmetric keys. Must include the private key.</param> + public AsymmetricCryptoKeyStoreWrapper(ICryptoKeyStore dataStore, RSACryptoServiceProvider asymmetricCrypto) { + Contract.Requires<ArgumentNullException>(dataStore != null); + Contract.Requires<ArgumentNullException>(asymmetricCrypto != null); + Contract.Requires<ArgumentException>(!asymmetricCrypto.PublicOnly); + this.dataStore = dataStore; + this.asymmetricCrypto = asymmetricCrypto; + } + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + var key = this.dataStore.GetKey(bucket, handle); + return this.Decrypt(bucket, handle, key); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.dataStore.GetKeys(bucket) + .Select(pair => new KeyValuePair<string, CryptoKey>(pair.Key, this.Decrypt(bucket, pair.Key, pair.Value))); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="decryptedCryptoKey">The key to store.</param> + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")] + public void StoreKey(string bucket, string handle, CryptoKey decryptedCryptoKey) { + byte[] encryptedKey = this.asymmetricCrypto.Encrypt(decryptedCryptoKey.Key, true); + var encryptedCryptoKey = new CryptoKey(encryptedKey, decryptedCryptoKey.ExpiresUtc); + this.dataStore.StoreKey(bucket, handle, encryptedCryptoKey); + + this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey)); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.dataStore.RemoveKey(bucket, handle); + this.cache.RemoveKey(bucket, handle); + } + + /// <summary> + /// Decrypts the specified key. + /// </summary> + /// <param name="bucket">The bucket.</param> + /// <param name="handle">The handle.</param> + /// <param name="encryptedCryptoKey">The encrypted key.</param> + /// <returns> + /// The decrypted key. + /// </returns> + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Helps readability because multiple keys are involved.")] + private CryptoKey Decrypt(string bucket, string handle, CryptoKey encryptedCryptoKey) { + if (encryptedCryptoKey == null) { + return null; + } + + // Avoid the asymmetric decryption if possible by looking up whether we have that in our cache. + CachedCryptoKey cached = (CachedCryptoKey)this.cache.GetKey(bucket, handle); + if (cached != null && MessagingUtilities.AreEquivalent(cached.EncryptedKey, encryptedCryptoKey.Key)) { + return cached; + } + + byte[] decryptedKey = this.asymmetricCrypto.Decrypt(encryptedCryptoKey.Key, true); + var decryptedCryptoKey = new CryptoKey(decryptedKey, encryptedCryptoKey.ExpiresUtc); + + // Store the decrypted version in the cache to save time next time. + this.cache.StoreKey(bucket, handle, new CachedCryptoKey(encryptedCryptoKey, decryptedCryptoKey)); + + return decryptedCryptoKey; + } + + /// <summary> + /// An encrypted key and its decrypted equivalent. + /// </summary> + private class CachedCryptoKey : CryptoKey { + /// <summary> + /// Initializes a new instance of the <see cref="CachedCryptoKey"/> class. + /// </summary> + /// <param name="encrypted">The encrypted key.</param> + /// <param name="decrypted">The decrypted key.</param> + internal CachedCryptoKey(CryptoKey encrypted, CryptoKey decrypted) + : base(decrypted.Key, decrypted.ExpiresUtc) { + Contract.Requires(encrypted != null); + Contract.Requires(decrypted != null); + Contract.Requires(encrypted.ExpiresUtc == decrypted.ExpiresUtc); + + this.EncryptedKey = encrypted.Key; + } + + /// <summary> + /// Gets the encrypted key. + /// </summary> + internal byte[] EncryptedKey { get; private set; } + + /// <summary> + /// Invariant conditions. + /// </summary> + [ContractInvariantMethod] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")] + private void ObjectInvariant() { + Contract.Invariant(this.EncryptedKey != null); + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKey.cs b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKey.cs new file mode 100644 index 0000000..cd10199 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKey.cs @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------- +// <copyright file="CryptoKey.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A cryptographic key and metadata concerning it. + /// </summary> + public class CryptoKey { + /// <summary> + /// Backing field for the <see cref="Key"/> property. + /// </summary> + private readonly byte[] key; + + /// <summary> + /// Backing field for the <see cref="ExpiresUtc"/> property. + /// </summary> + private readonly DateTime expiresUtc; + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKey"/> class. + /// </summary> + /// <param name="key">The cryptographic key.</param> + /// <param name="expiresUtc">The expires UTC.</param> + public CryptoKey(byte[] key, DateTime expiresUtc) { + Contract.Requires<ArgumentNullException>(key != null); + Contract.Requires<ArgumentException>(expiresUtc.Kind == DateTimeKind.Utc); + this.key = key; + this.expiresUtc = expiresUtc; + } + + /// <summary> + /// Gets the key. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's a buffer")] + public byte[] Key { + get { + Contract.Ensures(Contract.Result<byte[]>() != null); + return this.key; + } + } + + /// <summary> + /// Gets the expiration date of this key (UTC time). + /// </summary> + public DateTime ExpiresUtc { + get { + Contract.Ensures(Contract.Result<DateTime>().Kind == DateTimeKind.Utc); + return this.expiresUtc; + } + } + + /// <summary> + /// Determines whether the specified <see cref="System.Object"/> is equal to this instance. + /// </summary> + /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param> + /// <returns> + /// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>. + /// </returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <paramref name="obj"/> parameter is null. + /// </exception> + public override bool Equals(object obj) { + var other = obj as CryptoKey; + if (other == null) { + return false; + } + + return this.ExpiresUtc == other.ExpiresUtc + && MessagingUtilities.AreEquivalent(this.Key, other.Key); + } + + /// <summary> + /// Returns a hash code for this instance. + /// </summary> + /// <returns> + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// </returns> + public override int GetHashCode() { + return this.ExpiresUtc.GetHashCode(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs new file mode 100644 index 0000000..11634ac --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="CryptoKeyCollisionException.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Security.Permissions; + + /// <summary> + /// Thrown by a hosting application or web site when a cryptographic key is created with a + /// bucket and handle that conflicts with a previously stored and unexpired key. + /// </summary> + [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "Specialized exception has no need of a message parameter.")] + [Serializable] + public class CryptoKeyCollisionException : ArgumentException { + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. + /// </summary> + public CryptoKeyCollisionException() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. + /// </summary> + /// <param name="inner">The inner exception to include.</param> + public CryptoKeyCollisionException(Exception inner) : base(null, inner) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyCollisionException"/> class. + /// </summary> + /// <param name="info">The <see cref="System.Runtime.Serialization.SerializationInfo"/> + /// that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The System.Runtime.Serialization.StreamingContext + /// that contains contextual information about the source or destination.</param> + protected CryptoKeyCollisionException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) + : base(info, context) { + throw new NotImplementedException(); + } + + /// <summary> + /// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with information about the exception. + /// </summary> + /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> + /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"/> that contains contextual information about the source or destination.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// The <paramref name="info"/> parameter is a null reference (Nothing in Visual Basic). + /// </exception> + /// <PermissionSet> + /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Read="*AllFiles*" PathDiscovery="*AllFiles*"/> + /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="SerializationFormatter"/> + /// </PermissionSet> +#if CLR4 + [SecurityCritical] +#else + [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] +#endif + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { + base.GetObjectData(info, context); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs b/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs index 31b053e..cfe7f6c 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/ExpiredMessageException.cs @@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { public ExpiredMessageException(DateTime utcExpirationDate, IProtocolMessage faultedMessage) : base(string.Format(CultureInfo.CurrentCulture, MessagingStrings.ExpiredMessage, utcExpirationDate.ToLocalTime(), DateTime.Now), faultedMessage) { Contract.Requires<ArgumentException>(utcExpirationDate.Kind == DateTimeKind.Utc); - Contract.Requires<ArgumentNullException>(faultedMessage != null, "faultedMessage"); + Contract.Requires<ArgumentNullException>(faultedMessage != null); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Bindings/ICryptoKeyStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/ICryptoKeyStore.cs new file mode 100644 index 0000000..815c488 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Bindings/ICryptoKeyStore.cs @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------- +// <copyright file="ICryptoKeyStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A persistent store for rotating symmetric cryptographic keys. + /// </summary> + /// <remarks> + /// Implementations should persist it in such a way that the keys are shared across all servers + /// on a web farm, where applicable. + /// The store should consider protecting the persistent store against theft resulting in the loss + /// of the confidentiality of the keys. One possible mitigation is to asymmetrically encrypt + /// each key using a certificate installed in the server's certificate store. + /// </remarks> + [ContractClass(typeof(ICryptoKeyStoreContract))] + public interface ICryptoKeyStore { + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns>The cryptographic key, or <c>null</c> if no matching key was found.</returns> + CryptoKey GetKey(string bucket, string handle); + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns>A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>.</returns> + [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Important for scalability")] + IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket); + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + void StoreKey(string bucket, string handle, CryptoKey key); + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + void RemoveKey(string bucket, string handle); + } + + /// <summary> + /// Code contract for the <see cref="ICryptoKeyStore"/> interface. + /// </summary> + [ContractClassFor(typeof(ICryptoKeyStore))] + internal abstract class ICryptoKeyStoreContract : ICryptoKeyStore { + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + CryptoKey ICryptoKeyStore.GetKey(string bucket, string handle) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + IEnumerable<KeyValuePair<string, CryptoKey>> ICryptoKeyStore.GetKeys(string bucket) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); + Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<string, CryptoKey>>>() != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + void ICryptoKeyStore.StoreKey(string bucket, string handle, CryptoKey key) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + Contract.Requires<ArgumentNullException>(key != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + void ICryptoKeyStore.RemoveKey(string bucket, string handle) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Bindings/MemoryCryptoKeyStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/MemoryCryptoKeyStore.cs new file mode 100644 index 0000000..63d1953 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Bindings/MemoryCryptoKeyStore.cs @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------- +// <copyright file="MemoryCryptoKeyStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Bindings { + using System; + using System.Collections.Generic; + using System.Linq; + + /// <summary> + /// A in-memory store of crypto keys. + /// </summary> + internal class MemoryCryptoKeyStore : ICryptoKeyStore { + /// <summary> + /// How frequently to check for and remove expired secrets. + /// </summary> + private static readonly TimeSpan cleaningInterval = TimeSpan.FromMinutes(30); + + /// <summary> + /// An in-memory cache of decrypted symmetric keys. + /// </summary> + /// <remarks> + /// The key is the bucket name. The value is a dictionary whose key is the handle and whose value is the cached key. + /// </remarks> + private readonly Dictionary<string, Dictionary<string, CryptoKey>> store = new Dictionary<string, Dictionary<string, CryptoKey>>(StringComparer.Ordinal); + + /// <summary> + /// The last time the cache had expired keys removed from it. + /// </summary> + private DateTime lastCleaning = DateTime.UtcNow; + + /// <summary> + /// Gets the key in a given bucket and handle. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + lock (this.store) { + Dictionary<string, CryptoKey> cacheBucket; + if (this.store.TryGetValue(bucket, out cacheBucket)) { + CryptoKey key; + if (cacheBucket.TryGetValue(handle, out key)) { + return key; + } + } + } + + return null; + } + + /// <summary> + /// Gets a sequence of existing keys within a given bucket. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <returns> + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. + /// </returns> + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + lock (this.store) { + Dictionary<string, CryptoKey> cacheBucket; + if (this.store.TryGetValue(bucket, out cacheBucket)) { + return cacheBucket.ToList(); + } else { + return Enumerable.Empty<KeyValuePair<string, CryptoKey>>(); + } + } + } + + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + lock (this.store) { + Dictionary<string, CryptoKey> cacheBucket; + if (!this.store.TryGetValue(bucket, out cacheBucket)) { + this.store[bucket] = cacheBucket = new Dictionary<string, CryptoKey>(StringComparer.Ordinal); + } + + if (cacheBucket.ContainsKey(handle)) { + throw new CryptoKeyCollisionException(); + } + + cacheBucket[handle] = key; + + this.CleanExpiredKeysFromMemoryCacheIfAppropriate(); + } + } + + /// <summary> + /// Removes the key. + /// </summary> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + lock (this.store) { + Dictionary<string, CryptoKey> cacheBucket; + if (this.store.TryGetValue(bucket, out cacheBucket)) { + cacheBucket.Remove(handle); + } + } + } + + /// <summary> + /// Cleans the expired keys from memory cache if the cleaning interval has passed. + /// </summary> + private void CleanExpiredKeysFromMemoryCacheIfAppropriate() { + if (DateTime.UtcNow > this.lastCleaning + cleaningInterval) { + lock (this.store) { + if (DateTime.UtcNow > this.lastCleaning + cleaningInterval) { + this.ClearExpiredKeysFromMemoryCache(); + } + } + } + } + + /// <summary> + /// Weeds out expired keys from the in-memory cache. + /// </summary> + private void ClearExpiredKeysFromMemoryCache() { + lock (this.store) { + var emptyBuckets = new List<string>(); + foreach (var bucketPair in this.store) { + var expiredKeys = new List<string>(); + foreach (var handlePair in bucketPair.Value) { + if (handlePair.Value.ExpiresUtc < DateTime.UtcNow) { + expiredKeys.Add(handlePair.Key); + } + } + + foreach (var expiredKey in expiredKeys) { + bucketPair.Value.Remove(expiredKey); + } + + if (bucketPair.Value.Count == 0) { + emptyBuckets.Add(bucketPair.Key); + } + } + + foreach (string emptyBucket in emptyBuckets) { + this.store.Remove(emptyBucket); + } + + this.lastCleaning = DateTime.UtcNow; + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index f9961bf..ded5662 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -48,8 +48,14 @@ namespace DotNetOpenAuth.Messaging { protected internal const string JsonEncoded = "application/json"; /// <summary> + /// The "text/javascript" content-type that some servers return instead of the standard <see cref="JsonEncoded"/> one. + /// </summary> + protected internal const string JsonTextEncoded = "text/javascript"; + + /// <summary> /// The content-type for plain text. /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "PlainText", Justification = "Not 'Plaintext' in the crypographic sense.")] protected internal const string PlainTextEncoded = "text/plain"; /// <summary> @@ -763,7 +769,7 @@ namespace DotNetOpenAuth.Messaging { protected virtual OutgoingWebResponse PrepareIndirectResponse(IDirectedProtocolMessage message) { Contract.Requires<ArgumentNullException>(message != null); Contract.Requires<ArgumentException>(message.Recipient != null, MessagingStrings.DirectedMessageMissingRecipient); - Contract.Requires<ArgumentException>((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0, "Neither GET nor POST are allowed for this message."); + Contract.Requires<ArgumentException>((message.HttpMethods & (HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.PostRequest)) != 0); Contract.Ensures(Contract.Result<OutgoingWebResponse>() != null); Contract.Assert(message != null && message.Recipient != null); @@ -918,18 +924,20 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="message">The message to serialize.</param> /// <returns>A JSON string.</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] protected virtual string SerializeAsJson(IMessage message) { - Contract.Requires<ArgumentNullException>(message != null, "message"); + Contract.Requires<ArgumentNullException>(message != null); MessageDictionary messageDictionary = this.MessageDescriptions.GetAccessor(message); - var memoryStream = new MemoryStream(); - var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream, Encoding.UTF8); - var serializer = MessageSerializer.Get(message.GetType()); - serializer.Serialize(messageDictionary, jsonWriter); - jsonWriter.Flush(); - - string json = Encoding.UTF8.GetString(memoryStream.ToArray()); - return json; + using (var memoryStream = new MemoryStream()) { + using (var jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(memoryStream, Encoding.UTF8)) { + MessageSerializer.Serialize(messageDictionary, jsonWriter); + jsonWriter.Flush(); + } + + string json = Encoding.UTF8.GetString(memoryStream.ToArray()); + return json; + } } /// <summary> @@ -941,8 +949,9 @@ namespace DotNetOpenAuth.Messaging { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(json)); var dictionary = new Dictionary<string, string>(); - var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), this.XmlDictionaryReaderQuotas); - MessageSerializer.DeserializeJsonAsFlatDictionary(dictionary, jsonReader); + using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), this.XmlDictionaryReaderQuotas)) { + MessageSerializer.DeserializeJsonAsFlatDictionary(dictionary, jsonReader); + } return dictionary; } diff --git a/src/DotNetOpenAuth/Messaging/DataBag.cs b/src/DotNetOpenAuth/Messaging/DataBag.cs index 91ccc13..17a7bda 100644 --- a/src/DotNetOpenAuth/Messaging/DataBag.cs +++ b/src/DotNetOpenAuth/Messaging/DataBag.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; /// <summary> @@ -81,14 +82,13 @@ namespace DotNetOpenAuth.Messaging { /// Gets or sets the UTC creation date of this token. /// </summary> /// <value>The UTC creation date.</value> - [MessagePart("timestamp", IsRequired = true, Encoder = typeof(TimestampEncoder))] + [MessagePart("ts", IsRequired = true, Encoder = typeof(TimestampEncoder))] internal DateTime UtcCreationDate { get; set; } /// <summary> /// Gets or sets the signature. /// </summary> /// <value>The signature.</value> - [MessagePart("sig")] internal byte[] Signature { get; set; } /// <summary> @@ -103,9 +103,10 @@ namespace DotNetOpenAuth.Messaging { /// <remarks> /// This ensures that one token cannot be misused as another kind of token. /// </remarks> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Accessed by reflection")] [MessagePart("t", IsRequired = true, AllowEmpty = false)] - private string BagType { - get { return this.GetType().Name; } + private Type BagType { + get { return this.GetType(); } } #region IMessage Methods diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs index 1fc5432..9cb63e6 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/UriStyleMessageFormatter.cs +++ b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs @@ -1,13 +1,14 @@ //----------------------------------------------------------------------- -// <copyright file="UriStyleMessageFormatter.cs" company="Andrew Arnott"> +// <copyright file="DataBagFormatterBase.cs" company="Andrew Arnott"> // Copyright (c) Andrew Arnott. All rights reserved. // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.OAuth2.ChannelElements { +namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics.Contracts; + using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -20,26 +21,31 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// A serializer for <see cref="DataBag"/>-derived types /// </summary> /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> - internal class UriStyleMessageFormatter<T> : IDataBagFormatter<T> where T : DataBag, new() { + internal abstract class DataBagFormatterBase<T> : IDataBagFormatter<T> where T : DataBag, new() { + /// <summary> + /// The message description cache to use for data bag types. + /// </summary> + protected static readonly MessageDescriptionCollection MessageDescriptions = new MessageDescriptionCollection(); + /// <summary> /// The length of the nonce to include in tokens that can be decoded once only. /// </summary> private const int NonceLength = 6; /// <summary> - /// The message description cache to use for data bag types. + /// The minimum allowable lifetime for the key used to encrypt/decrypt or sign this databag. /// </summary> - private static readonly MessageDescriptionCollection MessageDescriptions = new MessageDescriptionCollection(); + private readonly TimeSpan minimumAge = TimeSpan.FromDays(1); /// <summary> - /// The symmetric secret used for signing/encryption of verification codes and refresh tokens. + /// The symmetric key store with the secret used for signing/encryption of verification codes and refresh tokens. /// </summary> - private readonly byte[] symmetricSecret; + private readonly ICryptoKeyStore cryptoKeyStore; /// <summary> - /// The hashing algorithm to use while signing when using a symmetric secret. + /// The bucket for symmetric keys. /// </summary> - private readonly HashAlgorithm symmetricHasher; + private readonly string cryptoKeyBucket; /// <summary> /// The crypto to use for signing access tokens. @@ -52,11 +58,6 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { private readonly RSACryptoServiceProvider asymmetricEncrypting; /// <summary> - /// The hashing algorithm to use for asymmetric signatures. - /// </summary> - private readonly HashAlgorithm hasherForAsymmetricSigning; - - /// <summary> /// A value indicating whether the data in this instance will be protected against tampering. /// </summary> private readonly bool signed; @@ -82,61 +83,63 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { private readonly bool compressed; /// <summary> - /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. - /// </summary> - /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> - /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> - /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>.</param> - /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - internal UriStyleMessageFormatter(bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) { - Contract.Requires<ArgumentException>(signed || decodeOnceOnly == null, "A signature must be applied if this data is meant to be decoded only once."); - Contract.Requires<ArgumentException>(maximumAge.HasValue || decodeOnceOnly == null, "A maximum age must be given if a message can only be decoded once."); - - this.signed = signed; - this.maximumAge = maximumAge; - this.decodeOnceOnly = decodeOnceOnly; - this.encrypted = encrypted; - this.compressed = compressed; - } - - /// <summary> - /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. + /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. /// </summary> /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - internal UriStyleMessageFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + protected DataBagFormatterBase(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) : this(signingKey != null, encryptingKey != null, compressed, maximumAge, decodeOnceOnly) { this.asymmetricSigning = signingKey; this.asymmetricEncrypting = encryptingKey; - this.hasherForAsymmetricSigning = new SHA1CryptoServiceProvider(); } /// <summary> - /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. + /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. /// </summary> - /// <param name="symmetricSecret">The symmetric secret to use for signing and encrypting.</param> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> - /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="minimumAge">The required minimum lifespan within which this token must be decodable and verifiable; useful only when <paramref name="signed"/> and/or <paramref name="encrypted"/> is true.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> - internal UriStyleMessageFormatter(byte[] symmetricSecret = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + protected DataBagFormatterBase(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) : this(signed, encrypted, compressed, maximumAge, decodeOnceOnly) { - Contract.Requires<ArgumentException>(symmetricSecret != null || (!signed && !encrypted), "A secret is required when signing or encrypting is required."); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket) || cryptoKeyStore == null); + Contract.Requires<ArgumentException>(cryptoKeyStore != null || (!signed && !encrypted)); - if (symmetricSecret != null) { - this.symmetricHasher = new HMACSHA256(symmetricSecret); + this.cryptoKeyStore = cryptoKeyStore; + this.cryptoKeyBucket = bucket; + if (minimumAge.HasValue) { + this.minimumAge = minimumAge.Value; } + } + + /// <summary> + /// Initializes a new instance of the <see cref="DataBagFormatterBase<T>"/> class. + /// </summary> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + private DataBagFormatterBase(bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) { + Contract.Requires<ArgumentException>(signed || decodeOnceOnly == null); + Contract.Requires<ArgumentException>(maximumAge.HasValue || decodeOnceOnly == null); - this.symmetricSecret = symmetricSecret; + this.signed = signed; + this.maximumAge = maximumAge; + this.decodeOnceOnly = decodeOnceOnly; + this.encrypted = encrypted; + this.compressed = compressed; } /// <summary> - /// Serializes the specified message. + /// Serializes the specified message, including compression, encryption, signing, and nonce handling where applicable. /// </summary> /// <param name="message">The message to serialize. Must not be null.</param> /// <returns>A non-null, non-empty value.</returns> @@ -147,55 +150,80 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { message.Nonce = MessagingUtilities.GetNonCryptoRandomData(NonceLength); } - if (this.signed) { - message.Signature = this.CalculateSignature(message); - } - - var fields = MessageSerializer.Get(message.GetType()).Serialize(MessageDescriptions.GetAccessor(message)); - string value = MessagingUtilities.CreateQueryString(fields); - - byte[] encoded = Encoding.UTF8.GetBytes(value); + byte[] encoded = this.SerializeCore(message); if (this.compressed) { encoded = MessagingUtilities.Compress(encoded); } + string symmetricSecretHandle = null; if (this.encrypted) { - encoded = this.Encrypt(encoded); + encoded = this.Encrypt(encoded, out symmetricSecretHandle); + } + + if (this.signed) { + message.Signature = this.CalculateSignature(encoded, symmetricSecretHandle); } - return Convert.ToBase64String(encoded); + int capacity = this.signed ? 4 + message.Signature.Length + 4 + encoded.Length : encoded.Length; + using (var finalStream = new MemoryStream(capacity)) { + var writer = new BinaryWriter(finalStream); + if (this.signed) { + writer.WriteBuffer(message.Signature); + } + + writer.WriteBuffer(encoded); + writer.Flush(); + + string payload = MessagingUtilities.ConvertToBase64WebSafeString(finalStream.ToArray()); + string result = payload; + if (symmetricSecretHandle != null && (this.signed || this.encrypted)) { + result = MessagingUtilities.CombineKeyHandleAndPayload(symmetricSecretHandle, payload); + } + + return result; + } } /// <summary> - /// Deserializes a <see cref="DataBag"/>. + /// Deserializes a <see cref="DataBag"/>, including decompression, decryption, signature and nonce validation where applicable. /// </summary> /// <param name="containingMessage">The message that contains the <see cref="DataBag"/> serialized value. Must not be nulll.</param> /// <param name="value">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> /// <returns>The deserialized value. Never null.</returns> public T Deserialize(IProtocolMessage containingMessage, string value) { + string symmetricSecretHandle = null; + if (this.encrypted && this.cryptoKeyStore != null) { + string valueWithoutHandle; + MessagingUtilities.ExtractKeyHandleAndPayload(containingMessage, "<TODO>", value, out symmetricSecretHandle, out valueWithoutHandle); + value = valueWithoutHandle; + } + var message = new T { ContainingMessage = containingMessage }; - byte[] data = Convert.FromBase64String(value); + byte[] data = MessagingUtilities.FromBase64WebSafeString(value); + + byte[] signature = null; + if (this.signed) { + using (var dataStream = new MemoryStream(data)) { + var dataReader = new BinaryReader(dataStream); + signature = dataReader.ReadBuffer(); + data = dataReader.ReadBuffer(); + } + + // Verify that the verification code was issued by message authorization server. + ErrorUtilities.VerifyProtocol(this.IsSignatureValid(data, signature, symmetricSecretHandle), MessagingStrings.SignatureInvalid); + } if (this.encrypted) { - data = this.Decrypt(data); + data = this.Decrypt(data, symmetricSecretHandle); } if (this.compressed) { data = MessagingUtilities.Decompress(data); } - value = Encoding.UTF8.GetString(data); - - // Deserialize into message newly created instance. - var serializer = MessageSerializer.Get(message.GetType()); - var fields = MessageDescriptions.GetAccessor(message); - serializer.Deserialize(HttpUtility.ParseQueryString(value).ToDictionary(), fields); - - if (this.signed) { - // Verify that the verification code was issued by message authorization server. - ErrorUtilities.VerifyProtocol(this.IsSignatureValid(message), Protocol.bad_verification_code); - } + this.DeserializeCore(message, data); + message.Signature = signature; // TODO: we don't really need this any more, do we? if (this.maximumAge.HasValue) { // Has message verification code expired? @@ -221,72 +249,85 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } /// <summary> + /// Serializes the <see cref="DataBag"/> instance to a buffer. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The buffer containing the serialized data.</returns> + protected abstract byte[] SerializeCore(T message); + + /// <summary> + /// Deserializes the <see cref="DataBag"/> instance from a buffer. + /// </summary> + /// <param name="message">The message instance to initialize with data from the buffer.</param> + /// <param name="data">The data buffer.</param> + protected abstract void DeserializeCore(T message, byte[] data); + + /// <summary> /// Determines whether the signature on this instance is valid. /// </summary> - /// <param name="message">The message whose signature is to be checked.</param> + /// <param name="signedData">The signed data.</param> + /// <param name="signature">The signature.</param> + /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> /// <returns> - /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. + /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. /// </returns> - private bool IsSignatureValid(DataBag message) { - Contract.Requires<ArgumentNullException>(message != null, "message"); + private bool IsSignatureValid(byte[] signedData, byte[] signature, string symmetricSecretHandle) { + Contract.Requires<ArgumentNullException>(signedData != null); + Contract.Requires<ArgumentNullException>(signature != null); if (this.asymmetricSigning != null) { - byte[] bytesToSign = this.GetBytesToSign(message); - return this.asymmetricSigning.VerifyData(bytesToSign, this.hasherForAsymmetricSigning, message.Signature); + using (var hasher = new SHA1CryptoServiceProvider()) { + return this.asymmetricSigning.VerifyData(signedData, hasher, signature); + } } else { - return MessagingUtilities.AreEquivalentConstantTime(message.Signature, this.CalculateSignature(message)); + return MessagingUtilities.AreEquivalentConstantTime(signature, this.CalculateSignature(signedData, symmetricSecretHandle)); } } /// <summary> /// Calculates the signature for the data in this verification code. /// </summary> - /// <param name="message">The message whose signature is to be calculated.</param> - /// <returns>The calculated signature.</returns> - private byte[] CalculateSignature(DataBag message) { - Contract.Requires<ArgumentNullException>(message != null, "message"); - Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.symmetricHasher != null); + /// <param name="bytesToSign">The bytes to sign.</param> + /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// The calculated signature. + /// </returns> + private byte[] CalculateSignature(byte[] bytesToSign, string symmetricSecretHandle) { + Contract.Requires<ArgumentNullException>(bytesToSign != null); + Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.cryptoKeyStore != null); Contract.Ensures(Contract.Result<byte[]>() != null); - byte[] bytesToSign = this.GetBytesToSign(message); if (this.asymmetricSigning != null) { - return this.asymmetricSigning.SignData(bytesToSign, this.hasherForAsymmetricSigning); + using (var hasher = new SHA1CryptoServiceProvider()) { + return this.asymmetricSigning.SignData(bytesToSign, hasher); + } } else { - return this.symmetricHasher.ComputeHash(bytesToSign); + var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); + ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key."); + using (var symmetricHasher = new HMACSHA256(key.Key)) { + return symmetricHasher.ComputeHash(bytesToSign); + } } } /// <summary> - /// Gets the bytes to sign. - /// </summary> - /// <param name="message">The message to be encoded as normalized bytes for signing.</param> - /// <returns>A buffer of the bytes to sign.</returns> - private byte[] GetBytesToSign(DataBag message) { - Contract.Requires<ArgumentNullException>(message != null, "message"); - - // Sign the data, being sure to avoid any impact of the signature field itself. - var fields = MessageDescriptions.GetAccessor(message); - var fieldsCopy = fields.ToDictionary(); - fieldsCopy.Remove("sig"); - - var sortedData = new SortedDictionary<string, string>(fieldsCopy, StringComparer.OrdinalIgnoreCase); - string value = MessagingUtilities.CreateQueryString(sortedData); - byte[] bytesToSign = Encoding.UTF8.GetBytes(value); - return bytesToSign; - } - - /// <summary> /// Encrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. /// </summary> /// <param name="value">The value.</param> - /// <returns>The encrypted value.</returns> - private byte[] Encrypt(byte[] value) { - Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.symmetricSecret != null); + /// <param name="symmetricSecretHandle">Receives the symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// The encrypted value. + /// </returns> + private byte[] Encrypt(byte[] value, out string symmetricSecretHandle) { + Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.cryptoKeyStore != null); if (this.asymmetricEncrypting != null) { + symmetricSecretHandle = null; return this.asymmetricEncrypting.EncryptWithRandomSymmetricKey(value); } else { - return MessagingUtilities.Encrypt(value, this.symmetricSecret); + var cryptoKey = this.cryptoKeyStore.GetCurrentKey(this.cryptoKeyBucket, this.minimumAge); + symmetricSecretHandle = cryptoKey.Key; + return MessagingUtilities.Encrypt(value, cryptoKey.Value.Key); } } @@ -294,14 +335,19 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// Decrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate. /// </summary> /// <param name="value">The value.</param> - /// <returns>The decrypted value.</returns> - private byte[] Decrypt(byte[] value) { - Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.symmetricSecret != null); + /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param> + /// <returns> + /// The decrypted value. + /// </returns> + private byte[] Decrypt(byte[] value, string symmetricSecretHandle) { + Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || symmetricSecretHandle != null); if (this.asymmetricEncrypting != null) { return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value); } else { - return MessagingUtilities.Decrypt(value, this.symmetricSecret); + var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle); + ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key."); + return MessagingUtilities.Decrypt(value, key.Key); } } } diff --git a/src/DotNetOpenAuth/Messaging/EnumerableCache.cs b/src/DotNetOpenAuth/Messaging/EnumerableCache.cs index a606675..be65bca 100644 --- a/src/DotNetOpenAuth/Messaging/EnumerableCache.cs +++ b/src/DotNetOpenAuth/Messaging/EnumerableCache.cs @@ -79,7 +79,7 @@ namespace DotNetOpenAuth.Messaging { private object generatorLock = new object(); /// <summary> - /// Initializes a new instance of the EnumerableCacheExtensions.EnumerableCache class. + /// Initializes a new instance of the EnumerableCache class. /// </summary> /// <param name="generator">The generator.</param> internal EnumerableCache(IEnumerable<T> generator) { @@ -137,7 +137,7 @@ namespace DotNetOpenAuth.Messaging { private int cachePosition = -1; /// <summary> - /// Initializes a new instance of the EnumerableCacheExtensions.EnumerableCache.EnumeratorCache class. + /// Initializes a new instance of the EnumeratorCache class. /// </summary> /// <param name="parent">The parent cached enumerable whose GetEnumerator method is calling this constructor.</param> internal EnumeratorCache(EnumerableCache<T> parent) { diff --git a/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs b/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs index 529c331..c7dcc42 100644 --- a/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs +++ b/src/DotNetOpenAuth/Messaging/IDataBagFormatter.cs @@ -50,7 +50,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="message">The message to serialize. Must not be null.</param> /// <returns>A non-null, non-empty value.</returns> string IDataBagFormatter<T>.Serialize(T message) { - Contract.Requires<ArgumentNullException>(message != null, "message"); + Contract.Requires<ArgumentNullException>(message != null); Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); throw new System.NotImplementedException(); @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="data">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param> /// <returns>The deserialized value. Never null.</returns> T IDataBagFormatter<T>.Deserialize(IProtocolMessage containingMessage, string data) { - Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage"); + Contract.Requires<ArgumentNullException>(containingMessage != null); Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(data)); Contract.Ensures(Contract.Result<T>() != null); diff --git a/src/DotNetOpenAuth/Messaging/IStreamSerializingDataBag.cs b/src/DotNetOpenAuth/Messaging/IStreamSerializingDataBag.cs new file mode 100644 index 0000000..2003f9e --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/IStreamSerializingDataBag.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------- +// <copyright file="IStreamSerializingDataBag.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Diagnostics.Contracts; + using System.IO; + + /// <summary> + /// An interface implemented by <see cref="DataBag"/>-derived types that support binary serialization. + /// </summary> + [ContractClass(typeof(IStreamSerializingDataBaContract))] + internal interface IStreamSerializingDataBag { + /// <summary> + /// Serializes the instance to the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + void Serialize(Stream stream); + + /// <summary> + /// Initializes the fields on this instance from the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + void Deserialize(Stream stream); + } + + /// <summary> + /// Code Contract for the <see cref="IStreamSerializingDataBag"/> interface. + /// </summary> + [ContractClassFor(typeof(IStreamSerializingDataBag))] + internal abstract class IStreamSerializingDataBaContract : IStreamSerializingDataBag { + /// <summary> + /// Serializes the instance to the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + void IStreamSerializingDataBag.Serialize(Stream stream) { + Contract.Requires(stream != null); + Contract.Requires(stream.CanWrite); + throw new NotImplementedException(); + } + + /// <summary> + /// Initializes the fields on this instance from the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + void IStreamSerializingDataBag.Deserialize(Stream stream) { + Contract.Requires(stream != null); + Contract.Requires(stream.CanRead); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs index 0ad3da5..77a206c 100644 --- a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs +++ b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs @@ -59,8 +59,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="messageDictionary">The message dictionary to fill with the JSON-deserialized data.</param> /// <param name="reader">The JSON reader.</param> internal static void DeserializeJsonAsFlatDictionary(IDictionary<string, string> messageDictionary, XmlDictionaryReader reader) { - Contract.Requires<ArgumentNullException>(messageDictionary != null, "messageDictionary"); - Contract.Requires<ArgumentNullException>(reader != null, "reader"); + Contract.Requires<ArgumentNullException>(messageDictionary != null); + Contract.Requires<ArgumentNullException>(reader != null); reader.Read(); // one extra one to skip the root node. while (reader.Read()) { @@ -77,38 +77,6 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message. - /// </summary> - /// <param name="messageDictionary">The message to be serialized.</param> - /// <returns>The dictionary of values to send for the message.</returns> - [Pure] - [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] - internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); - - // Rather than hand back the whole message dictionary (which - // includes keys with blank values), create a new dictionary - // that only has required keys, and optional keys whose - // values are not empty (or default). - var result = new Dictionary<string, string>(); - foreach (var pair in messageDictionary) { - MessagePart partDescription; - if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { - Contract.Assume(partDescription != null); - if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { - result.Add(pair.Key, pair.Value); - } - } else { - // This is extra data. We always write it out. - result.Add(pair.Key, pair.Value); - } - } - - return result; - } - - /// <summary> /// Reads the data from a message instance and writes a XML/JSON encoding of it. /// </summary> /// <param name="messageDictionary">The message to be serialized.</param> @@ -118,9 +86,9 @@ namespace DotNetOpenAuth.Messaging { /// to create the <see cref="XmlDictionaryWriter"/> instance capable of emitting JSON. /// </remarks> [Pure] - internal void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) { + internal static void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) { Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Requires<ArgumentNullException>(writer != null, "writer"); + Contract.Requires<ArgumentNullException>(writer != null); writer.WriteStartElement("root"); writer.WriteAttributeString("type", "object"); @@ -155,6 +123,59 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Reads XML/JSON into a message dictionary. + /// </summary> + /// <param name="messageDictionary">The message to deserialize into.</param> + /// <param name="reader">The XML/JSON to read into the message.</param> + /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> + /// <remarks> + /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader(System.IO.Stream, System.Xml.XmlDictionaryReaderQuotas)"/> + /// to create the <see cref="XmlDictionaryReader"/> instance capable of reading JSON. + /// </remarks> + internal static void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) { + Contract.Requires<ArgumentNullException>(messageDictionary != null); + Contract.Requires<ArgumentNullException>(reader != null); + + DeserializeJsonAsFlatDictionary(messageDictionary, reader); + + // Make sure all the required parts are present and valid. + messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary); + messageDictionary.Message.EnsureValidMessage(); + } + + /// <summary> + /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message. + /// </summary> + /// <param name="messageDictionary">The message to be serialized.</param> + /// <returns>The dictionary of values to send for the message.</returns> + [Pure] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] + internal IDictionary<string, string> Serialize(MessageDictionary messageDictionary) { + Contract.Requires<ArgumentNullException>(messageDictionary != null); + Contract.Ensures(Contract.Result<IDictionary<string, string>>() != null); + + // Rather than hand back the whole message dictionary (which + // includes keys with blank values), create a new dictionary + // that only has required keys, and optional keys whose + // values are not empty (or default). + var result = new Dictionary<string, string>(); + foreach (var pair in messageDictionary) { + MessagePart partDescription; + if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { + Contract.Assume(partDescription != null); + if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { + result.Add(pair.Key, pair.Value); + } + } else { + // This is extra data. We always write it out. + result.Add(pair.Key, pair.Value); + } + } + + return result; + } + + /// <summary> /// Reads name=value pairs into a message. /// </summary> /// <param name="fields">The name=value pairs that were read in from the transport.</param> @@ -186,27 +207,6 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Reads XML/JSON into a message dictionary. - /// </summary> - /// <param name="messageDictionary">The message to deserialize into.</param> - /// <param name="reader">The XML/JSON to read into the message.</param> - /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> - /// <remarks> - /// Use <see cref="System.Runtime.Serialization.Json.JsonReaderWriterFactory.CreateJsonReader(System.IO.Stream, System.Xml.XmlDictionaryReaderQuotas)"/> - /// to create the <see cref="XmlDictionaryReader"/> instance capable of reading JSON. - /// </remarks> - internal void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) { - Contract.Requires<ArgumentNullException>(messageDictionary != null); - Contract.Requires<ArgumentNullException>(reader != null, "reader"); - - DeserializeJsonAsFlatDictionary(messageDictionary, reader); - - // Make sure all the required parts are present and valid. - messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary); - messageDictionary.Message.EnsureValidMessage(); - } - - /// <summary> /// Determines whether the specified type is numeric. /// </summary> /// <param name="type">The type to test.</param> diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index d2e0add..4be9eb5 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -21,11 +21,13 @@ namespace DotNetOpenAuth.Messaging { using System.Text; using System.Web; using System.Web.Mvc; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.Messaging.Reflection; /// <summary> /// A grab-bag of utility methods useful for the channel stack of the protocol. /// </summary> + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Utility class touches lots of surface area")] public static class MessagingUtilities { /// <summary> /// The cryptographically strong random data generator used for creating secrets. @@ -54,12 +56,42 @@ namespace DotNetOpenAuth.Messaging { internal const string Digits = "0123456789"; /// <summary> + /// The set of digits and alphabetic letters (upper and lowercase). + /// </summary> + internal const string AlphaNumeric = UppercaseLetters + LowercaseLetters + Digits; + + /// <summary> + /// All the characters that are allowed for use as a base64 encoding character. + /// </summary> + internal const string Base64Characters = AlphaNumeric + "+" + "/"; + + /// <summary> + /// All the characters that are allowed for use as a base64 encoding character + /// in the "web safe" context. + /// </summary> + internal const string Base64WebSafeCharacters = AlphaNumeric + "-" + "_"; + + /// <summary> /// The set of digits, and alphabetic letters (upper and lowercase) that are clearly /// visually distinguishable. /// </summary> internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ"; /// <summary> + /// The length of private symmetric secret handles. + /// </summary> + /// <remarks> + /// This value needn't be high, as we only expect to have a small handful of unexpired secrets at a time, + /// and handle recycling is permissible. + /// </remarks> + private const int SymmetricSecretHandleLength = 4; + + /// <summary> + /// The default lifetime of a private secret. + /// </summary> + private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Configuration.Messaging.PrivateSecretMaximumAge; + + /// <summary> /// A character array containing just the = character. /// </summary> private static readonly char[] EqualsArray = new char[] { '=' }; @@ -240,8 +272,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="collection">The collection to add to.</param> /// <param name="values">The values to add to the collection.</param> public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> values) { - Contract.Requires<ArgumentNullException>(collection != null, "collection"); - Contract.Requires<ArgumentNullException>(values != null, "values"); + Contract.Requires<ArgumentNullException>(collection != null); + Contract.Requires<ArgumentNullException>(values != null); foreach (var value in values) { collection.Add(value); @@ -249,13 +281,24 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Tests whether two timespans are within reasonable approximation of each other. + /// </summary> + /// <param name="self">One TimeSpan.</param> + /// <param name="other">The other TimeSpan.</param> + /// <param name="marginOfError">The allowable margin of error.</param> + /// <returns><c>true</c> if the two TimeSpans are within <paramref name="marginOfError"/> of each other.</returns> + public static bool Equals(this TimeSpan self, TimeSpan other, TimeSpan marginOfError) { + return TimeSpan.FromMilliseconds(Math.Abs((self - other).TotalMilliseconds)) < marginOfError; + } + + /// <summary> /// Clears any existing elements in a collection and fills the collection with a given set of values. /// </summary> /// <typeparam name="T">The type of value kept in the collection.</typeparam> /// <param name="collection">The collection to modify.</param> /// <param name="values">The new values to fill the collection.</param> internal static void ResetContents<T>(this ICollection<T> collection, IEnumerable<T> values) { - Contract.Requires<ArgumentNullException>(collection != null, "collection"); + Contract.Requires<ArgumentNullException>(collection != null); collection.Clear(); if (values != null) { @@ -270,8 +313,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="messageDescription">The message description whose parts should be removed from the URL.</param> /// <returns>A cleaned URL.</returns> internal static Uri StripMessagePartsFromQueryString(this Uri uri, MessageDescription messageDescription) { - Contract.Requires<ArgumentNullException>(uri != null, "uri"); - Contract.Requires<ArgumentNullException>(messageDescription != null, "messageDescription"); + Contract.Requires<ArgumentNullException>(uri != null); + Contract.Requires<ArgumentNullException>(messageDescription != null); NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query); var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => messageDescription.Mapping.ContainsKey(key)).ToList(); @@ -348,7 +391,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>A value prepared for an HTTP header.</returns> internal static string AssembleAuthorizationHeader(string scheme, IEnumerable<KeyValuePair<string, string>> fields) { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(scheme)); - Contract.Requires<ArgumentNullException>(fields != null, "fields"); + Contract.Requires<ArgumentNullException>(fields != null); var authorization = new StringBuilder(); authorization.Append(scheme); @@ -396,6 +439,40 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Encodes a symmetric key handle and the blob that is encrypted/signed with that key into a single string + /// that can be decoded by <see cref="ExtractKeyHandleAndPayload"/>. + /// </summary> + /// <param name="handle">The cryptographic key handle.</param> + /// <param name="payload">The encrypted/signed blob.</param> + /// <returns>The combined encoded value.</returns> + internal static string CombineKeyHandleAndPayload(string handle, string payload) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(payload)); + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + return handle + "!" + payload; + } + + /// <summary> + /// Extracts the key handle and encrypted blob from a string previously returned from <see cref="CombineKeyHandleAndPayload"/>. + /// </summary> + /// <param name="containingMessage">The containing message.</param> + /// <param name="messagePart">The message part.</param> + /// <param name="keyHandleAndBlob">The value previously returned from <see cref="CombineKeyHandleAndPayload"/>.</param> + /// <param name="handle">The crypto key handle.</param> + /// <param name="dataBlob">The encrypted/signed data.</param> + internal static void ExtractKeyHandleAndPayload(IProtocolMessage containingMessage, string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) { + Contract.Requires<ArgumentNullException>(containingMessage != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(messagePart)); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(keyHandleAndBlob)); + + int privateHandleIndex = keyHandleAndBlob.IndexOf('!'); + ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart, keyHandleAndBlob); + handle = keyHandleAndBlob.Substring(0, privateHandleIndex); + dataBlob = keyHandleAndBlob.Substring(privateHandleIndex + 1); + } + + /// <summary> /// Gets a buffer of random data (not cryptographically strong). /// </summary> /// <param name="length">The length of the sequence to generate.</param> @@ -455,8 +532,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> /// <returns>A base64 encoded string.</returns> internal static string ComputeHash(this HashAlgorithm algorithm, string value, Encoding encoding = null) { - Contract.Requires<ArgumentNullException>(algorithm != null, "algorithm"); - Contract.Requires<ArgumentNullException>(value != null, "value"); + Contract.Requires<ArgumentNullException>(algorithm != null); + Contract.Requires<ArgumentNullException>(value != null); Contract.Ensures(Contract.Result<string>() != null); encoding = encoding ?? Encoding.UTF8; @@ -474,8 +551,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> /// <returns>A base64 encoded string.</returns> internal static string ComputeHash(this HashAlgorithm algorithm, IDictionary<string, string> data, Encoding encoding = null) { - Contract.Requires<ArgumentNullException>(algorithm != null, "algorithm"); - Contract.Requires<ArgumentNullException>(data != null, "data"); + Contract.Requires<ArgumentNullException>(algorithm != null); + Contract.Requires<ArgumentNullException>(data != null); Contract.Ensures(Contract.Result<string>() != null); // Assemble the dictionary to sign, taking care to remove the signature itself @@ -495,8 +572,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="encoding">The encoding to use when converting the string to a byte array.</param> /// <returns>A base64 encoded string.</returns> internal static string ComputeHash(this HashAlgorithm algorithm, IEnumerable<KeyValuePair<string, string>> sortedData, Encoding encoding = null) { - Contract.Requires<ArgumentNullException>(algorithm != null, "algorithm"); - Contract.Requires<ArgumentNullException>(sortedData != null, "sortedData"); + Contract.Requires<ArgumentNullException>(algorithm != null); + Contract.Requires<ArgumentNullException>(sortedData != null); Contract.Ensures(Contract.Result<string>() != null); return ComputeHash(algorithm, CreateQueryString(sortedData), encoding); @@ -509,19 +586,20 @@ namespace DotNetOpenAuth.Messaging { /// <param name="key">The symmetric secret to use to encrypt the buffer. Allowed values are 128, 192, or 256 bytes in length.</param> /// <returns>The encrypted buffer</returns> internal static byte[] Encrypt(byte[] buffer, byte[] key) { - SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key); - - var ms = new MemoryStream(); - var binaryWriter = new BinaryWriter(ms); - binaryWriter.Write((byte)1); // version of encryption algorithm - binaryWriter.Write(crypto.IV); - binaryWriter.Flush(); - - var cryptoStream = new CryptoStream(ms, crypto.CreateEncryptor(), CryptoStreamMode.Write); - cryptoStream.Write(buffer, 0, buffer.Length); - cryptoStream.FlushFinalBlock(); - - return ms.ToArray(); + using (SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key)) { + using (var ms = new MemoryStream()) { + var binaryWriter = new BinaryWriter(ms); + binaryWriter.Write((byte)1); // version of encryption algorithm + binaryWriter.Write(crypto.IV); + binaryWriter.Flush(); + + var cryptoStream = new CryptoStream(ms, crypto.CreateEncryptor(), CryptoStreamMode.Write); + cryptoStream.Write(buffer, 0, buffer.Length); + cryptoStream.FlushFinalBlock(); + + return ms.ToArray(); + } + } } /// <summary> @@ -530,28 +608,30 @@ namespace DotNetOpenAuth.Messaging { /// <param name="buffer">The buffer to decrypt.</param> /// <param name="key">The symmetric secret to use to decrypt the buffer. Allowed values are 128, 192, and 256.</param> /// <returns>The encrypted buffer</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] internal static byte[] Decrypt(byte[] buffer, byte[] key) { - SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key); - - var ms = new MemoryStream(buffer); - var binaryReader = new BinaryReader(ms); - int algorithmVersion = binaryReader.ReadByte(); - ErrorUtilities.VerifyProtocol(algorithmVersion == 1, MessagingStrings.UnsupportedEncryptionAlgorithm); - crypto.IV = binaryReader.ReadBytes(crypto.IV.Length); - - // Allocate space for the decrypted buffer. We don't know how long it will be yet, - // but it will never be larger than the encrypted buffer. - var decryptedBuffer = new byte[buffer.Length]; - int actualDecryptedLength; + using (SymmetricAlgorithm crypto = CreateSymmetricAlgorithm(key)) { + using (var ms = new MemoryStream(buffer)) { + var binaryReader = new BinaryReader(ms); + int algorithmVersion = binaryReader.ReadByte(); + ErrorUtilities.VerifyProtocol(algorithmVersion == 1, MessagingStrings.UnsupportedEncryptionAlgorithm); + crypto.IV = binaryReader.ReadBytes(crypto.IV.Length); + + // Allocate space for the decrypted buffer. We don't know how long it will be yet, + // but it will never be larger than the encrypted buffer. + var decryptedBuffer = new byte[buffer.Length]; + int actualDecryptedLength; + + using (var cryptoStream = new CryptoStream(ms, crypto.CreateDecryptor(), CryptoStreamMode.Read)) { + actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); + } - using (var cryptoStream = new CryptoStream(ms, crypto.CreateDecryptor(), CryptoStreamMode.Read)) { - actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); + // Create a new buffer with only the decrypted data. + var finalDecryptedBuffer = new byte[actualDecryptedLength]; + Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); + return finalDecryptedBuffer; + } } - - // Create a new buffer with only the decrypted data. - var finalDecryptedBuffer = new byte[actualDecryptedLength]; - Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); - return finalDecryptedBuffer; } /// <summary> @@ -585,30 +665,31 @@ namespace DotNetOpenAuth.Messaging { /// <param name="buffer">The buffer to encrypt.</param> /// <returns>The encrypted data.</returns> internal static byte[] EncryptWithRandomSymmetricKey(this RSACryptoServiceProvider crypto, byte[] buffer) { - Contract.Requires<ArgumentNullException>(crypto != null, "crypto"); - Contract.Requires<ArgumentNullException>(buffer != null, "buffer"); + Contract.Requires<ArgumentNullException>(crypto != null); + Contract.Requires<ArgumentNullException>(buffer != null); - var symmetricCrypto = new RijndaelManaged { - Mode = CipherMode.CBC, - }; + using (var symmetricCrypto = new RijndaelManaged()) { + symmetricCrypto.Mode = CipherMode.CBC; - var encryptedStream = new MemoryStream(); - var encryptedStreamWriter = new BinaryWriter(encryptedStream); + using (var encryptedStream = new MemoryStream()) { + var encryptedStreamWriter = new BinaryWriter(encryptedStream); - byte[] prequel = new byte[symmetricCrypto.Key.Length + symmetricCrypto.IV.Length]; - Array.Copy(symmetricCrypto.Key, prequel, symmetricCrypto.Key.Length); - Array.Copy(symmetricCrypto.IV, 0, prequel, symmetricCrypto.Key.Length, symmetricCrypto.IV.Length); - byte[] encryptedPrequel = crypto.Encrypt(prequel, false); + byte[] prequel = new byte[symmetricCrypto.Key.Length + symmetricCrypto.IV.Length]; + Array.Copy(symmetricCrypto.Key, prequel, symmetricCrypto.Key.Length); + Array.Copy(symmetricCrypto.IV, 0, prequel, symmetricCrypto.Key.Length, symmetricCrypto.IV.Length); + byte[] encryptedPrequel = crypto.Encrypt(prequel, false); - encryptedStreamWriter.Write(encryptedPrequel.Length); - encryptedStreamWriter.Write(encryptedPrequel); - encryptedStreamWriter.Flush(); + encryptedStreamWriter.Write(encryptedPrequel.Length); + encryptedStreamWriter.Write(encryptedPrequel); + encryptedStreamWriter.Flush(); - var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateEncryptor(), CryptoStreamMode.Write); - cryptoStream.Write(buffer, 0, buffer.Length); - cryptoStream.FlushFinalBlock(); + var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateEncryptor(), CryptoStreamMode.Write); + cryptoStream.Write(buffer, 0, buffer.Length); + cryptoStream.FlushFinalBlock(); - return encryptedStream.ToArray(); + return encryptedStream.ToArray(); + } + } } /// <summary> @@ -617,40 +698,81 @@ namespace DotNetOpenAuth.Messaging { /// <param name="crypto">The asymmetric encryption provider to use for decryption.</param> /// <param name="buffer">The buffer to decrypt.</param> /// <returns>The decrypted data.</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] internal static byte[] DecryptWithRandomSymmetricKey(this RSACryptoServiceProvider crypto, byte[] buffer) { - Contract.Requires<ArgumentNullException>(crypto != null, "crypto"); - Contract.Requires<ArgumentNullException>(buffer != null, "buffer"); + Contract.Requires<ArgumentNullException>(crypto != null); + Contract.Requires<ArgumentNullException>(buffer != null); - var encryptedStream = new MemoryStream(buffer); - var encryptedStreamReader = new BinaryReader(encryptedStream); + using (var encryptedStream = new MemoryStream(buffer)) { + var encryptedStreamReader = new BinaryReader(encryptedStream); - byte[] encryptedPrequel = encryptedStreamReader.ReadBytes(encryptedStreamReader.ReadInt32()); - byte[] prequel = crypto.Decrypt(encryptedPrequel, false); + byte[] encryptedPrequel = encryptedStreamReader.ReadBytes(encryptedStreamReader.ReadInt32()); + byte[] prequel = crypto.Decrypt(encryptedPrequel, false); - var symmetricCrypto = new RijndaelManaged { - Mode = CipherMode.CBC, - }; + using (var symmetricCrypto = new RijndaelManaged()) { + symmetricCrypto.Mode = CipherMode.CBC; + + byte[] symmetricKey = new byte[symmetricCrypto.Key.Length]; + byte[] symmetricIV = new byte[symmetricCrypto.IV.Length]; + Array.Copy(prequel, symmetricKey, symmetricKey.Length); + Array.Copy(prequel, symmetricKey.Length, symmetricIV, 0, symmetricIV.Length); + symmetricCrypto.Key = symmetricKey; + symmetricCrypto.IV = symmetricIV; - byte[] symmetricKey = new byte[symmetricCrypto.Key.Length]; - byte[] symmetricIV = new byte[symmetricCrypto.IV.Length]; - Array.Copy(prequel, symmetricKey, symmetricKey.Length); - Array.Copy(prequel, symmetricKey.Length, symmetricIV, 0, symmetricIV.Length); - symmetricCrypto.Key = symmetricKey; - symmetricCrypto.IV = symmetricIV; + // Allocate space for the decrypted buffer. We don't know how long it will be yet, + // but it will never be larger than the encrypted buffer. + var decryptedBuffer = new byte[encryptedStream.Length - encryptedStream.Position]; + int actualDecryptedLength; - // Allocate space for the decrypted buffer. We don't know how long it will be yet, - // but it will never be larger than the encrypted buffer. - var decryptedBuffer = new byte[encryptedStream.Length - encryptedStream.Position]; - int actualDecryptedLength; + using (var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateDecryptor(), CryptoStreamMode.Read)) { + actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); + } - using (var cryptoStream = new CryptoStream(encryptedStream, symmetricCrypto.CreateDecryptor(), CryptoStreamMode.Read)) { - actualDecryptedLength = cryptoStream.Read(decryptedBuffer, 0, decryptedBuffer.Length); + // Create a new buffer with only the decrypted data. + var finalDecryptedBuffer = new byte[actualDecryptedLength]; + Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); + return finalDecryptedBuffer; + } } + } - // Create a new buffer with only the decrypted data. - var finalDecryptedBuffer = new byte[actualDecryptedLength]; - Array.Copy(decryptedBuffer, finalDecryptedBuffer, actualDecryptedLength); - return finalDecryptedBuffer; + /// <summary> + /// Gets a key from a given bucket with the longest remaining life, or creates a new one if necessary. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <param name="bucket">The bucket where the key should be found or stored.</param> + /// <param name="minimumRemainingLife">The minimum remaining life required on the returned key.</param> + /// <param name="keySize">The required size of the key, in bits.</param> + /// <returns> + /// A key-value pair whose key is the secret's handle and whose value is the cryptographic key. + /// </returns> + internal static KeyValuePair<string, CryptoKey> GetCurrentKey(this ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan minimumRemainingLife, int keySize = 256) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); + Contract.Requires<ArgumentException>(keySize % 8 == 0); + + var cryptoKeyPair = cryptoKeyStore.GetKeys(bucket).FirstOrDefault(pair => pair.Value.Key.Length == keySize / 8); + if (cryptoKeyPair.Value == null || cryptoKeyPair.Value.ExpiresUtc < DateTime.UtcNow + minimumRemainingLife) { + // No key exists with enough remaining life for the required purpose. Create a new key. + ErrorUtilities.VerifyHost(minimumRemainingLife <= SymmetricSecretKeyLifespan, "Unable to create a new symmetric key with the required lifespan of {0} because it is beyond the limit of {1}.", minimumRemainingLife, SymmetricSecretKeyLifespan); + byte[] secret = GetCryptoRandomData(keySize / 8); + DateTime expires = DateTime.UtcNow + SymmetricSecretKeyLifespan; + var cryptoKey = new CryptoKey(secret, expires); + + // Store this key so we can find and use it later. + int failedAttempts = 0; + tryAgain: + try { + string handle = GetRandomString(SymmetricSecretHandleLength, Base64WebSafeCharacters); + cryptoKeyPair = new KeyValuePair<string, CryptoKey>(handle, cryptoKey); + cryptoKeyStore.StoreKey(bucket, handle, cryptoKey); + } catch (CryptoKeyCollisionException) { + ErrorUtilities.VerifyInternal(++failedAttempts < 3, "Unable to derive a unique handle to a private symmetric key."); + goto tryAgain; + } + } + + return cryptoKeyPair; } /// <summary> @@ -658,13 +780,18 @@ namespace DotNetOpenAuth.Messaging { /// </summary> /// <param name="buffer">The buffer to compress.</param> /// <returns>The compressed data.</returns> + [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "This Dispose is safe.")] internal static byte[] Compress(byte[] buffer) { - var ms = new MemoryStream(); - using (var compressingStream = new DeflateStream(ms, CompressionMode.Compress, true)) { - compressingStream.Write(buffer, 0, buffer.Length); - } + Contract.Requires<ArgumentNullException>(buffer != null); + Contract.Ensures(Contract.Result<byte[]>() != null); + + using (var ms = new MemoryStream()) { + using (var compressingStream = new DeflateStream(ms, CompressionMode.Compress, true)) { + compressingStream.Write(buffer, 0, buffer.Length); + } - return ms.ToArray(); + return ms.ToArray(); + } } /// <summary> @@ -673,13 +800,66 @@ namespace DotNetOpenAuth.Messaging { /// <param name="buffer">The buffer to decompress.</param> /// <returns>The decompressed data.</returns> internal static byte[] Decompress(byte[] buffer) { - var compressedDataStream = new MemoryStream(buffer); - var decompressedDataStream = new MemoryStream(); - using (var decompressingStream = new DeflateStream(compressedDataStream, CompressionMode.Decompress, true)) { - decompressingStream.CopyTo(decompressedDataStream); + Contract.Requires<ArgumentNullException>(buffer != null); + Contract.Ensures(Contract.Result<byte[]>() != null); + + using (var compressedDataStream = new MemoryStream(buffer)) { + using (var decompressedDataStream = new MemoryStream()) { + using (var decompressingStream = new DeflateStream(compressedDataStream, CompressionMode.Decompress, true)) { + decompressingStream.CopyTo(decompressedDataStream); + } + + return decompressedDataStream.ToArray(); + } + } + } + + /// <summary> + /// Converts to data buffer to a base64-encoded string, using web safe characters and with the padding removed. + /// </summary> + /// <param name="data">The data buffer.</param> + /// <returns>A web-safe base64-encoded string without padding.</returns> + internal static string ConvertToBase64WebSafeString(byte[] data) { + var builder = new StringBuilder(Convert.ToBase64String(data)); + + // Swap out the URL-unsafe characters, and trim the padding characters. + builder.Replace('+', '-').Replace('/', '_'); + while (builder[builder.Length - 1] == '=') { // should happen at most twice. + builder.Length -= 1; + } + + return builder.ToString(); + } + + /// <summary> + /// Decodes a (web-safe) base64-string back to its binary buffer form. + /// </summary> + /// <param name="base64WebSafe">The base64-encoded string. May be web-safe encoded.</param> + /// <returns>A data buffer.</returns> + internal static byte[] FromBase64WebSafeString(string base64WebSafe) { + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(base64WebSafe)); + Contract.Ensures(Contract.Result<byte[]>() != null); + + // Restore the padding characters and original URL-unsafe characters. + int missingPaddingCharacters; + switch (base64WebSafe.Length % 4) { + case 3: + missingPaddingCharacters = 1; + break; + case 2: + missingPaddingCharacters = 2; + break; + case 0: + missingPaddingCharacters = 0; + break; + default: + throw ErrorUtilities.ThrowInternal("No more than two padding characters should be present for base64."); } + var builder = new StringBuilder(base64WebSafe, base64WebSafe.Length + missingPaddingCharacters); + builder.Replace('-', '+').Replace('_', '/'); + builder.Append('=', missingPaddingCharacters); - return decompressedDataStream.ToArray(); + return Convert.FromBase64String(builder.ToString()); } /// <summary> @@ -1235,7 +1415,7 @@ namespace DotNetOpenAuth.Messaging { /// <param name="sequence">The sequence.</param> /// <returns>A dictionary.</returns> internal static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> sequence) { - Contract.Requires<ArgumentNullException>(sequence != null, "sequence"); + Contract.Requires<ArgumentNullException>(sequence != null); return sequence.ToDictionary(pair => pair.Key, pair => pair.Value); } @@ -1343,6 +1523,31 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Writes a buffer, prefixed with its own length. + /// </summary> + /// <param name="writer">The binary writer.</param> + /// <param name="buffer">The buffer.</param> + internal static void WriteBuffer(this BinaryWriter writer, byte[] buffer) { + Contract.Requires<ArgumentNullException>(writer != null); + Contract.Requires<ArgumentNullException>(buffer != null); + writer.Write(buffer.Length); + writer.Write(buffer, 0, buffer.Length); + } + + /// <summary> + /// Reads a buffer that is prefixed with its own length. + /// </summary> + /// <param name="reader">The binary reader positioned at the buffer length.</param> + /// <returns>The read buffer.</returns> + internal static byte[] ReadBuffer(this BinaryReader reader) { + Contract.Requires<ArgumentNullException>(reader != null); + int length = reader.ReadInt32(); + byte[] buffer = new byte[length]; + ErrorUtilities.VerifyProtocol(reader.Read(buffer, 0, length) == length, "Unexpected buffer length."); + return buffer; + } + + /// <summary> /// Constructs a Javascript expression that will create an object /// on the user agent when assigned to a variable. /// </summary> @@ -1450,10 +1655,20 @@ namespace DotNetOpenAuth.Messaging { /// <param name="key">The symmetric key to use for encryption/decryption.</param> /// <returns>A symmetric algorithm.</returns> private static SymmetricAlgorithm CreateSymmetricAlgorithm(byte[] key) { - return new RijndaelManaged { - Mode = CipherMode.CBC, - Key = key, - }; + SymmetricAlgorithm result = null; + try { + result = new RijndaelManaged(); + result.Mode = CipherMode.CBC; + result.Key = key; + return result; + } catch { + IDisposable disposableResult = result; + if (disposableResult != null) { + disposableResult.Dispose(); + } + + throw; + } } /// <summary> @@ -1467,7 +1682,7 @@ namespace DotNetOpenAuth.Messaging { private Comparison<T> comparison; /// <summary> - /// Initializes a new instance of the MessagingUtilities.ComparisonHelper class. + /// Initializes a new instance of the ComparisonHelper class. /// </summary> /// <param name="comparison">The comparison method to use.</param> internal ComparisonHelper(Comparison<T> comparison) { diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs index 2747e31..e332fc4 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescriptionCollection.cs @@ -138,7 +138,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { private readonly Version version; /// <summary> - /// Initializes a new instance of the <see cref="MessageDescriptionCollection.MessageTypeAndVersion"/> struct. + /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct. /// </summary> /// <param name="messageType">Type of the message.</param> /// <param name="messageVersion">The message version.</param> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index bdd2827..e5cbff8 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -100,6 +100,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { Map<bool>(value => value.ToString().ToLowerInvariant(), null, safeBool); Map<CultureInfo>(c => c.Name, null, str => new CultureInfo(str)); Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), null, str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); + Map<Type>(t => t.FullName, null, str => Type.GetType(str)); } /// <summary> @@ -244,11 +245,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { value)); } } else { - if (this.property != null) { - this.property.SetValue(message, this.ToValue(value), null); - } else { - this.field.SetValue(message, this.ToValue(value)); - } + this.SetValueAsObject(message, this.ToValue(value)); } } catch (Exception ex) { throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); @@ -321,8 +318,8 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="toValue">The function to convert a string to the custom type.</param> [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toString", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "toValue", Justification = "Code contracts")] private static void Map<T>(Func<T, string> toString, Func<T, string> toOriginalString, Func<string, T> toValue) { - Contract.Requires<ArgumentNullException>(toString != null, "toString"); - Contract.Requires<ArgumentNullException>(toValue != null, "toValue"); + Contract.Requires<ArgumentNullException>(toString != null); + Contract.Requires<ArgumentNullException>(toValue != null); if (toOriginalString == null) { toOriginalString = toString; @@ -373,6 +370,32 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> + /// Gets the value of the message part, without converting it to/from a string. + /// </summary> + /// <param name="message">The message instance to read from.</param> + /// <returns>The value of the member.</returns> + private object GetValueAsObject(IMessage message) { + if (this.property != null) { + return this.property.GetValue(message, null); + } else { + return this.field.GetValue(message); + } + } + + /// <summary> + /// Sets the value of a message part directly with a given value. + /// </summary> + /// <param name="message">The message instance to read from.</param> + /// <param name="value">The value to set on the this part.</param> + private void SetValueAsObject(IMessage message, object value) { + if (this.property != null) { + this.property.SetValue(message, value, null); + } else { + this.field.SetValue(message, value); + } + } + + /// <summary> /// Converts a string representation of the member's value to the appropriate type. /// </summary> /// <param name="value">The string representation of the member's value.</param> @@ -396,19 +419,6 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> - /// Gets the value of the message part, without converting it to/from a string. - /// </summary> - /// <param name="message">The message instance to read from.</param> - /// <returns>The value of the member.</returns> - private object GetValueAsObject(IMessage message) { - if (this.property != null) { - return this.property.GetValue(message, null); - } else { - return this.field.GetValue(message); - } - } - - /// <summary> /// Validates that the message part and its attribute have agreeable settings. /// </summary> /// <exception cref="ArgumentException"> diff --git a/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs b/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs index 530077a..b28ce74 100644 --- a/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs +++ b/src/DotNetOpenAuth/Messaging/StandardMessageFactory.cs @@ -144,7 +144,7 @@ namespace DotNetOpenAuth.Messaging { var matches = this.requestMessageTypes.Keys .Where(message => message.CheckMessagePartsPassBasicValidation(fields)) - .OrderByDescending(message => this.CountInCommon(message.Mapping.Keys, fields.Keys)) + .OrderByDescending(message => CountInCommon(message.Mapping.Keys, fields.Keys)) .ThenByDescending(message => message.Mapping.Count) .CacheGeneratedResults(); var match = matches.FirstOrDefault(); @@ -181,7 +181,7 @@ namespace DotNetOpenAuth.Messaging { let ctors = this.FindMatchingResponseConstructors(message, request.GetType()) where ctors.Any() orderby GetDerivationDistance(ctors.First().GetParameters()[0].ParameterType, request.GetType()), - this.CountInCommon(message.Mapping.Keys, fields.Keys) descending, + CountInCommon(message.Mapping.Keys, fields.Keys) descending, message.Mapping.Count descending select message).CacheGeneratedResults(); var match = matches.FirstOrDefault(); @@ -247,8 +247,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="derivedType">The concrete class that implements the <paramref name="assignableType"/>.</param> /// <returns>The distance between the two types. 0 if the types are equivalent, 1 if the type immediately derives from or implements the base type, or progressively higher integers.</returns> private static int GetDerivationDistance(Type assignableType, Type derivedType) { - Contract.Requires<ArgumentNullException>(assignableType != null, "assignableType"); - Contract.Requires<ArgumentNullException>(derivedType != null, "derivedType"); + Contract.Requires<ArgumentNullException>(assignableType != null); + Contract.Requires<ArgumentNullException>(derivedType != null); Contract.Requires<ArgumentException>(assignableType.IsAssignableFrom(derivedType)); // If this is the two types are equivalent... @@ -268,6 +268,21 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Counts how many strings are in the intersection of two collections. + /// </summary> + /// <param name="collection1">The first collection.</param> + /// <param name="collection2">The second collection.</param> + /// <param name="comparison">The string comparison method to use.</param> + /// <returns>A non-negative integer no greater than the count of elements in the smallest collection.</returns> + private static int CountInCommon(ICollection<string> collection1, ICollection<string> collection2, StringComparison comparison = StringComparison.Ordinal) { + Contract.Requires<ArgumentNullException>(collection1 != null); + Contract.Requires<ArgumentNullException>(collection2 != null); + Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() <= Math.Min(collection1.Count, collection2.Count)); + + return collection1.Count(value1 => collection2.Any(value2 => string.Equals(value1, value2, comparison))); + } + + /// <summary> /// Finds constructors for response messages that take a given request message type. /// </summary> /// <param name="messageDescription">The message description.</param> @@ -279,20 +294,5 @@ namespace DotNetOpenAuth.Messaging { return this.responseMessageTypes[messageDescription].Where(pair => pair.Key.IsAssignableFrom(requestType)).Select(pair => pair.Value); } - - /// <summary> - /// Counts how many strings are in the intersection of two collections. - /// </summary> - /// <param name="collection1">The first collection.</param> - /// <param name="collection2">The second collection.</param> - /// <param name="comparison">The string comparison method to use.</param> - /// <returns>A non-negative integer no greater than the count of elements in the smallest collection.</returns> - private int CountInCommon(ICollection<string> collection1, ICollection<string> collection2, StringComparison comparison = StringComparison.Ordinal) { - Contract.Requires<ArgumentNullException>(collection1 != null, "collection1"); - Contract.Requires<ArgumentNullException>(collection2 != null, "collection2"); - Contract.Ensures(Contract.Result<int>() >= 0 && Contract.Result<int>() <= Math.Min(collection1.Count, collection2.Count)); - - return collection1.Count(value1 => collection2.Any(value2 => string.Equals(value1, value2, comparison))); - } } } diff --git a/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs b/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs index 147b420..1fc3608 100644 --- a/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs +++ b/src/DotNetOpenAuth/Messaging/StandardMessageFactoryChannel.cs @@ -34,8 +34,8 @@ namespace DotNetOpenAuth.Messaging { /// <param name="bindingElements">The binding elements to apply to the channel.</param> protected StandardMessageFactoryChannel(ICollection<Type> messageTypes, ICollection<Version> versions, params IChannelBindingElement[] bindingElements) : base(new StandardMessageFactory(), bindingElements) { - Contract.Requires<ArgumentNullException>(messageTypes != null, "messageTypes"); - Contract.Requires<ArgumentNullException>(versions != null, "versions"); + Contract.Requires<ArgumentNullException>(messageTypes != null); + Contract.Requires<ArgumentNullException>(versions != null); this.messageTypes = messageTypes; this.versions = versions; @@ -54,7 +54,7 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// Gets or sets the message descriptions. /// </summary> - internal override MessageDescriptionCollection MessageDescriptions { + internal sealed override MessageDescriptionCollection MessageDescriptions { get { return base.MessageDescriptions; } @@ -73,7 +73,7 @@ namespace DotNetOpenAuth.Messaging { /// Gets or sets a tool that can figure out what kind of message is being received /// so it can be deserialized. /// </summary> - protected override IMessageFactory MessageFactory { + protected sealed override IMessageFactory MessageFactory { get { return (StandardMessageFactory)base.MessageFactory; } @@ -93,7 +93,7 @@ namespace DotNetOpenAuth.Messaging { /// <returns>The generated/retrieved message descriptions.</returns> private static IEnumerable<MessageDescription> GetMessageDescriptions(ICollection<Type> messageTypes, ICollection<Version> versions, MessageDescriptionCollection descriptionsCache) { - Contract.Requires<ArgumentNullException>(messageTypes != null, "messageTypes"); + Contract.Requires<ArgumentNullException>(messageTypes != null); Contract.Requires<ArgumentNullException>(descriptionsCache != null); Contract.Ensures(Contract.Result<IEnumerable<MessageDescription>>() != null); diff --git a/src/DotNetOpenAuth/Messaging/TimestampEncoder.cs b/src/DotNetOpenAuth/Messaging/TimestampEncoder.cs index 93df88b..b83a426 100644 --- a/src/DotNetOpenAuth/Messaging/TimestampEncoder.cs +++ b/src/DotNetOpenAuth/Messaging/TimestampEncoder.cs @@ -16,7 +16,7 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// The reference date and time for calculating time stamps. /// </summary> - private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); /// <summary> /// Initializes a new instance of the <see cref="TimestampEncoder"/> class. diff --git a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs new file mode 100644 index 0000000..9bffc3d --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs @@ -0,0 +1,77 @@ +//----------------------------------------------------------------------- +// <copyright file="UriStyleMessageFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Web; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// A serializer for <see cref="DataBag"/>-derived types + /// </summary> + /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> + internal class UriStyleMessageFormatter<T> : DataBagFormatterBase<T> where T : DataBag, new() { + /// <summary> + /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. + /// </summary> + /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param> + /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting the token.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal UriStyleMessageFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param> + /// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param> + /// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param> + /// <param name="minimumAge">The minimum age.</param> + /// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param> + /// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected internal UriStyleMessageFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) { + Contract.Requires<ArgumentException>((cryptoKeyStore != null && !String.IsNullOrEmpty(bucket)) || (!signed && !encrypted)); + } + + /// <summary> + /// Serializes the <see cref="DataBag"/> instance to a buffer. + /// </summary> + /// <param name="message">The message.</param> + /// <returns>The buffer containing the serialized data.</returns> + protected override byte[] SerializeCore(T message) { + var fields = MessageSerializer.Get(message.GetType()).Serialize(MessageDescriptions.GetAccessor(message)); + string value = MessagingUtilities.CreateQueryString(fields); + return Encoding.UTF8.GetBytes(value); + } + + /// <summary> + /// Deserializes the <see cref="DataBag"/> instance from a buffer. + /// </summary> + /// <param name="message">The message instance to initialize with data from the buffer.</param> + /// <param name="data">The data buffer.</param> + protected override void DeserializeCore(T message, byte[] data) { + string value = Encoding.UTF8.GetString(data); + + // Deserialize into message newly created instance. + var serializer = MessageSerializer.Get(message.GetType()); + var fields = MessageDescriptions.GetAccessor(message); + serializer.Deserialize(HttpUtility.ParseQueryString(value).ToDictionary(), fields); + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index 4b5aebe..76b5149 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -42,7 +42,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { securitySettings, new OAuthConsumerMessageFactory()) { Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null, "securitySettings"); + Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Requires<ArgumentNullException>(signingBindingElement != null); Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); } @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { securitySettings, new OAuthServiceProviderMessageFactory(tokenManager)) { Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null, "securitySettings"); + Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Requires<ArgumentNullException>(signingBindingElement != null); Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); } @@ -82,7 +82,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings, IMessageFactory messageTypeProvider) : base(messageTypeProvider, InitializeBindingElements(signingBindingElement, store, tokenManager, securitySettings)) { Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null, "securitySettings"); + Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Requires<ArgumentNullException>(signingBindingElement != null); Contract.Requires<ArgumentException>(signingBindingElement.SignatureCallback == null, OAuthStrings.SigningElementAlreadyAssociatedWithChannel); diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs index 83954bf..82ecb0a 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs @@ -28,10 +28,10 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. /// </summary> - /// <param name="username">The username.</param> + /// <param name="userName">The username.</param> /// <param name="roles">The roles this user belongs to.</param> - public OAuthPrincipal(string username, string[] roles) - : this(new OAuthIdentity(username), roles) { + public OAuthPrincipal(string userName, string[] roles) + : this(new OAuthIdentity(userName), roles) { } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs index bfebd8b..f53aa1b 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -38,7 +38,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contract"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")] internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager, ServiceProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(tokenManager != null); - Contract.Requires<ArgumentNullException>(securitySettings != null, "securitySettings"); + Contract.Requires<ArgumentNullException>(securitySettings != null); this.tokenManager = tokenManager; this.securitySettings = securitySettings; diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs index a48c95a..ea8be94 100644 --- a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs @@ -24,7 +24,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorizationServer">The authorization server.</param> public AuthorizationServer(IAuthorizationServer authorizationServer) { - Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + Contract.Requires<ArgumentNullException>(authorizationServer != null); this.OAuthChannel = new OAuth2AuthorizationServerChannel(authorizationServer); } @@ -70,13 +70,13 @@ namespace DotNetOpenAuth.OAuth2 { /// Approves an authorization request and sends an HTTP response to the user agent to redirect the user back to the Client. /// </summary> /// <param name="authorizationRequest">The authorization request to approve.</param> - /// <param name="username">The username of the account that approved the request (or whose data will be accessed by the client).</param> + /// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param> /// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param> /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> - public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, IEnumerable<string> scopes = null, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); + public void ApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) { + Contract.Requires<ArgumentNullException>(authorizationRequest != null); - var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, username, scopes, callback); + var response = this.PrepareApproveAuthorizationRequest(authorizationRequest, userName, scopes, callback); this.Channel.Send(response); } @@ -87,7 +87,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="authorizationRequest">The authorization request to disapprove.</param> /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> public void RejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); + Contract.Requires<ArgumentNullException>(authorizationRequest != null); var response = this.PrepareRejectAuthorizationRequest(authorizationRequest, callback); this.Channel.Send(response); @@ -117,7 +117,7 @@ namespace DotNetOpenAuth.OAuth2 { /// asymmetric key for signing and encrypting the access token. If this is not true, use the <see cref="ReadAccessTokenRequest"/> method instead. /// </remarks> public bool TryPrepareAccessTokenResponse(HttpRequestInfo httpRequestInfo, out IDirectResponseProtocolMessage response) { - Contract.Requires<ArgumentNullException>(httpRequestInfo != null, "httpRequestInfo"); + Contract.Requires<ArgumentNullException>(httpRequestInfo != null); Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<IDirectResponseProtocolMessage>(out response) != null)); var request = this.ReadAccessTokenRequest(httpRequestInfo); @@ -157,7 +157,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> /// <returns>The authorization response message to send to the Client.</returns> public EndUserAuthorizationFailedResponse PrepareRejectAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); + Contract.Requires<ArgumentNullException>(authorizationRequest != null); Contract.Ensures(Contract.Result<EndUserAuthorizationFailedResponse>() != null); if (callback == null) { @@ -172,13 +172,13 @@ namespace DotNetOpenAuth.OAuth2 { /// Approves an authorization request. /// </summary> /// <param name="authorizationRequest">The authorization request to approve.</param> - /// <param name="username">The username of the account that approved the request (or whose data will be accessed by the client).</param> + /// <param name="userName">The username of the account that approved the request (or whose data will be accessed by the client).</param> /// <param name="scopes">The scope of access the client should be granted. If <c>null</c>, all scopes in the original request will be granted.</param> /// <param name="callback">The Client callback URL to use when formulating the redirect to send the user agent back to the Client.</param> /// <returns>The authorization response message to send to the Client.</returns> - public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string username, IEnumerable<string> scopes = null, Uri callback = null) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(username)); + public EndUserAuthorizationSuccessResponseBase PrepareApproveAuthorizationRequest(EndUserAuthorizationRequest authorizationRequest, string userName, IEnumerable<string> scopes = null, Uri callback = null) { + Contract.Requires<ArgumentNullException>(authorizationRequest != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(userName)); Contract.Ensures(Contract.Result<EndUserAuthorizationSuccessResponseBase>() != null); if (callback == null) { @@ -187,19 +187,19 @@ namespace DotNetOpenAuth.OAuth2 { var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier); EndUserAuthorizationSuccessResponseBase response; - switch (authorizationRequest.ResponseType) { - case EndUserAuthorizationResponseType.AccessToken: + switch (EndUserAuthorizationRequest.ResponseType) { + case EndUserAuthorizationResponseTypes.AccessToken: response = new EndUserAuthorizationSuccessAccessTokenResponse(callback, authorizationRequest); break; - case EndUserAuthorizationResponseType.Both: - case EndUserAuthorizationResponseType.AuthorizationCode: + case EndUserAuthorizationResponseTypes.Both: + case EndUserAuthorizationResponseTypes.AuthorizationCode: response = new EndUserAuthorizationSuccessAuthCodeResponse(callback, authorizationRequest); break; default: throw ErrorUtilities.ThrowInternal("Unexpected response type."); } - response.AuthorizingUsername = username; + response.AuthorizingUsername = userName; // Customize the approved scope if the authorization server has decided to do so. if (scopes != null) { @@ -218,8 +218,8 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="includeRefreshToken">If set to <c>true</c>, the response will include a long-lived refresh token.</param> /// <returns>The response message to send to the client.</returns> public virtual IDirectResponseProtocolMessage PrepareAccessTokenResponse(AccessTokenRequestBase request, RSACryptoServiceProvider accessTokenEncryptingPublicKey, TimeSpan? accessTokenLifetime = null, bool includeRefreshToken = true) { - Contract.Requires<ArgumentNullException>(request != null, "request"); - Contract.Requires<ArgumentNullException>(accessTokenEncryptingPublicKey != null, "accessTokenEncryptingPublicKey"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(accessTokenEncryptingPublicKey != null); var tokenRequest = (ITokenCarryingRequest)request; using (var crypto = this.AuthorizationServerServices.CreateAccessTokenSigningCryptoServiceProvider()) { @@ -233,7 +233,7 @@ namespace DotNetOpenAuth.OAuth2 { response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope); if (includeRefreshToken) { - var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.Secret); + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.CryptoKeyStore); var refreshToken = new RefreshToken(tokenRequest.AuthorizationDescription); response.RefreshToken = refreshTokenFormatter.Serialize(refreshToken); } @@ -249,7 +249,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns>The URL to redirect to. Never <c>null</c>.</returns> /// <exception cref="ProtocolException">Thrown if no callback URL could be determined.</exception> protected Uri GetCallback(EndUserAuthorizationRequest authorizationRequest) { - Contract.Requires<ArgumentNullException>(authorizationRequest != null, "authorizationRequest"); + Contract.Requires<ArgumentNullException>(authorizationRequest != null); Contract.Ensures(Contract.Result<Uri>() != null); var client = this.AuthorizationServerServices.GetClientOrThrow(authorizationRequest.ClientIdentifier); diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs index 2404963..b772c0e 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs @@ -55,8 +55,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { var response = message as ITokenCarryingRequest; if (response != null) { - switch (response.CodeOrTokenType) - { + switch (response.CodeOrTokenType) { case CodeOrTokenType.AuthorizationCode: var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer); var code = (AuthorizationCode)response.AuthorizationDescription; @@ -70,8 +69,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { } var accessTokenResponse = message as AccessTokenSuccessResponse; - if (accessTokenResponse != null) - { + if (accessTokenResponse != null) { var directResponseMessage = (IDirectResponseProtocolMessage)accessTokenResponse; var accessTokenRequest = (AccessTokenRequestBase)directResponseMessage.OriginatingRequest; ErrorUtilities.VerifyProtocol(accessTokenRequest.GrantType != GrantType.None || accessTokenResponse.RefreshToken == null, OAuthStrings.NoGrantNoRefreshToken); @@ -108,7 +106,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { tokenRequest.AuthorizationDescription = verificationCode; break; case CodeOrTokenType.RefreshToken: - var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.Secret); + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore); var refreshToken = refreshTokenFormatter.Deserialize(message, tokenRequest.CodeOrToken); tokenRequest.AuthorizationDescription = refreshToken; break; diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs index 015bf53..932db73 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessToken.cs @@ -28,7 +28,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <param name="authorization">The authorization to be described by the access token.</param> /// <param name="lifetime">The lifetime of the access token.</param> internal AccessToken(IAuthorizationDescription authorization, TimeSpan? lifetime) { - Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentNullException>(authorization != null); this.ClientIdentifier = authorization.ClientIdentifier; this.UtcCreationDate = authorization.UtcIssued; diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs index 76867a9..237b1cd 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs @@ -18,9 +18,9 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// </summary> internal class AuthorizationCode : AuthorizationDataBag { /// <summary> - /// The hash algorithm used on the callback URI. + /// The name of the bucket for symmetric keys used to sign authorization codes. /// </summary> - private readonly HashAlgorithm hasher = new SHA256Managed(); + internal const string AuthorizationCodeKeyBucket = "https://localhost/dnoa/oauth_authorization_code"; /// <summary> /// Initializes a new instance of the <see cref="AuthorizationCode"/> class. @@ -37,10 +37,10 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <param name="username">The name on the account that authorized access.</param> internal AuthorizationCode(string clientIdentifier, Uri callback, IEnumerable<string> scopes, string username) { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier)); - Contract.Requires<ArgumentNullException>(callback != null, "callback"); + Contract.Requires<ArgumentNullException>(callback != null); this.ClientIdentifier = clientIdentifier; - this.CallbackHash = this.CalculateCallbackHash(callback); + this.CallbackHash = CalculateCallbackHash(callback); this.Scope.ResetContents(scopes); this.User = username; } @@ -57,16 +57,17 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <param name="authorizationServer">The authorization server that will be serializing/deserializing this authorization code. Must not be null.</param> /// <returns>A DataBag formatter.</returns> internal static IDataBagFormatter<AuthorizationCode> CreateFormatter(IAuthorizationServer authorizationServer) { - Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + Contract.Requires<ArgumentNullException>(authorizationServer != null); Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null); return new UriStyleMessageFormatter<AuthorizationCode>( - authorizationServer.Secret, - true, - true, - false, - AuthorizationCodeBindingElement.MaximumMessageAge, - authorizationServer.VerificationCodeNonceStore); + authorizationServer.CryptoKeyStore, + AuthorizationCodeKeyBucket, + signed: true, + encrypted: true, + compressed: false, + maximumAge: AuthorizationCodeBindingElement.MaximumMessageAge, + decodeOnceOnly: authorizationServer.VerificationCodeNonceStore); } /// <summary> @@ -79,7 +80,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// </remarks> /// <exception cref="ProtocolException">Thrown when the callback URLs do not match.</exception> internal void VerifyCallback(Uri callback) { - ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalent(this.CallbackHash, this.CalculateCallbackHash(callback)), Protocol.redirect_uri_mismatch); + ErrorUtilities.VerifyProtocol(MessagingUtilities.AreEquivalent(this.CallbackHash, CalculateCallbackHash(callback)), Protocol.redirect_uri_mismatch); } /// <summary> @@ -89,8 +90,10 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <returns> /// A base64 encoding of the hash of the URL. /// </returns> - private byte[] CalculateCallbackHash(Uri callback) { - return this.hasher.ComputeHash(Encoding.UTF8.GetBytes(callback.AbsoluteUri)); + private static byte[] CalculateCallbackHash(Uri callback) { + using (var hasher = new SHA256Managed()) { + return hasher.ComputeHash(Encoding.UTF8.GetBytes(callback.AbsoluteUri)); + } } } } diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs index 760dbb4..33986c7 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/EndUserAuthorizationResponseTypeEncoder.cs @@ -30,14 +30,14 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// The <paramref name="value"/> in string form, ready for message transport. /// </returns> public string Encode(object value) { - var responseType = (EndUserAuthorizationResponseType)value; + var responseType = (EndUserAuthorizationResponseTypes)value; switch (responseType) { - case EndUserAuthorizationResponseType.Both: + case EndUserAuthorizationResponseTypes.Both: return Protocol.ResponseTypes.CodeAndToken; - case EndUserAuthorizationResponseType.AccessToken: + case EndUserAuthorizationResponseTypes.AccessToken: return Protocol.ResponseTypes.Token; - case EndUserAuthorizationResponseType.AuthorizationCode: + case EndUserAuthorizationResponseTypes.AuthorizationCode: return Protocol.ResponseTypes.Code; default: throw ErrorUtilities.ThrowFormat(MessagingStrings.UnexpectedMessagePartValue, Protocol.response_type, value); @@ -55,11 +55,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { public object Decode(string value) { switch (value) { case Protocol.ResponseTypes.CodeAndToken: - return EndUserAuthorizationResponseType.Both; + return EndUserAuthorizationResponseTypes.Both; case Protocol.ResponseTypes.Token: - return EndUserAuthorizationResponseType.AccessToken; + return EndUserAuthorizationResponseTypes.AccessToken; case Protocol.ResponseTypes.Code: - return EndUserAuthorizationResponseType.AuthorizationCode; + return EndUserAuthorizationResponseTypes.AuthorizationCode; default: throw ErrorUtilities.ThrowFormat(MessagingStrings.UnexpectedMessagePartValue, Protocol.response_type, value); } diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs index 5ecf01c..0e648d4 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2AuthorizationServerChannel.cs @@ -22,7 +22,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <param name="authorizationServer">The authorization server.</param> protected internal OAuth2AuthorizationServerChannel(IAuthorizationServer authorizationServer) : base(InitializeBindingElements(authorizationServer)) { - Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + Contract.Requires<ArgumentNullException>(authorizationServer != null); this.AuthorizationServer = authorizationServer; } @@ -94,7 +94,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// An array of binding elements used to initialize the channel. /// </returns> private static IChannelBindingElement[] InitializeBindingElements(IAuthorizationServer authorizationServer) { - Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + Contract.Requires<ArgumentNullException>(authorizationServer != null); var bindingElements = new List<IChannelBindingElement>(); bindingElements.Add(new AuthServerAllFlowsBindingElement()); diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ClientChannel.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ClientChannel.cs index fde2cc7..e4a9afd 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ClientChannel.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/OAuth2ClientChannel.cs @@ -58,8 +58,9 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <exception cref="ProtocolException">Thrown when the response is not valid.</exception> protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) { // The spec says direct responses should be JSON objects, but Facebook uses HttpFormUrlEncoded instead, calling it text/plain + // Others return text/javascript. Again bad. string body = response.GetResponseReader().ReadToEnd(); - if (response.ContentType.MediaType == JsonEncoded) { + if (response.ContentType.MediaType == JsonEncoded || response.ContentType.MediaType == JsonTextEncoded) { return this.DeserializeFromJson(body); } else if (response.ContentType.MediaType == HttpFormUrlEncoded || response.ContentType.MediaType == PlainTextEncoded) { return HttpUtility.ParseQueryString(body).ToDictionary(); diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs index 8feb3fb..9fe54c5 100644 --- a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs +++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { using System; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; /// <summary> /// The refresh token issued to a client by an authorization server that allows the client @@ -15,6 +16,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// </summary> internal class RefreshToken : AuthorizationDataBag { /// <summary> + /// The name of the bucket for symmetric keys used to sign refresh tokens. + /// </summary> + internal const string RefreshTokenKeyBucket = "https://localhost/dnoa/oauth_refresh_token"; + + /// <summary> /// Initializes a new instance of the <see cref="RefreshToken"/> class. /// </summary> public RefreshToken() { @@ -25,7 +31,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// </summary> /// <param name="authorization">The authorization this refresh token should describe.</param> internal RefreshToken(IAuthorizationDescription authorization) { - Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentNullException>(authorization != null); this.ClientIdentifier = authorization.ClientIdentifier; this.UtcCreationDate = authorization.UtcIssued; @@ -36,14 +42,15 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements { /// <summary> /// Creates a formatter capable of serializing/deserializing a refresh token. /// </summary> - /// <param name="symmetricSecret">The symmetric secret used by the authorization server to sign/encrypt refresh tokens. Must not be null.</param> - /// <returns>A DataBag formatter. Never null.</returns> - internal static IDataBagFormatter<RefreshToken> CreateFormatter(byte[] symmetricSecret) - { - Contract.Requires<ArgumentNullException>(symmetricSecret != null, "symmetricSecret"); + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <returns> + /// A DataBag formatter. Never null. + /// </returns> + internal static IDataBagFormatter<RefreshToken> CreateFormatter(ICryptoKeyStore cryptoKeyStore) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); Contract.Ensures(Contract.Result<IDataBagFormatter<RefreshToken>>() != null); - return new UriStyleMessageFormatter<RefreshToken>(symmetricSecret, true, true); + return new UriStyleMessageFormatter<RefreshToken>(cryptoKeyStore, RefreshTokenKeyBucket, signed: true, encrypted: true); } } } diff --git a/src/DotNetOpenAuth/OAuth2/ClientBase.cs b/src/DotNetOpenAuth/OAuth2/ClientBase.cs index 48c9913..045fdf2 100644 --- a/src/DotNetOpenAuth/OAuth2/ClientBase.cs +++ b/src/DotNetOpenAuth/OAuth2/ClientBase.cs @@ -62,7 +62,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="request">The request for protected resources from the service provider.</param> /// <param name="accessToken">The access token previously obtained from the Authorization Server.</param> - public void AuthorizeRequest(HttpWebRequest request, string accessToken) { + public static void AuthorizeRequest(HttpWebRequest request, string accessToken) { Contract.Requires<ArgumentNullException>(request != null); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(accessToken)); @@ -86,7 +86,7 @@ namespace DotNetOpenAuth.OAuth2 { this.RefreshToken(authorization); } - this.AuthorizeRequest(request, authorization.AccessToken); + AuthorizeRequest(request, authorization.AccessToken); } /// <summary> @@ -96,7 +96,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="skipIfUsefulLifeExceeds">If given, the access token will <em>not</em> be refreshed if its remaining lifetime exceeds this value.</param> /// <returns>A value indicating whether the access token was actually renewed; <c>true</c> if it was renewed, or <c>false</c> if it still had useful life remaining.</returns> public bool RefreshToken(IAuthorizationState authorization, TimeSpan? skipIfUsefulLifeExceeds = null) { - Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentNullException>(authorization != null); Contract.Requires<ArgumentException>(!string.IsNullOrEmpty(authorization.RefreshToken)); if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue) { @@ -138,9 +138,9 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorizationState">The authorization state maintained by the client.</param> /// <param name="accessTokenSuccess">The access token containing response message.</param> - internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, AccessTokenSuccessResponse accessTokenSuccess) { - Contract.Requires<ArgumentNullException>(authorizationState != null, "authorizationState"); - Contract.Requires<ArgumentNullException>(accessTokenSuccess != null, "accessTokenSuccess"); + internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, AccessTokenSuccessResponse accessTokenSuccess) { + Contract.Requires<ArgumentNullException>(authorizationState != null); + Contract.Requires<ArgumentNullException>(accessTokenSuccess != null); authorizationState.AccessToken = accessTokenSuccess.AccessToken; authorizationState.RefreshToken = accessTokenSuccess.RefreshToken; @@ -165,9 +165,9 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorizationState">The authorization state maintained by the client.</param> /// <param name="accessTokenSuccess">The access token containing response message.</param> - internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess) { - Contract.Requires<ArgumentNullException>(authorizationState != null, "authorizationState"); - Contract.Requires<ArgumentNullException>(accessTokenSuccess != null, "accessTokenSuccess"); + internal static void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAccessTokenResponse accessTokenSuccess) { + Contract.Requires<ArgumentNullException>(authorizationState != null); + Contract.Requires<ArgumentNullException>(accessTokenSuccess != null); authorizationState.AccessToken = accessTokenSuccess.AccessToken; authorizationState.AccessTokenExpirationUtc = DateTime.UtcNow + accessTokenSuccess.Lifetime; @@ -192,8 +192,8 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="authorizationState">The authorization state to update.</param> /// <param name="authorizationSuccess">The authorization success message obtained from the authorization server.</param> internal void UpdateAuthorizationWithResponse(IAuthorizationState authorizationState, EndUserAuthorizationSuccessAuthCodeResponse authorizationSuccess) { - Contract.Requires<ArgumentNullException>(authorizationState != null, "authorizationState"); - Contract.Requires<ArgumentNullException>(authorizationSuccess != null, "authorizationSuccess"); + Contract.Requires<ArgumentNullException>(authorizationState != null); + Contract.Requires<ArgumentNullException>(authorizationSuccess != null); var accessTokenRequest = new AccessTokenAuthorizationCodeRequest(this.AuthorizationServer) { ClientIdentifier = this.ClientIdentifier, @@ -205,7 +205,7 @@ namespace DotNetOpenAuth.OAuth2 { var accessTokenSuccess = accessTokenResponse as AccessTokenSuccessResponse; var failedAccessTokenResponse = accessTokenResponse as AccessTokenFailedResponse; if (accessTokenSuccess != null) { - this.UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); + UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); } else { authorizationState.Delete(); string error = failedAccessTokenResponse != null ? failedAccessTokenResponse.Error : "(unknown)"; @@ -218,8 +218,8 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="authorization">The authorization to measure.</param> /// <returns>A fractional number no greater than 1. Could be negative if the access token has already expired.</returns> - private double ProportionalLifeRemaining(IAuthorizationState authorization) { - Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + private static double ProportionalLifeRemaining(IAuthorizationState authorization) { + Contract.Requires<ArgumentNullException>(authorization != null); Contract.Requires<ArgumentException>(authorization.AccessTokenIssueDateUtc.HasValue); Contract.Requires<ArgumentException>(authorization.AccessTokenExpirationUtc.HasValue); diff --git a/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs index ed6cd63..74b8ebb 100644 --- a/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth/OAuth2/IAccessTokenAnalyzer.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth2 { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using System.Text; @@ -26,6 +27,8 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="user">The user whose data is accessible with this access token.</param> /// <param name="scope">The scope of access authorized by this access token.</param> /// <returns>A value indicating whether this access token is valid.</returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope); } @@ -51,7 +54,7 @@ namespace DotNetOpenAuth.OAuth2 { /// A value indicating whether this access token is valid. /// </returns> bool IAccessTokenAnalyzer.TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out HashSet<string> scope) { - Contract.Requires<ArgumentNullException>(message != null, "message"); + Contract.Requires<ArgumentNullException>(message != null); Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(accessToken)); Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn<string>(out user) != null)); diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs index 9a62277..67fad8b 100644 --- a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs @@ -20,14 +20,14 @@ namespace DotNetOpenAuth.OAuth2 { [ContractClass(typeof(IAuthorizationServerContract))] public interface IAuthorizationServer { /// <summary> - /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens. + /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. /// </summary> /// <remarks> - /// This secret should be kept strictly confidential in the authorization server(s) - /// and NOT shared with the resource server. Anyone with this secret can mint + /// This store should be kept strictly confidential in the authorization server(s) + /// and NOT shared with the resource server. Anyone with these secrets can mint /// tokens to essentially grant themselves access to anything they want. /// </remarks> - byte[] Secret { get; } + ICryptoKeyStore CryptoKeyStore { get; } /// <summary> /// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once. @@ -92,17 +92,11 @@ namespace DotNetOpenAuth.OAuth2 { } /// <summary> - /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens. + /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens. /// </summary> - /// <value></value> - /// <remarks> - /// This secret should be kept strictly confidential in the authorization server(s) - /// and NOT shared with the resource server. Anyone with this secret can mint - /// tokens to essentially grant themselves access to anything they want. - /// </remarks> - byte[] IAuthorizationServer.Secret { + ICryptoKeyStore IAuthorizationServer.CryptoKeyStore { get { - Contract.Ensures(Contract.Result<byte[]>() != null); + Contract.Ensures(Contract.Result<ICryptoKeyStore>() != null); throw new NotImplementedException(); } } @@ -170,7 +164,7 @@ namespace DotNetOpenAuth.OAuth2 { /// account or piece of hardware in which the tokens were stored. </para> /// </remarks> bool IAuthorizationServer.IsAuthorizationValid(IAuthorizationDescription authorization) { - Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentNullException>(authorization != null); throw new NotImplementedException(); } } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs index 28739a0..3c7202e 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/AccessTokenAuthorizationCodeRequest.cs @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { internal AccessTokenAuthorizationCodeRequest(AuthorizationServerDescription authorizationServer) : this(authorizationServer.TokenEndpoint, authorizationServer.Version) { - Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + Contract.Requires<ArgumentNullException>(authorizationServer != null); } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs index 8708ca2..fc11831 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationFailedResponse.cs @@ -35,7 +35,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <param name="request">The authorization request from the user agent on behalf of the client.</param> internal EndUserAuthorizationFailedResponse(Uri clientCallback, EndUserAuthorizationRequest request) : base(((IProtocolMessage)request).Version, MessageTransport.Indirect, clientCallback) { - Contract.Requires<ArgumentNullException>(request != null, "request"); + Contract.Requires<ArgumentNullException>(request != null); ((IMessageWithClientState)this).ClientState = request.ClientState; } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs index 11d39d1..fa270ee 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationRequest.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; @@ -46,11 +47,11 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <summary> /// Gets the grant type that the client expects of the authorization server. /// </summary> - /// <value>Always <see cref="EndUserAuthorizationResponseType.AuthorizationCode"/>. Other response types are not supported.</value> + /// <value>Always <see cref="EndUserAuthorizationResponseTypes.AuthorizationCode"/>. Other response types are not supported.</value> [MessagePart(Protocol.response_type, IsRequired = true, Encoder = typeof(EndUserAuthorizationResponseTypeEncoder))] - public EndUserAuthorizationResponseType ResponseType + public static EndUserAuthorizationResponseTypes ResponseType { - get { return EndUserAuthorizationResponseType.AuthorizationCode; } + get { return EndUserAuthorizationResponseTypes.AuthorizationCode; } } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationResponseType.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationResponseType.cs index 3afcae5..814f625 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationResponseType.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationResponseType.cs @@ -12,7 +12,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// after the user has granted authorized access. /// </summary> [Flags] - public enum EndUserAuthorizationResponseType { + public enum EndUserAuthorizationResponseTypes { /// <summary> /// An access token should be returned immediately. /// </summary> diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs index 025cf84..46bbc87 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAccessTokenResponse.cs @@ -38,8 +38,8 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <param name="request">The authorization request from the user agent on behalf of the client.</param> internal EndUserAuthorizationSuccessAccessTokenResponse(Uri clientCallback, EndUserAuthorizationRequest request) : base(clientCallback, request) { - Contract.Requires<ArgumentNullException>(clientCallback != null, "clientCallback"); - Contract.Requires<ArgumentNullException>(request != null, "request"); + Contract.Requires<ArgumentNullException>(clientCallback != null); + Contract.Requires<ArgumentNullException>(request != null); ((IMessageWithClientState)this).ClientState = request.ClientState; } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs index d3748a9..6302304 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessAuthCodeResponse.cs @@ -35,8 +35,8 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <param name="request">The authorization request from the user agent on behalf of the client.</param> internal EndUserAuthorizationSuccessAuthCodeResponse(Uri clientCallback, EndUserAuthorizationRequest request) : base(clientCallback, request) { - Contract.Requires<ArgumentNullException>(clientCallback != null, "clientCallback"); - Contract.Requires<ArgumentNullException>(request != null, "request"); + Contract.Requires<ArgumentNullException>(clientCallback != null); + Contract.Requires<ArgumentNullException>(request != null); ((IMessageWithClientState)this).ClientState = request.ClientState; } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs index f89dc9b..9b308c8 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/EndUserAuthorizationSuccessResponseBase.cs @@ -38,8 +38,8 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <param name="request">The authorization request from the user agent on behalf of the client.</param> internal EndUserAuthorizationSuccessResponseBase(Uri clientCallback, EndUserAuthorizationRequest request) : base(request, clientCallback) { - Contract.Requires<ArgumentNullException>(clientCallback != null, "clientCallback"); - Contract.Requires<ArgumentNullException>(request != null, "request"); + Contract.Requires<ArgumentNullException>(clientCallback != null); + Contract.Requires<ArgumentNullException>(request != null); ((IMessageWithClientState)this).ClientState = request.ClientState; this.Scope = new HashSet<string>(OAuthUtilities.ScopeStringComparer); } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs index 3233360..e390d6e 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/MessageBase.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; @@ -87,7 +88,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// Implementations of this interface should ensure that this property never returns null. /// </remarks> Version IMessage.Version { - get { return this.version; } + get { return this.Version; } } /// <summary> @@ -110,7 +111,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// </summary> /// <value><see cref="MessageProtections.None"/></value> MessageProtections IProtocolMessage.RequiredProtection { - get { return MessageProtections.None; } + get { return RequiredProtection; } } /// <summary> @@ -118,7 +119,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// </summary> /// <value></value> MessageTransport IProtocolMessage.Transport { - get { return this.messageTransport; } + get { return this.Transport; } } #endregion @@ -152,12 +153,33 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// Gets the originating request message that caused this response to be formed. /// </summary> IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest { - get { return this.originatingRequest; } + get { return this.OriginatingRequest; } } #endregion /// <summary> + /// Gets the level of protection this message requires. + /// </summary> + protected static MessageProtections RequiredProtection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Gets a value indicating whether this is a direct or indirect message. + /// </summary> + protected MessageTransport Transport { + get { return this.messageTransport; } + } + + /// <summary> + /// Gets the version of the protocol or extension this message is prepared to implement. + /// </summary> + protected Version Version { + get { return this.version; } + } + + /// <summary> /// Gets or sets the preferred method of transport for the message. /// </summary> /// <remarks> @@ -168,6 +190,13 @@ namespace DotNetOpenAuth.OAuth2.Messages { protected HttpDeliveryMethods HttpMethods { get; set; } /// <summary> + /// Gets the originating request message that caused this response to be formed. + /// </summary> + protected IDirectedProtocolMessage OriginatingRequest { + get { return this.originatingRequest; } + } + + /// <summary> /// Gets the URL of the intended receiver of this message. /// </summary> protected Uri Recipient { get; private set; } diff --git a/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs b/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs index 0f12a8c..34da922 100644 --- a/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs +++ b/src/DotNetOpenAuth/OAuth2/Messages/UnauthorizedResponse.cs @@ -23,7 +23,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <param name="version">The protocol version.</param> internal UnauthorizedResponse(ProtocolException exception, Version version = null) : base(version ?? Protocol.Default.Version) { - Contract.Requires<ArgumentNullException>(exception != null, "exception"); + Contract.Requires<ArgumentNullException>(exception != null); this.ErrorMessage = exception.Message; } @@ -43,7 +43,7 @@ namespace DotNetOpenAuth.OAuth2.Messages { /// <param name="exception">The exception.</param> internal UnauthorizedResponse(IDirectedProtocolMessage request, ProtocolException exception) : this(request) { - Contract.Requires<ArgumentNullException>(exception != null, "exception"); + Contract.Requires<ArgumentNullException>(exception != null); this.ErrorMessage = exception.Message; } diff --git a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs index 64a6d0c..74d5791 100644 --- a/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs +++ b/src/DotNetOpenAuth/OAuth2/OAuthUtilities.cs @@ -81,7 +81,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="scopes">The scopes to serialize.</param> /// <returns>A space-delimited list.</returns> public static string JoinScopes(HashSet<string> scopes) { - Contract.Requires<ArgumentNullException>(scopes != null, "scopes"); + Contract.Requires<ArgumentNullException>(scopes != null); return string.Join(" ", scopes.ToArray()); } diff --git a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs index af9dcfd..a5baef0 100644 --- a/src/DotNetOpenAuth/OAuth2/ResourceServer.cs +++ b/src/DotNetOpenAuth/OAuth2/ResourceServer.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OAuth2 { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Linq; using System.Net; @@ -28,7 +29,7 @@ namespace DotNetOpenAuth.OAuth2 { /// </summary> /// <param name="accessTokenAnalyzer">The access token analyzer.</param> public ResourceServer(IAccessTokenAnalyzer accessTokenAnalyzer) { - Contract.Requires<ArgumentNullException>(accessTokenAnalyzer != null, "accessTokenAnalyzer"); + Contract.Requires<ArgumentNullException>(accessTokenAnalyzer != null); this.AccessTokenAnalyzer = accessTokenAnalyzer; this.Channel = new OAuth2ResourceServerChannel(); @@ -49,29 +50,33 @@ namespace DotNetOpenAuth.OAuth2 { /// <summary> /// Discovers what access the client should have considering the access token in the current request. /// </summary> - /// <param name="username">The name on the account the client has access to.</param> + /// <param name="userName">The name on the account the client has access to.</param> /// <param name="scope">The set of operations the client is authorized for.</param> /// <returns>An error to return to the client if access is not authorized; <c>null</c> if access is granted.</returns> - public OutgoingWebResponse VerifyAccess(out string username, out HashSet<string> scope) { - return this.VerifyAccess(this.Channel.GetRequestFromContext(), out username, out scope); + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "0#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + public OutgoingWebResponse VerifyAccess(out string userName, out HashSet<string> scope) { + return this.VerifyAccess(this.Channel.GetRequestFromContext(), out userName, out scope); } /// <summary> /// Discovers what access the client should have considering the access token in the current request. /// </summary> /// <param name="httpRequestInfo">The HTTP request info.</param> - /// <param name="username">The name on the account the client has access to.</param> + /// <param name="userName">The name on the account the client has access to.</param> /// <param name="scope">The set of operations the client is authorized for.</param> /// <returns> /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. /// </returns> - public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string username, out HashSet<string> scope) { - Contract.Requires<ArgumentNullException>(httpRequestInfo != null, "httpRequestInfo"); + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] + public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out string userName, out HashSet<string> scope) { + Contract.Requires<ArgumentNullException>(httpRequestInfo != null); AccessProtectedResourceRequest request = null; try { if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(httpRequestInfo, out request)) { - if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out username, out scope)) { + if (this.AccessTokenAnalyzer.TryValidateAccessToken(request, request.AccessToken, out userName, out scope)) { // No errors to return. return null; } @@ -80,14 +85,14 @@ namespace DotNetOpenAuth.OAuth2 { } else { var response = new UnauthorizedResponse(new ProtocolException("Missing access token")); - username = null; + userName = null; scope = null; return this.Channel.PrepareResponse(response); } } catch (ProtocolException ex) { var response = request != null ? new UnauthorizedResponse(request, ex) : new UnauthorizedResponse(ex); - username = null; + userName = null; scope = null; return this.Channel.PrepareResponse(response); } @@ -101,6 +106,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns> /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. /// </returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] public virtual OutgoingWebResponse VerifyAccess(HttpRequestInfo httpRequestInfo, out IPrincipal principal) { string username; HashSet<string> scope; @@ -118,9 +124,11 @@ namespace DotNetOpenAuth.OAuth2 { /// <returns> /// An error to return to the client if access is not authorized; <c>null</c> if access is granted. /// </returns> + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#", Justification = "Try pattern")] + [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Try pattern")] public virtual OutgoingWebResponse VerifyAccess(HttpRequestMessageProperty request, Uri requestUri, out IPrincipal principal) { - Contract.Requires<ArgumentNullException>(request != null, "request"); - Contract.Requires<ArgumentNullException>(requestUri != null, "requestUri"); + Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(requestUri != null); return this.VerifyAccess(new HttpRequestInfo(request, requestUri), out principal); } diff --git a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs index ae52acf..1f59e52 100644 --- a/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth/OAuth2/StandardAccessTokenAnalyzer.cs @@ -22,8 +22,8 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="authorizationServerPublicSigningKey">The crypto service provider with the authorization server public signing key.</param> /// <param name="resourceServerPrivateEncryptionKey">The crypto service provider with the resource server private encryption key.</param> public StandardAccessTokenAnalyzer(RSACryptoServiceProvider authorizationServerPublicSigningKey, RSACryptoServiceProvider resourceServerPrivateEncryptionKey) { - Contract.Requires<ArgumentNullException>(authorizationServerPublicSigningKey != null, "authorizationServerPublicSigningKey"); - Contract.Requires<ArgumentNullException>(resourceServerPrivateEncryptionKey != null, "resourceServerPrivateEncryptionKey"); + Contract.Requires<ArgumentNullException>(authorizationServerPublicSigningKey != null); + Contract.Requires<ArgumentNullException>(resourceServerPrivateEncryptionKey != null); Contract.Requires<ArgumentException>(!resourceServerPrivateEncryptionKey.PublicOnly); this.AuthorizationServerPublicSigningKey = authorizationServerPublicSigningKey; this.ResourceServerPrivateEncryptionKey = resourceServerPrivateEncryptionKey; diff --git a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs index 129102d..65c5898 100644 --- a/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs +++ b/src/DotNetOpenAuth/OAuth2/UserAgentClient.cs @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="authorizationEndpoint">The authorization endpoint.</param> public UserAgentClient(Uri authorizationEndpoint) : base(new AuthorizationServerDescription { AuthorizationEndpoint = authorizationEndpoint }) { - Contract.Requires<ArgumentNullException>(authorizationEndpoint != null, "authorizationEndpoint"); + Contract.Requires<ArgumentNullException>(authorizationEndpoint != null); } /// <summary> @@ -55,7 +55,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="authorization">The authorization state that is tracking this particular request. Optional.</param> /// <returns>A fully-qualified URL suitable to initiate the authorization flow.</returns> public Uri RequestUserAuthorization(IAuthorizationState authorization) { - Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); + Contract.Requires<ArgumentNullException>(authorization != null); Contract.Requires<InvalidOperationException>(!string.IsNullOrEmpty(this.ClientIdentifier)); if (authorization.Callback == null) { @@ -78,7 +78,7 @@ namespace DotNetOpenAuth.OAuth2 { /// <param name="authorizationState">The authorization.</param> /// <returns>The granted authorization, or <c>null</c> if the incoming HTTP request did not contain an authorization server response or authorization was rejected.</returns> public IAuthorizationState ProcessUserAuthorization(Uri actualRedirectUrl, IAuthorizationState authorizationState = null) { - Contract.Requires<ArgumentNullException>(actualRedirectUrl != null, "actualRedirectUrl"); + Contract.Requires<ArgumentNullException>(actualRedirectUrl != null); if (authorizationState == null) { authorizationState = new AuthorizationState(); @@ -94,7 +94,7 @@ namespace DotNetOpenAuth.OAuth2 { EndUserAuthorizationSuccessAuthCodeResponse authCodeSuccess; EndUserAuthorizationFailedResponse failure; if ((accessTokenSuccess = response as EndUserAuthorizationSuccessAccessTokenResponse) != null) { - this.UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); + UpdateAuthorizationWithResponse(authorizationState, accessTokenSuccess); } else if ((authCodeSuccess = response as EndUserAuthorizationSuccessAuthCodeResponse) != null) { this.UpdateAuthorizationWithResponse(authorizationState, authCodeSuccess); } else if ((failure = response as EndUserAuthorizationFailedResponse) != null) { diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs index 3c7e89f..5b97ad4 100644 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ b/src/DotNetOpenAuth/OpenId/Association.cs @@ -14,6 +14,7 @@ namespace DotNetOpenAuth.OpenId { using System.Text; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> /// Stores a secret used in signing and verifying messages. @@ -51,7 +52,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets a unique handle by which this <see cref="Association"/> may be stored or retrieved. /// </summary> - public string Handle { get; private set; } + public string Handle { get; internal set; } /// <summary> /// Gets the UTC time when this <see cref="Association"/> will expire. @@ -85,6 +86,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets or sets the UTC time that this <see cref="Association"/> was first created. /// </summary> + [MessagePart] internal DateTime Issued { get; set; } /// <summary> @@ -102,6 +104,7 @@ namespace DotNetOpenAuth.OpenId { /// Gets the shared secret key between the consumer and provider. /// </summary> [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It is a buffer.")] + [MessagePart("key")] protected internal byte[] SecretKey { get; private set; } /// <summary> @@ -117,6 +120,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Gets the lifetime the OpenID provider permits this <see cref="Association"/>. /// </summary> + [MessagePart("ttl")] protected TimeSpan TotalLifeLength { get; private set; } /// <summary> @@ -156,7 +160,7 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// The newly dehydrated <see cref="Association"/>, which can be returned /// from a custom association store's - /// <see cref="IAssociationStore<TKey>.GetAssociation(TKey, SecuritySettings)"/> method. + /// <see cref="IRelyingPartyAssociationStore.GetAssociation(Uri, SecuritySettings)"/> method. /// </returns> public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) { Contract.Requires<ArgumentNullException>(!String.IsNullOrEmpty(handle)); @@ -219,7 +223,7 @@ namespace DotNetOpenAuth.OpenId { if (a.Handle != this.Handle || a.Issued != this.Issued || - a.TotalLifeLength != this.TotalLifeLength) { + !MessagingUtilities.Equals(a.TotalLifeLength, this.TotalLifeLength, TimeSpan.FromSeconds(1))) { return false; } diff --git a/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs b/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs deleted file mode 100644 index 7a20fd5..0000000 --- a/src/DotNetOpenAuth/OpenId/AssociationMemoryStore.cs +++ /dev/null @@ -1,133 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="AssociationMemoryStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System.Collections.Generic; - using System.Linq; - - /// <summary> - /// Manages a set of associations in memory only (no database). - /// </summary> - /// <typeparam name="TKey">The type of the key.</typeparam> - /// <remarks> - /// This class should be used for low-to-medium traffic relying party sites that can afford to lose associations - /// if the app pool was ever restarted. High traffic relying parties and providers should write their own - /// implementation of <see cref="IAssociationStore<TKey>"/> that works against their own database schema - /// to allow for persistance and recall of associations across servers in a web farm and server restarts. - /// </remarks> - internal class AssociationMemoryStore<TKey> : IAssociationStore<TKey> { - /// <summary> - /// How many association store requests should occur between each spring cleaning. - /// </summary> - private const int PeriodicCleaningFrequency = 10; - - /// <summary> - /// For Relying Parties, this maps OP Endpoints to a set of associations with that endpoint. - /// For Providers, this keeps smart and dumb associations in two distinct pools. - /// </summary> - private Dictionary<TKey, Associations> serverAssocsTable = new Dictionary<TKey, Associations>(); - - /// <summary> - /// A counter to track how close we are to an expired association cleaning run. - /// </summary> - private int periodicCleaning; - - /// <summary> - /// Stores a given association for later recall. - /// </summary> - /// <param name="distinguishingFactor">The distinguishing factor, either an OP Endpoint or smart/dumb mode.</param> - /// <param name="association">The association to store.</param> - public void StoreAssociation(TKey distinguishingFactor, Association association) { - lock (this) { - if (!this.serverAssocsTable.ContainsKey(distinguishingFactor)) { - this.serverAssocsTable.Add(distinguishingFactor, new Associations()); - } - Associations server_assocs = this.serverAssocsTable[distinguishingFactor]; - - server_assocs.Set(association); - - unchecked { - this.periodicCleaning++; - } - if (this.periodicCleaning % PeriodicCleaningFrequency == 0) { - this.ClearExpiredAssociations(); - } - } - } - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securitySettings">The security settings.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - public Association GetAssociation(TKey distinguishingFactor, SecuritySettings securitySettings) { - lock (this) { - return this.GetServerAssociations(distinguishingFactor).Best.FirstOrDefault(assoc => securitySettings.IsAssociationInPermittedRange(assoc)); - } - } - - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - /// </returns> - public Association GetAssociation(TKey distinguishingFactor, string handle) { - lock (this) { - return this.GetServerAssociations(distinguishingFactor).Get(handle); - } - } - - /// <summary> - /// Removes a specified handle that may exist in the store. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// True if the association existed in this store previous to this call. - /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - public bool RemoveAssociation(TKey distinguishingFactor, string handle) { - lock (this) { - return this.GetServerAssociations(distinguishingFactor).Remove(handle); - } - } - - /// <summary> - /// Gets the server associations for a given OP Endpoint or dumb/smart mode. - /// </summary> - /// <param name="distinguishingFactor">The distinguishing factor, either an OP Endpoint (for relying parties) or smart/dumb (for providers).</param> - /// <returns>The collection of associations that fit the <paramref name="distinguishingFactor"/>.</returns> - internal Associations GetServerAssociations(TKey distinguishingFactor) { - lock (this) { - if (!this.serverAssocsTable.ContainsKey(distinguishingFactor)) { - this.serverAssocsTable.Add(distinguishingFactor, new Associations()); - } - - return this.serverAssocsTable[distinguishingFactor]; - } - } - - /// <summary> - /// Clears all expired associations from the store. - /// </summary> - private void ClearExpiredAssociations() { - lock (this) { - foreach (Associations assocs in this.serverAssocsTable.Values) { - assocs.ClearExpired(); - } - } - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs index 97a55c1..66ac276 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs +++ b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs @@ -35,11 +35,9 @@ namespace DotNetOpenAuth.OpenId.Behaviors { private static readonly TimeSpan MaximumAssociationLifetime = TimeSpan.FromSeconds(86400); /// <summary> - /// Initializes static members of the <see cref="GsaIcamProfile"/> class. + /// Backing field for the <see cref="DisableSslRequirement"/> static property. /// </summary> - static GsaIcamProfile() { - DisableSslRequirement = DotNetOpenAuthSection.Configuration.Messaging.RelaxSslRequirements; - } + private static bool disableSslRequirement = DotNetOpenAuthSection.Configuration.Messaging.RelaxSslRequirements; /// <summary> /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class. @@ -65,7 +63,10 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// <summary> /// Gets or sets a value indicating whether to ignore the SSL requirement (for testing purposes only). /// </summary> - public static bool DisableSslRequirement { get; set; } + public static bool DisableSslRequirement { // not an auto-property because it has a default value, and FxCop doesn't want us using static constructors. + get { return disableSslRequirement; } + set { disableSslRequirement = value; } + } #region IRelyingPartyBehavior Members diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs index bc613ed..d9a0e50 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/OpenIdChannel.cs @@ -46,11 +46,11 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the <see cref="OpenIdChannel"/> class /// for use by a Relying Party. /// </summary> - /// <param name="associationStore">The association store to use.</param> + /// <param name="cryptoKeyStore">The association store to use.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings to apply.</param> - internal OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) - : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings, false) { + internal OpenIdChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings) + : this(cryptoKeyStore, nonceStore, new OpenIdMessageFactory(), securitySettings, false) { Contract.Requires<ArgumentNullException>(securitySettings != null); } @@ -58,11 +58,12 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the <see cref="OpenIdChannel"/> class /// for use by a Provider. /// </summary> - /// <param name="associationStore">The association store to use.</param> + /// <param name="cryptoKeyStore">The OpenID Provider's association store or handle encoder.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings.</param> - internal OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) - : this(associationStore, nonceStore, new OpenIdMessageFactory(), securitySettings) { + internal OpenIdChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) + : this(cryptoKeyStore, nonceStore, new OpenIdMessageFactory(), securitySettings) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); Contract.Requires<ArgumentNullException>(securitySettings != null); } @@ -70,13 +71,13 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the <see cref="OpenIdChannel"/> class /// for use by a Relying Party. /// </summary> - /// <param name="associationStore">The association store to use.</param> + /// <param name="cryptoKeyStore">The association store to use.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> /// <param name="securitySettings">The security settings to apply.</param> /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> - private OpenIdChannel(IAssociationStore<Uri> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : - this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings, nonVerifying)) { + private OpenIdChannel(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, RelyingPartySecuritySettings securitySettings, bool nonVerifying) : + this(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings, nonVerifying)) { Contract.Requires<ArgumentNullException>(messageTypeProvider != null); Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings); @@ -86,12 +87,13 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the <see cref="OpenIdChannel"/> class /// for use by a Provider. /// </summary> - /// <param name="associationStore">The association store to use.</param> + /// <param name="cryptoKeyStore">The association store to use.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="messageTypeProvider">An object that knows how to distinguish the various OpenID message types for deserialization purposes.</param> /// <param name="securitySettings">The security settings.</param> - private OpenIdChannel(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) : - this(messageTypeProvider, InitializeBindingElements(associationStore, nonceStore, securitySettings, false)) { + private OpenIdChannel(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, IMessageFactory messageTypeProvider, ProviderSecuritySettings securitySettings) : + this(messageTypeProvider, InitializeBindingElements(cryptoKeyStore, nonceStore, securitySettings)) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); Contract.Requires<ArgumentNullException>(messageTypeProvider != null); Contract.Requires<ArgumentNullException>(securitySettings != null); } @@ -302,65 +304,43 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <summary> /// Initializes the binding elements. /// </summary> - /// <typeparam name="T">The distinguishing factor used by the association store.</typeparam> - /// <param name="associationStore">The association store.</param> + /// <param name="cryptoKeyStore">The crypto key store.</param> /// <param name="nonceStore">The nonce store to use.</param> /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param> /// <param name="nonVerifying">A value indicating whether the channel is set up with no functional security binding elements.</param> /// <returns> /// An array of binding elements which may be used to construct the channel. /// </returns> - [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Needed for code contracts.")] - private static IChannelBindingElement[] InitializeBindingElements<T>(IAssociationStore<T> associationStore, INonceStore nonceStore, SecuritySettings securitySettings, bool nonVerifying) { + private static IChannelBindingElement[] InitializeBindingElements(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore, RelyingPartySecuritySettings securitySettings, bool nonVerifying) { Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentException>(!nonVerifying || securitySettings is RelyingPartySecuritySettings); - - var rpSecuritySettings = securitySettings as RelyingPartySecuritySettings; - var opSecuritySettings = securitySettings as ProviderSecuritySettings; - ErrorUtilities.VerifyInternal(rpSecuritySettings != null || opSecuritySettings != null, "Expected an RP or OP security settings instance."); - ErrorUtilities.VerifyInternal(!nonVerifying || rpSecuritySettings != null, "Non-verifying channels can only be constructed for relying parties."); - bool isRelyingPartyRole = rpSecuritySettings != null; - - var rpAssociationStore = associationStore as IAssociationStore<Uri>; - var opAssociationStore = associationStore as IAssociationStore<AssociationRelyingPartyType>; - ErrorUtilities.VerifyInternal(isRelyingPartyRole || opAssociationStore != null, "Providers MUST have an association store."); SigningBindingElement signingElement; - if (isRelyingPartyRole) { - signingElement = nonVerifying ? null : new SigningBindingElement(rpAssociationStore); - } else { - signingElement = new SigningBindingElement(opAssociationStore, opSecuritySettings); - } + signingElement = nonVerifying ? null : new SigningBindingElement(new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore ?? new MemoryCryptoKeyStore())); var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings)); - if (isRelyingPartyRole) { - elements.Add(new RelyingPartySecurityOptions(rpSecuritySettings)); - elements.Add(new BackwardCompatibilityBindingElement()); - ReturnToNonceBindingElement requestNonceElement = null; - - if (associationStore != null) { - if (nonceStore != null) { - // There is no point in having a ReturnToNonceBindingElement without - // a ReturnToSignatureBindingElement because the nonce could be - // artificially changed without it. - requestNonceElement = new ReturnToNonceBindingElement(nonceStore, rpSecuritySettings); - elements.Add(requestNonceElement); - } + elements.Add(new RelyingPartySecurityOptions(securitySettings)); + elements.Add(new BackwardCompatibilityBindingElement()); + ReturnToNonceBindingElement requestNonceElement = null; - // It is important that the return_to signing element comes last - // so that the nonce is included in the signature. - elements.Add(new ReturnToSignatureBindingElement(rpAssociationStore, rpSecuritySettings)); + if (cryptoKeyStore != null) { + if (nonceStore != null) { + // There is no point in having a ReturnToNonceBindingElement without + // a ReturnToSignatureBindingElement because the nonce could be + // artificially changed without it. + requestNonceElement = new ReturnToNonceBindingElement(nonceStore, securitySettings); + elements.Add(requestNonceElement); } - ErrorUtilities.VerifyOperation(!rpSecuritySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore); - } else { - // Providers must always have a nonce store. - ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); + // It is important that the return_to signing element comes last + // so that the nonce is included in the signature. + elements.Add(new ReturnToSignatureBindingElement(cryptoKeyStore)); } + ErrorUtilities.VerifyOperation(!securitySettings.RejectUnsolicitedAssertions || requestNonceElement != null, OpenIdStrings.UnsolicitedAssertionRejectionRequiresNonceStore); + if (nonVerifying) { elements.Add(new SkipSecurityBindingElement()); } else { @@ -374,5 +354,33 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { return elements.ToArray(); } + + /// <summary> + /// Initializes the binding elements. + /// </summary> + /// <param name="cryptoKeyStore">The OpenID Provider's crypto key store.</param> + /// <param name="nonceStore">The nonce store to use.</param> + /// <param name="securitySettings">The security settings to apply. Must be an instance of either <see cref="RelyingPartySecuritySettings"/> or <see cref="ProviderSecuritySettings"/>.</param> + /// <returns> + /// An array of binding elements which may be used to construct the channel. + /// </returns> + private static IChannelBindingElement[] InitializeBindingElements(IProviderAssociationStore cryptoKeyStore, INonceStore nonceStore, ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); + Contract.Requires<ArgumentNullException>(nonceStore != null); + + SigningBindingElement signingElement; + signingElement = new SigningBindingElement(cryptoKeyStore, securitySettings); + + var extensionFactory = OpenIdExtensionFactoryAggregator.LoadFromConfiguration(); + + List<IChannelBindingElement> elements = new List<IChannelBindingElement>(8); + elements.Add(new ExtensionsBindingElement(extensionFactory, securitySettings)); + elements.Add(new StandardReplayProtectionBindingElement(nonceStore, true)); + elements.Add(new StandardExpirationBindingElement()); + elements.Add(signingElement); + + return elements.ToArray(); + } } } diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs index 9721b37..3649543 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToNonceBindingElement.cs @@ -53,6 +53,11 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { internal const string NonceParameter = OpenIdUtilities.CustomParameterPrefix + "request_nonce"; /// <summary> + /// The context within which return_to nonces must be unique -- they all go into the same bucket. + /// </summary> + private const string ReturnToNonceContext = "https://localhost/dnoa/return_to_nonce"; + + /// <summary> /// The length of the generated nonce's random part. /// </summary> private const int NonceByteLength = 128 / 8; // 128-bit nonce @@ -186,7 +191,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { } IReplayProtectedProtocolMessage replayResponse = response; - if (!this.nonceStore.StoreNonce(replayResponse.NonceContext, nonce.RandomPartAsString, nonce.CreationDateUtc)) { + if (!this.nonceStore.StoreNonce(ReturnToNonceContext, nonce.RandomPartAsString, nonce.CreationDateUtc)) { Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", replayResponse.Nonce, replayResponse.UtcCreationDate); throw new ReplayedMessageException(message); } @@ -224,7 +229,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { private byte[] randomPart; /// <summary> - /// Initializes a new instance of the <see cref="ReturnToNonceBindingElement.CustomNonce"/> class. + /// Initializes a new instance of the <see cref="CustomNonce"/> class. /// </summary> /// <param name="creationDate">The creation date of the nonce.</param> /// <param name="randomPart">The random bits that help make the nonce unique.</param> @@ -261,7 +266,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { internal static CustomNonce Deserialize(string value) { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - byte[] nonce = Convert.FromBase64String(value); + byte[] nonce = MessagingUtilities.FromBase64WebSafeString(value); Contract.Assume(nonce != null); DateTime creationDateUtc = new DateTime(BitConverter.ToInt64(nonce, 0), DateTimeKind.Utc); byte[] randomPart = new byte[NonceByteLength]; @@ -278,7 +283,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { byte[] nonce = new byte[timestamp.Length + this.randomPart.Length]; timestamp.CopyTo(nonce, 0); this.randomPart.CopyTo(nonce, timestamp.Length); - string base64Nonce = Convert.ToBase64String(nonce); + string base64Nonce = MessagingUtilities.ConvertToBase64WebSafeString(nonce); return base64Nonce; } } diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs index 438c496..30358e0 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs @@ -11,7 +11,9 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { using System.Diagnostics.Contracts; using System.Security.Cryptography; using System.Web; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId.Messages; using DotNetOpenAuth.OpenId.RelyingParty; @@ -43,19 +45,23 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { private const string ReturnToSignatureHandleParameterName = OpenIdUtilities.CustomParameterPrefix + "return_to_sig_handle"; /// <summary> - /// The hashing algorithm used to generate the private signature on the return_to parameter. + /// The URI to use for private associations at this RP. /// </summary> - private PrivateSecretManager secretManager; + private static readonly Uri SecretUri = new Uri("https://localhost/dnoa/secret"); + + /// <summary> + /// The key store used to generate the private signature on the return_to parameter. + /// </summary> + private ICryptoKeyStore cryptoKeyStore; /// <summary> /// Initializes a new instance of the <see cref="ReturnToSignatureBindingElement"/> class. /// </summary> - /// <param name="secretStore">The secret store from which to retrieve the secret used for signing.</param> - /// <param name="securitySettings">The security settings.</param> - internal ReturnToSignatureBindingElement(IAssociationStore<Uri> secretStore, RelyingPartySecuritySettings securitySettings) { - Contract.Requires<ArgumentNullException>(secretStore != null); + /// <param name="cryptoKeyStore">The crypto key store.</param> + internal ReturnToSignatureBindingElement(ICryptoKeyStore cryptoKeyStore) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); - this.secretManager = new PrivateSecretManager(securitySettings, secretStore); + this.cryptoKeyStore = cryptoKeyStore; } #region IChannelBindingElement Members @@ -96,8 +102,10 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { SignedResponseRequest request = message as SignedResponseRequest; if (request != null && request.ReturnTo != null && request.SignReturnTo) { - request.AddReturnToArguments(ReturnToSignatureHandleParameterName, this.secretManager.CurrentHandle); - request.AddReturnToArguments(ReturnToSignatureParameterName, this.GetReturnToSignature(request.ReturnTo)); + var cryptoKeyPair = this.cryptoKeyStore.GetCurrentKey(SecretUri.AbsoluteUri, DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); + request.AddReturnToArguments(ReturnToSignatureHandleParameterName, cryptoKeyPair.Key); + string signature = Convert.ToBase64String(this.GetReturnToSignature(request.ReturnTo, cryptoKeyPair.Value)); + request.AddReturnToArguments(ReturnToSignatureParameterName, signature); // We return none because we are not signing the entire message (only a part). return MessageProtections.None; @@ -134,10 +142,11 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // Only check the return_to signature if one is present. if (returnToParameters[ReturnToSignatureHandleParameterName] != null) { // Set the safety flag showing whether the return_to url had a valid signature. - string expected = this.GetReturnToSignature(response.ReturnTo); + byte[] expectedBytes = this.GetReturnToSignature(response.ReturnTo); string actual = returnToParameters[ReturnToSignatureParameterName]; actual = OpenIdUtilities.FixDoublyUriDecodedBase64String(actual); - response.ReturnToParametersSignatureValidated = actual == expected; + byte[] actualBytes = Convert.FromBase64String(actual); + response.ReturnToParametersSignatureValidated = MessagingUtilities.AreEquivalentConstantTime(actualBytes, expectedBytes); if (!response.ReturnToParametersSignatureValidated) { Logger.Bindings.WarnFormat("The return_to signature failed verification."); } @@ -155,13 +164,16 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Gets the return to signature. /// </summary> /// <param name="returnTo">The return to.</param> - /// <returns>The generated signature.</returns> + /// <param name="cryptoKey">The crypto key.</param> + /// <returns> + /// The generated signature. + /// </returns> /// <remarks> /// Only the parameters in the return_to URI are signed, rather than the base URI /// itself, in order that OPs that might change the return_to's implicit port :80 part /// or other minor changes do not invalidate the signature. /// </remarks> - private string GetReturnToSignature(Uri returnTo) { + private byte[] GetReturnToSignature(Uri returnTo, CryptoKey cryptoKey = null) { Contract.Requires<ArgumentNullException>(returnTo != null); // Assemble the dictionary to sign, taking care to remove the signature itself @@ -182,12 +194,18 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { byte[] bytesToSign = KeyValueFormEncoding.GetBytes(sortedReturnToParameters); byte[] signature; try { - signature = this.secretManager.Sign(bytesToSign, returnToParameters[ReturnToSignatureHandleParameterName]); + if (cryptoKey == null) { + cryptoKey = this.cryptoKeyStore.GetKey(SecretUri.AbsoluteUri, returnToParameters[ReturnToSignatureHandleParameterName]); + } + + using (var signer = new HMACSHA256(cryptoKey.Key)) { + signature = signer.ComputeHash(bytesToSign); + } } catch (ProtocolException ex) { throw ErrorUtilities.Wrap(ex, OpenIdStrings.MaximumAuthenticationTimeExpired); } - string signatureBase64 = Convert.ToBase64String(signature); - return signatureBase64; + + return signature; } } } diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs index 30310ac..e301a3e 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/SigningBindingElement.cs @@ -28,12 +28,12 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// <summary> /// The association store used by Relying Parties to look up the secrets needed for signing. /// </summary> - private readonly IAssociationStore<Uri> rpAssociations; + private readonly IRelyingPartyAssociationStore rpAssociations; /// <summary> /// The association store used by Providers to look up the secrets needed for signing. /// </summary> - private readonly IAssociationStore<AssociationRelyingPartyType> opAssociations; + private readonly IProviderAssociationStore opAssociations; /// <summary> /// The security settings at the Provider. @@ -45,7 +45,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Initializes a new instance of the SigningBindingElement class for use by a Relying Party. /// </summary> /// <param name="associationStore">The association store used to look up the secrets needed for signing. May be null for dumb Relying Parties.</param> - internal SigningBindingElement(IAssociationStore<Uri> associationStore) { + internal SigningBindingElement(IRelyingPartyAssociationStore associationStore) { this.rpAssociations = associationStore; } @@ -54,7 +54,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> /// <param name="associationStore">The association store used to look up the secrets needed for signing.</param> /// <param name="securitySettings">The security settings.</param> - internal SigningBindingElement(IAssociationStore<AssociationRelyingPartyType> associationStore, ProviderSecuritySettings securitySettings) { + internal SigningBindingElement(IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(associationStore != null); Contract.Requires<ArgumentNullException>(securitySettings != null); @@ -83,7 +83,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// Gets a value indicating whether this binding element is on a Provider channel. /// </summary> private bool IsOnProvider { - get { return this.opAssociations != null; } + get { return this.opSecuritySettings != null; } } #region IChannelBindingElement Methods @@ -232,14 +232,14 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // get included in the list of checked parameters. Protocol protocol = Protocol.Lookup(signedMessage.Version); var partsRequiringProtection = from part in this.Channel.MessageDescriptions.Get(signedMessage).Mapping.Values - where part.RequiredProtection != ProtectionLevel.None - where part.IsRequired || part.IsNondefaultValueSet(signedMessage) - select part.Name; + where part.RequiredProtection != ProtectionLevel.None + where part.IsRequired || part.IsNondefaultValueSet(signedMessage) + select part.Name; ErrorUtilities.VerifyInternal(partsRequiringProtection.All(name => name.StartsWith(protocol.openid.Prefix, StringComparison.Ordinal)), "Signing only works when the parameters start with the 'openid.' prefix."); string[] signedParts = signedMessage.SignedParameterOrder.Split(','); var unsignedParts = from partName in partsRequiringProtection - where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length)) - select partName; + where !signedParts.Contains(partName.Substring(protocol.openid.Prefix.Length)) + select partName; ErrorUtilities.VerifyProtocol(!unsignedParts.Any(), OpenIdStrings.SignatureDoesNotIncludeMandatoryParts, string.Join(", ", unsignedParts.ToArray())); } @@ -293,7 +293,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { MessageDescription description = this.Channel.MessageDescriptions.Get(signedMessage); var signedParts = from part in description.Mapping.Values where (part.RequiredProtection & System.Net.Security.ProtectionLevel.Sign) != 0 - && part.GetValue(signedMessage) != null + && part.GetValue(signedMessage) != null select part.Name; string prefix = Protocol.V20.openid.Prefix; ErrorUtilities.VerifyInternal(signedParts.All(name => name.StartsWith(prefix, StringComparison.Ordinal)), "All signed message parts must start with 'openid.'."); @@ -378,8 +378,8 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // Since we have an association handle, we're either signing with a smart association, // or verifying a dumb one. bool signing = string.IsNullOrEmpty(signedMessage.Signature); - AssociationRelyingPartyType type = signing ? AssociationRelyingPartyType.Smart : AssociationRelyingPartyType.Dumb; - association = this.opAssociations.GetAssociation(type, signedMessage.AssociationHandle); + bool isPrivateAssociation = !signing; + association = this.opAssociations.Deserialize(signedMessage, isPrivateAssociation, signedMessage.AssociationHandle); if (association == null) { // There was no valid association with the requested handle. // Let's tell the RP to forget about that association. @@ -403,12 +403,7 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // If no assoc_handle was given or it was invalid, the only thing // left to do is sign a message using a 'dumb' mode association. Protocol protocol = Protocol.Default; - Association association = this.opAssociations.GetAssociation(AssociationRelyingPartyType.Dumb, this.opSecuritySettings); - if (association == null) { - association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opSecuritySettings); - this.opAssociations.StoreAssociation(AssociationRelyingPartyType.Dumb, association); - } - + Association association = HmacShaAssociation.Create(protocol, protocol.Args.SignatureAlgorithm.HMAC_SHA256, AssociationRelyingPartyType.Dumb, this.opAssociations, this.opSecuritySettings); return association; } } diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHKeyGeneration.cs b/src/DotNetOpenAuth/OpenId/DiffieHellman/DHKeyGeneration.cs index c48151b..6eca6a0 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHKeyGeneration.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellman/DHKeyGeneration.cs @@ -8,6 +8,14 @@ // // (C) 2003 The Mentalis.org Team (http://www.mentalis.org/) // +// Source Code License +// Copyright © 2002-2007, The Mentalis.org Team +// All rights reserved. +// http://www.mentalis.org/ +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// - Neither the name of the Mentalis.org Team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Org.Mentalis.Security.Cryptography { diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHParameters.cs b/src/DotNetOpenAuth/OpenId/DiffieHellman/DHParameters.cs index 2772d7a..8105125 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DHParameters.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellman/DHParameters.cs @@ -8,7 +8,14 @@ // // (C) 2003 The Mentalis.org Team (http://www.mentalis.org/) // - +// Source Code License +// Copyright © 2002-2007, The Mentalis.org Team +// All rights reserved. +// http://www.mentalis.org/ +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// - Neither the name of the Mentalis.org Team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Org.Mentalis.Security.Cryptography { /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellman.cs b/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellman.cs index 6438135..5019f70 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellman.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellman.cs @@ -8,6 +8,15 @@ // // (C) 2003 The Mentalis.org Team (http://www.mentalis.org/) // +// +// Source Code License +// Copyright © 2002-2007, The Mentalis.org Team +// All rights reserved. +// http://www.mentalis.org/ +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// - Neither the name of the Mentalis.org Team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; using System.Security.Cryptography; diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellmanManaged.cs b/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellmanManaged.cs index 75787c3..6ac28df 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellmanManaged.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellman/DiffieHellmanManaged.cs @@ -11,6 +11,15 @@ // References: // - PKCS#3 [http://www.rsasecurity.com/rsalabs/pkcs/pkcs-3/] // +// +// Source Code License +// Copyright © 2002-2007, The Mentalis.org Team +// All rights reserved. +// http://www.mentalis.org/ +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// - Neither the name of the Mentalis.org Team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; using System.Security.Cryptography; diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs index 4e12619..249f1f3 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs @@ -123,7 +123,7 @@ namespace DotNetOpenAuth.OpenId { /// </summary> private class DHSha { /// <summary> - /// Initializes a new instance of the <see cref="DiffieHellmanUtilities.DHSha"/> class. + /// Initializes a new instance of the <see cref="DHSha"/> class. /// </summary> /// <param name="algorithm">The hashing algorithm used in this particular Diffie-Hellman session type.</param> /// <param name="getName">A function that will return the value of the openid.session_type parameter for a given version of OpenID.</param> diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs index edc08ee..29da1be 100644 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs @@ -102,7 +102,6 @@ namespace DotNetOpenAuth.OpenId { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); Contract.Requires<ArgumentNullException>(secret != null); Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); - HmacSha match = hmacShaAssociationTypes.FirstOrDefault(sha => String.Equals(sha.GetAssociationType(protocol), associationType, StringComparison.Ordinal)); ErrorUtilities.VerifyProtocol(match != null, OpenIdStrings.NoAssociationTypeFoundByName, associationType); return new HmacShaAssociation(match, handle, secret, totalLifeLength); @@ -139,35 +138,29 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Creates a new association of a given type. + /// Creates a new association of a given type at an OpenID Provider. /// </summary> /// <param name="protocol">The protocol.</param> /// <param name="associationType">Type of the association (i.e. HMAC-SHA1 or HMAC-SHA256)</param> /// <param name="associationUse">A value indicating whether the new association will be used privately by the Provider for "dumb mode" authentication /// or shared with the Relying Party for "smart mode" authentication.</param> + /// <param name="associationStore">The Provider's association store.</param> /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns>The newly created association.</returns> + /// <returns> + /// The newly created association. + /// </returns> /// <remarks> /// The new association is NOT automatically put into an association store. This must be done by the caller. /// </remarks> - internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, ProviderSecuritySettings securitySettings) { + internal static HmacShaAssociation Create(Protocol protocol, string associationType, AssociationRelyingPartyType associationUse, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(protocol != null); Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(associationType)); + Contract.Requires<ArgumentNullException>(associationStore != null); Contract.Requires<ArgumentNullException>(securitySettings != null); Contract.Ensures(Contract.Result<HmacShaAssociation>() != null); int secretLength = GetSecretLength(protocol, associationType); - // Generate the handle. It must be unique, and preferably unpredictable, - // so we use a time element and a random data element to generate it. - string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); - string handle = string.Format( - CultureInfo.InvariantCulture, - "{{{0}}}{{{1}}}{{{2}}}", - DateTime.UtcNow.Ticks, - uniq, - secretLength); - // Generate the secret that will be used for signing byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); @@ -180,10 +173,13 @@ namespace DotNetOpenAuth.OpenId { lifetime = DumbSecretLifetime; } + string handle = associationStore.Serialize(secret, DateTime.UtcNow + lifetime, associationUse == AssociationRelyingPartyType.Dumb); + Contract.Assert(protocol != null); // All the way up to the method call, the condition holds, yet we get a Requires failure next Contract.Assert(secret != null); Contract.Assert(!String.IsNullOrEmpty(associationType)); - return Create(protocol, associationType, handle, secret, lifetime); + var result = Create(protocol, associationType, handle, secret, lifetime); + return result; } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs index 39891b3..215ea24 100644 --- a/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs +++ b/src/DotNetOpenAuth/OpenId/HostMetaDiscoveryService.cs @@ -435,7 +435,7 @@ namespace DotNetOpenAuth.OpenId { [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "By design")] public class HostMetaProxy { /// <summary> - /// Initializes a new instance of the <see cref="HostMetaDiscoveryService.HostMetaProxy"/> class. + /// Initializes a new instance of the <see cref="HostMetaProxy"/> class. /// </summary> /// <param name="proxyFormat">The proxy formatting string.</param> /// <param name="signingHostFormat">The signing host formatting string.</param> diff --git a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs b/src/DotNetOpenAuth/OpenId/IAssociationStore.cs deleted file mode 100644 index fb4487c..0000000 --- a/src/DotNetOpenAuth/OpenId/IAssociationStore.cs +++ /dev/null @@ -1,189 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IAssociationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId { - using System; - using System.Diagnostics.Contracts; - - /// <summary> - /// An enumeration that can specify how a given <see cref="Association"/> is used. - /// </summary> - public enum AssociationRelyingPartyType { - /// <summary> - /// The <see cref="Association"/> manages a shared secret between - /// Provider and Relying Party sites that allows the RP to verify - /// the signature on a message from an OP. - /// </summary> - Smart, - - /// <summary> - /// The <see cref="Association"/> manages a secret known alone by - /// a Provider that allows the Provider to verify its own signatures - /// for "dumb" (stateless) relying parties. - /// </summary> - Dumb - } - - /// <summary> - /// Stores <see cref="Association"/>s for lookup by their handle, keeping - /// associations separated by a given distinguishing factor (like which server the - /// association is with). - /// </summary> - /// <typeparam name="TKey"> - /// <see cref="System.Uri"/> for consumers (to distinguish associations across servers) or - /// <see cref="AssociationRelyingPartyType"/> for providers (to distinguish dumb and smart client associations). - /// </typeparam> - /// <remarks> - /// Expired associations should be periodically cleared out of an association store. - /// This should be done frequently enough to avoid a memory leak, but sparingly enough - /// to not be a performance drain. Because this balance can vary by host, it is the - /// responsibility of the host to initiate this cleaning. - /// </remarks> - ////[ContractClass(typeof(IAssociationStoreContract<>))] - public interface IAssociationStore<TKey> { - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - /// <remarks> - /// TODO: what should implementations do on association handle conflict? - /// </remarks> - void StoreAssociation(TKey distinguishingFactor, Association association); - - /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - /// </returns> - /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="distinguishingFactor"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. - /// </remarks> - Association GetAssociation(TKey distinguishingFactor, SecuritySettings securityRequirements); - - /// <summary> - /// Gets the association for a given key and handle. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns> - Association GetAssociation(TKey distinguishingFactor, string handle); - - /// <summary>Removes a specified handle that may exist in the store.</summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns>True if the association existed in this store previous to this call.</returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - bool RemoveAssociation(TKey distinguishingFactor, string handle); - } - - // For some odd reason, having this next class causes our test project to fail to build with this error: - // Error 42 Method 'StoreAssociation' in type 'DotNetOpenAuth.OpenId.IAssociationStoreContract_Accessor`1' from assembly 'DotNetOpenAuth_Accessor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation. DotNetOpenAuth.Test - /////// <summary> - /////// Code Contract for the <see cref="IAssociationStore<TKey>"/> class. - /////// </summary> - /////// <typeparam name="TKey">The type of the key.</typeparam> - ////[ContractClassFor(typeof(IAssociationStore<>))] - ////internal abstract class IAssociationStoreContract<TKey> : IAssociationStore<TKey> { - //// #region IAssociationStore<TKey> Members - - //// /// <summary> - //// /// Saves an <see cref="Association"/> for later recall. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - //// /// <param name="association">The association to store.</param> - //// /// <remarks> - //// /// TODO: what should implementations do on association handle conflict? - //// /// </remarks> - //// void IAssociationStore<TKey>.StoreAssociation(TKey distinguishingFactor, Association association) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires<ArgumentNullException>(association != null); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Gets the best association (the one with the longest remaining life) for a given key. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - //// /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> - //// /// <returns> - //// /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. - //// /// </returns> - //// /// <remarks> - //// /// In the event that multiple associations exist for the given - //// /// <paramref name="distinguishingFactor"/>, it is important for the - //// /// implementation for this method to use the <paramref name="securityRequirements"/> - //// /// to pick the best (highest grade or longest living as the host's policy may dictate) - //// /// association that fits the security requirements. - //// /// Associations that are returned that do not meet the security requirements will be - //// /// ignored and a new association created. - //// /// </remarks> - //// Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, SecuritySettings securityRequirements) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires<ArgumentNullException>(securityRequirements != null); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Gets the association for a given key and handle. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - //// /// <param name="handle">The handle of the specific association that must be recalled.</param> - //// /// <returns> - //// /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - //// /// </returns> - //// Association IAssociationStore<TKey>.GetAssociation(TKey distinguishingFactor, string handle) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires(!String.IsNullOrEmpty(handle)); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Removes a specified handle that may exist in the store. - //// /// </summary> - //// /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - //// /// <param name="handle">The handle of the specific association that must be deleted.</param> - //// /// <returns> - //// /// True if the association existed in this store previous to this call. - //// /// </returns> - //// /// <remarks> - //// /// No exception should be thrown if the association does not exist in the store - //// /// before this call. - //// /// </remarks> - //// bool IAssociationStore<TKey>.RemoveAssociation(TKey distinguishingFactor, string handle) { - //// Contract.Requires<ArgumentNullException>(distinguishingFactor != null); - //// Contract.Requires(!String.IsNullOrEmpty(handle)); - //// throw new NotImplementedException(); - //// } - - //// /// <summary> - //// /// Clears all expired associations from the store. - //// /// </summary> - //// /// <remarks> - //// /// If another algorithm is in place to periodically clear out expired associations, - //// /// this method call may be ignored. - //// /// This should be done frequently enough to avoid a memory leak, but sparingly enough - //// /// to not be a performance drain. - //// /// </remarks> - //// void IAssociationStore<TKey>.ClearExpiredAssociations() { - //// throw new NotImplementedException(); - //// } - - //// #endregion - ////} -} diff --git a/src/DotNetOpenAuth/OpenId/IOpenIdApplicationStore.cs b/src/DotNetOpenAuth/OpenId/IOpenIdApplicationStore.cs new file mode 100644 index 0000000..6c04a81 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/IOpenIdApplicationStore.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// <copyright file="IOpenIdApplicationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId { + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A hybrid of the store interfaces that an OpenID Provider must implement, and + /// an OpenID Relying Party may implement to operate in stateful (smart) mode. + /// </summary> + public interface IOpenIdApplicationStore : ICryptoKeyStore, INonceStore { + } +} diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs index a4fdf49..5237826 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateDiffieHellmanResponse.cs @@ -66,20 +66,23 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Creates the association at the provider side after the association request has been received. /// </summary> /// <param name="request">The association request.</param> + /// <param name="associationStore">The OpenID Provider's association store or handle encoder.</param> /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns>The newly created association.</returns> + /// <returns> + /// The newly created association. + /// </returns> /// <remarks> /// The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller. /// </remarks> - protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { + protected override Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { var diffieHellmanRequest = request as AssociateDiffieHellmanRequest; ErrorUtilities.VerifyInternal(diffieHellmanRequest != null, "Expected a DH request type."); this.SessionType = this.SessionType ?? request.SessionType; // Go ahead and create the association first, complete with its secret that we're about to share. - Association association = HmacShaAssociation.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, securitySettings); + Association association = HmacShaAssociation.Create(this.Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); // We now need to securely communicate the secret to the relying party using Diffie-Hellman. // We do this by performing a DH algorithm on the secret and setting a couple of properties diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs index 3fd9424..2a0dc7a 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs @@ -129,18 +129,18 @@ namespace DotNetOpenAuth.OpenId.Messages { /// <summary> /// Creates a Provider's response to an incoming association request. /// </summary> - /// <param name="associationStore">The association store where a new association (if created) will be stored. Must not be null.</param> + /// <param name="associationStore">The association store.</param> /// <param name="securitySettings">The security settings on the Provider.</param> /// <returns> /// The appropriate association response that is ready to be sent back to the Relying Party. /// </returns> /// <remarks> - /// <para>If an association is created, it will be automatically be added to the provided + /// <para>If an association is created, it will be automatically be added to the provided /// association store.</para> - /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. + /// <para>Successful association response messages will derive from <see cref="AssociateSuccessfulResponse"/>. /// Failed association response messages will derive from <see cref="AssociateUnsuccessfulResponse"/>.</para> /// </remarks> - internal IProtocolMessage CreateResponse(IAssociationStore<AssociationRelyingPartyType> associationStore, ProviderSecuritySettings securitySettings) { + internal IProtocolMessage CreateResponse(IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(associationStore != null); Contract.Requires<ArgumentNullException>(securitySettings != null); @@ -152,8 +152,7 @@ namespace DotNetOpenAuth.OpenId.Messages { // Create and store the association if this is a successful response. var successResponse = response as AssociateSuccessfulResponse; if (successResponse != null) { - Association association = successResponse.CreateAssociation(this, securitySettings); - associationStore.StoreAssociation(AssociationRelyingPartyType.Smart, association); + successResponse.CreateAssociation(this, associationStore, securitySettings); } } else { response = this.CreateUnsuccessfulResponse(securitySettings); diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs index 137cd60..42d8816 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponse.cs @@ -99,15 +99,17 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Called to create the Association based on a request previously given by the Relying Party. /// </summary> /// <param name="request">The prior request for an association.</param> + /// <param name="associationStore">The Provider's association store.</param> /// <param name="securitySettings">The security settings for the Provider. Should be <c>null</c> for Relying Parties.</param> - /// <returns>The created association.</returns> + /// <returns> + /// The created association. + /// </returns> /// <remarks> - /// <para>The response message is updated to include the details of the created association by this method, - /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> - /// <para>This method is called by both the Provider and the Relying Party, but actually performs - /// quite different operations in either scenario.</para> + /// The response message is updated to include the details of the created association by this method. + /// This method is called by both the Provider and the Relying Party, but actually performs + /// quite different operations in either scenario. /// </remarks> - internal Association CreateAssociation(AssociateRequest request, ProviderSecuritySettings securitySettings) { + internal Association CreateAssociation(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(request != null); ErrorUtilities.VerifyInternal(!this.associationCreated, "The association has already been created."); @@ -119,7 +121,7 @@ namespace DotNetOpenAuth.OpenId.Messages { association = this.CreateAssociationAtRelyingParty(request); } else { ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); - association = this.CreateAssociationAtProvider(request, securitySettings); + association = this.CreateAssociationAtProvider(request, associationStore, securitySettings); this.ExpiresIn = association.SecondsTillExpiration; this.AssociationHandle = association.Handle; } @@ -133,16 +135,19 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Called to create the Association based on a request previously given by the Relying Party. /// </summary> /// <param name="request">The prior request for an association.</param> + /// <param name="associationStore">The Provider's association store.</param> /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns>The created association.</returns> + /// <returns> + /// The created association. + /// </returns> /// <remarks> - /// <para>The caller will update this message's <see cref="ExpiresIn"/> and <see cref="AssociationHandle"/> + /// <para>The caller will update this message's <see cref="ExpiresIn"/> and <see cref="AssociationHandle"/> /// properties based on the <see cref="Association"/> returned by this method, but any other /// association type specific properties must be set by this method.</para> - /// <para>The response message is updated to include the details of the created association by this method, + /// <para>The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> /// </remarks> - protected abstract Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings); + protected abstract Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings); /// <summary> /// Called to create the Association based on a request previously given by the Relying Party. diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs index dd37da6..d474608 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateSuccessfulResponseContract.cs @@ -15,8 +15,9 @@ namespace DotNetOpenAuth.OpenId.Messages { protected AssociateSuccessfulResponseContract() : base(null, null) { } - protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { + protected override Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(request != null); + Contract.Requires<ArgumentNullException>(associationStore != null); Contract.Requires<ArgumentNullException>(securitySettings != null); throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs index 493fe6b..7e2194a 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateUnencryptedResponse.cs @@ -37,19 +37,22 @@ namespace DotNetOpenAuth.OpenId.Messages { /// Called to create the Association based on a request previously given by the Relying Party. /// </summary> /// <param name="request">The prior request for an association.</param> + /// <param name="associationStore">The Provider's association store.</param> /// <param name="securitySettings">The security settings of the Provider.</param> - /// <returns>The created association.</returns> + /// <returns> + /// The created association. + /// </returns> /// <remarks> - /// <para>The caller will update this message's - /// <see cref="AssociateSuccessfulResponse.ExpiresIn"/> and - /// <see cref="AssociateSuccessfulResponse.AssociationHandle"/> + /// <para>The caller will update this message's + /// <see cref="AssociateSuccessfulResponse.ExpiresIn"/> and + /// <see cref="AssociateSuccessfulResponse.AssociationHandle"/> /// properties based on the <see cref="Association"/> returned by this method, but any other /// association type specific properties must be set by this method.</para> - /// <para>The response message is updated to include the details of the created association by this method, + /// <para>The response message is updated to include the details of the created association by this method, /// but the resulting association is <i>not</i> added to the association store and must be done by the caller.</para> /// </remarks> - protected override Association CreateAssociationAtProvider(AssociateRequest request, ProviderSecuritySettings securitySettings) { - Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, securitySettings); + protected override Association CreateAssociationAtProvider(AssociateRequest request, IProviderAssociationStore associationStore, ProviderSecuritySettings securitySettings) { + Association association = HmacShaAssociation.Create(Protocol, this.AssociationType, AssociationRelyingPartyType.Smart, associationStore, securitySettings); this.MacKey = association.SecretKey; return association; } diff --git a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs index f1bb5ac..f4d5243 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/CheckAuthenticationResponse.cs @@ -44,10 +44,10 @@ namespace DotNetOpenAuth.OpenId.Messages { this.IsValid = request.IsValid; // Confirm the RP should invalidate the association handle only if the association - // really doesn't exist. OpenID 2.0 section 11.4.2.2. + // is not valid (any longer). OpenID 2.0 section 11.4.2.2. IndirectSignedResponse signedResponse = new IndirectSignedResponse(request, provider.Channel); string invalidateHandle = ((ITamperResistantOpenIdMessage)signedResponse).InvalidateHandle; - if (!string.IsNullOrEmpty(invalidateHandle) && provider.AssociationStore.GetAssociation(AssociationRelyingPartyType.Smart, invalidateHandle) == null) { + if (!string.IsNullOrEmpty(invalidateHandle) && !provider.AssociationStore.IsValid(signedResponse, false, invalidateHandle)) { this.InvalidateHandle = invalidateHandle; } } diff --git a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs index 04eb23c..68babd9 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdUtilities.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId { using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; + using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; @@ -30,6 +31,19 @@ namespace DotNetOpenAuth.OpenId { internal const string CustomParameterPrefix = "dnoa."; /// <summary> + /// Creates a random association handle. + /// </summary> + /// <returns>The association handle.</returns> + public static string GenerateRandomAssociationHandle() { + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + // Generate the handle. It must be unique, and preferably unpredictable, + // so we use a time element and a random data element to generate it. + string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); + return string.Format(CultureInfo.InvariantCulture, "{{{0}}}{{{1}}}", DateTime.UtcNow.Ticks, uniq); + } + + /// <summary> /// Gets the OpenID protocol instance for the version in a message. /// </summary> /// <param name="message">The message.</param> @@ -156,5 +170,26 @@ namespace DotNetOpenAuth.OpenId { ErrorUtilities.VerifyOperation(aggregator != null, OpenIdStrings.UnsupportedChannelConfiguration); return aggregator.Factories; } + + /// <summary> + /// Determines whether the association with the specified handle is (still) valid. + /// </summary> + /// <param name="associationStore">The association store.</param> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// <c>true</c> if the specified containing message is valid; otherwise, <c>false</c>. + /// </returns> + internal static bool IsValid(this IProviderAssociationStore associationStore, IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + Contract.Requires<ArgumentNullException>(associationStore != null); + Contract.Requires<ArgumentNullException>(containingMessage != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + try { + return associationStore.Deserialize(containingMessage, isPrivateAssociation, handle) != null; + } catch (ProtocolException) { + return false; + } + } } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs index c5e6bba..974d729 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs @@ -31,7 +31,7 @@ namespace DotNetOpenAuth.OpenId.Provider { internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) : base(provider, request) { Contract.Requires<ArgumentNullException>(provider != null); - Contract.Requires<ArgumentException>(!(request is CheckIdRequest), "Instantiate AuthenticationRequest to handle this kind of message."); + Contract.Requires<ArgumentException>(!(request is CheckIdRequest)); this.positiveResponse = new IndirectSignedResponse(request); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs new file mode 100644 index 0000000..a8ac41e --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationDataBag.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.IO; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// A signed and encrypted serialization of an association. + /// </summary> + internal class AssociationDataBag : DataBag, IStreamSerializingDataBag { + /// <summary> + /// Initializes a new instance of the <see cref="AssociationDataBag"/> class. + /// </summary> + public AssociationDataBag() { + } + + /// <summary> + /// Gets or sets the association secret. + /// </summary> + [MessagePart(IsRequired = true)] + internal byte[] Secret { get; set; } + + /// <summary> + /// Gets or sets the UTC time that this association expires. + /// </summary> + [MessagePart(IsRequired = true)] + internal DateTime ExpiresUtc { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether this instance is for "dumb" mode RPs. + /// </summary> + /// <value> + /// <c>true</c> if this instance is private association; otherwise, <c>false</c>. + /// </value> + [MessagePart(IsRequired = true)] + internal bool IsPrivateAssociation { + get { return this.AssociationType == AssociationRelyingPartyType.Dumb; } + set { this.AssociationType = value ? AssociationRelyingPartyType.Dumb : AssociationRelyingPartyType.Smart; } + } + + /// <summary> + /// Gets or sets the type of the association (shared or private, a.k.a. smart or dumb). + /// </summary> + internal AssociationRelyingPartyType AssociationType { get; set; } + + /// <summary> + /// Serializes the instance to the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + public void Serialize(Stream stream) { + var writer = new BinaryWriter(stream); + writer.Write(this.IsPrivateAssociation); + writer.WriteBuffer(this.Secret); + writer.Write((int)(this.ExpiresUtc - TimestampEncoder.Epoch).TotalSeconds); + writer.Flush(); + } + + /// <summary> + /// Initializes the fields on this instance from the specified stream. + /// </summary> + /// <param name="stream">The stream.</param> + public void Deserialize(Stream stream) { + var reader = new BinaryReader(stream); + this.IsPrivateAssociation = reader.ReadBoolean(); + this.Secret = reader.ReadBuffer(); + this.ExpiresUtc = TimestampEncoder.Epoch + TimeSpan.FromSeconds(reader.ReadInt32()); + } + + /// <summary> + /// Creates the formatter used for serialization of this type. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param> + /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param> + /// <param name="minimumAge">The minimum age.</param> + /// <returns> + /// A formatter for serialization. + /// </returns> + internal static IDataBagFormatter<AssociationDataBag> CreateFormatter(ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan? minimumAge = null) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket)); + Contract.Ensures(Contract.Result<IDataBagFormatter<AssociationDataBag>>() != null); + return new BinaryDataBagFormatter<AssociationDataBag>(cryptoKeyStore, bucket, signed: true, encrypted: true, minimumAge: minimumAge); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs b/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs new file mode 100644 index 0000000..4d121b1 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/AssociationRelyingPartyType.cs @@ -0,0 +1,26 @@ +//----------------------------------------------------------------------- +// <copyright file="AssociationRelyingPartyType.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + /// <summary> + /// An enumeration that can specify how a given <see cref="Association"/> is used. + /// </summary> + public enum AssociationRelyingPartyType { + /// <summary> + /// The <see cref="Association"/> manages a shared secret between + /// Provider and Relying Party sites that allows the RP to verify + /// the signature on a message from an OP. + /// </summary> + Smart, + + /// <summary> + /// The <see cref="Association"/> manages a secret known alone by + /// a Provider that allows the Provider to verify its own signatures + /// for "dumb" (stateless) relying parties. + /// </summary> + Dumb + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs deleted file mode 100644 index c16f1f9..0000000 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderApplicationStore.cs +++ /dev/null @@ -1,20 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IProviderApplicationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.Provider { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging.Bindings; - - /// <summary> - /// A hybrid of all the store interfaces that a Provider requires in order - /// to operate in "smart" mode. - /// </summary> - public interface IProviderApplicationStore : IAssociationStore<AssociationRelyingPartyType>, INonceStore { - } -} diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderAssociationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderAssociationStore.cs new file mode 100644 index 0000000..5a554c1 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderAssociationStore.cs @@ -0,0 +1,90 @@ +//----------------------------------------------------------------------- +// <copyright file="IProviderAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Provides association serialization and deserialization. + /// </summary> + /// <remarks> + /// Implementations may choose to store the association details in memory or a database table and simply return a + /// short, randomly generated string that is the key to that data. Alternatively, an implementation may + /// sign and encrypt the association details and then encode the results as a base64 string and return that value + /// as the association handle, thereby avoiding any association persistence at the OpenID Provider. + /// When taking the latter approach however, it is of course imperative that the association be encrypted + /// to avoid disclosing the secret to anyone who sees the association handle, which itself isn't considered to + /// be confidential. + /// </remarks> + [ContractClass(typeof(IProviderAssociationStoreContract))] + internal interface IProviderAssociationStore { + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation); + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle); + } + + /// <summary> + /// Code contract for the <see cref="IProviderAssociationStore"/> interface. + /// </summary> + [ContractClassFor(typeof(IProviderAssociationStore))] + internal abstract class IProviderAssociationStoreContract : IProviderAssociationStore { + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The expires UTC.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + string IProviderAssociationStore.Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + Contract.Requires<ArgumentNullException>(secret != null); + Contract.Requires<ArgumentException>(expiresUtc.Kind == DateTimeKind.Utc); + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + throw new NotImplementedException(); + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + Association IProviderAssociationStore.Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { + Contract.Requires<ArgumentNullException>(containingMessage != null); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index 3b4cb56..1c065b2 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -55,7 +55,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> public OpenIdProvider() : this(DotNetOpenAuthSection.Configuration.OpenId.Provider.ApplicationStore.CreateInstance(HttpApplicationStore)) { - Contract.Ensures(this.AssociationStore != null); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); } @@ -64,10 +63,9 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. /// </summary> /// <param name="applicationStore">The application store to use. Cannot be null.</param> - public OpenIdProvider(IProviderApplicationStore applicationStore) - : this(applicationStore, applicationStore) { + public OpenIdProvider(IOpenIdApplicationStore applicationStore) + : this((INonceStore)applicationStore, (ICryptoKeyStore)applicationStore) { Contract.Requires<ArgumentNullException>(applicationStore != null); - Contract.Ensures(this.AssociationStore == applicationStore); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); } @@ -75,25 +73,25 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Initializes a new instance of the <see cref="OpenIdProvider"/> class. /// </summary> - /// <param name="associationStore">The association store to use. Cannot be null.</param> /// <param name="nonceStore">The nonce store to use. Cannot be null.</param> - private OpenIdProvider(IAssociationStore<AssociationRelyingPartyType> associationStore, INonceStore nonceStore) { - Contract.Requires<ArgumentNullException>(associationStore != null); + /// <param name="cryptoKeyStore">The crypto key store. Cannot be null.</param> + private OpenIdProvider(INonceStore nonceStore, ICryptoKeyStore cryptoKeyStore) { Contract.Requires<ArgumentNullException>(nonceStore != null); - Contract.Ensures(this.AssociationStore == associationStore); + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); Contract.Ensures(this.SecuritySettings != null); Contract.Ensures(this.Channel != null); - this.AssociationStore = associationStore; this.SecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); this.behaviors.CollectionChanged += this.OnBehaviorsChanged; foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.Provider.Behaviors.CreateInstances(false)) { this.behaviors.Add(behavior); } + this.AssociationStore = new SwitchingAssociationStore(cryptoKeyStore, this.SecuritySettings); this.Channel = new OpenIdChannel(this.AssociationStore, nonceStore, this.SecuritySettings); + this.CryptoKeyStore = cryptoKeyStore; - Reporting.RecordFeatureAndDependencyUse(this, associationStore, nonceStore); + Reporting.RecordFeatureAndDependencyUse(this, nonceStore); } /// <summary> @@ -101,16 +99,16 @@ namespace DotNetOpenAuth.OpenId.Provider { /// HttpApplication state dictionary to store associations and nonces. /// </summary> [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IProviderApplicationStore HttpApplicationStore { + public static IOpenIdApplicationStore HttpApplicationStore { get { Contract.Requires<InvalidOperationException>(HttpContext.Current != null && HttpContext.Current.Request != null, MessagingStrings.HttpContextRequired); - Contract.Ensures(Contract.Result<IProviderApplicationStore>() != null); + Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null); HttpContext context = HttpContext.Current; - var store = (IProviderApplicationStore)context.Application[ApplicationStoreKey]; + var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]; if (store == null) { context.Application.Lock(); try { - if ((store = (IProviderApplicationStore)context.Application[ApplicationStoreKey]) == null) { + if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) { context.Application[ApplicationStoreKey] = store = new StandardProviderApplicationStore(); } } finally { @@ -168,16 +166,28 @@ namespace DotNetOpenAuth.OpenId.Provider { } /// <summary> - /// Gets the list of services that can perform discovery on identifiers given to this relying party. + /// Gets the crypto key store. /// </summary> - internal IList<IIdentifierDiscoveryService> DiscoveryServices { - get { return this.RelyingParty.DiscoveryServices; } - } + public ICryptoKeyStore CryptoKeyStore { get; private set; } /// <summary> /// Gets the association store. /// </summary> - internal IAssociationStore<AssociationRelyingPartyType> AssociationStore { get; private set; } + internal IProviderAssociationStore AssociationStore { get; private set; } + + /// <summary> + /// Gets the channel. + /// </summary> + internal OpenIdChannel OpenIdChannel { + get { return (OpenIdChannel)this.Channel; } + } + + /// <summary> + /// Gets the list of services that can perform discovery on identifiers given to this relying party. + /// </summary> + internal IList<IIdentifierDiscoveryService> DiscoveryServices { + get { return this.RelyingParty.DiscoveryServices; } + } /// <summary> /// Gets the web request handler to use for discovery and the part of @@ -557,5 +567,74 @@ namespace DotNetOpenAuth.OpenId.Provider { Reporting.RecordFeatureUse(profile); } } + + /// <summary> + /// Provides a single OP association store instance that can handle switching between + /// association handle encoding modes. + /// </summary> + private class SwitchingAssociationStore : IProviderAssociationStore { + /// <summary> + /// The security settings of the Provider. + /// </summary> + private readonly ProviderSecuritySettings securitySettings; + + /// <summary> + /// The association store that records association secrets in the association handles themselves. + /// </summary> + private IProviderAssociationStore associationHandleEncoder; + + /// <summary> + /// The association store that records association secrets in a secret store. + /// </summary> + private IProviderAssociationStore associationSecretStorage; + + /// <summary> + /// Initializes a new instance of the <see cref="SwitchingAssociationStore"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + /// <param name="securitySettings">The security settings.</param> + internal SwitchingAssociationStore(ICryptoKeyStore cryptoKeyStore, ProviderSecuritySettings securitySettings) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); + Contract.Requires<ArgumentNullException>(securitySettings != null); + this.securitySettings = securitySettings; + + this.associationHandleEncoder = new ProviderAssociationHandleEncoder(cryptoKeyStore); + this.associationSecretStorage = new ProviderAssociationKeyStorage(cryptoKeyStore); + } + + /// <summary> + /// Gets the association store that applies given the Provider's current security settings. + /// </summary> + internal IProviderAssociationStore AssociationStore { + get { return this.securitySettings.EncodeAssociationSecretsInHandles ? this.associationHandleEncoder : this.associationSecretStorage; } + } + + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + return this.AssociationStore.Serialize(secret, expiresUtc, privateAssociation); + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + return this.AssociationStore.Deserialize(containingMessage, isPrivateAssociation, handle); + } + } } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs new file mode 100644 index 0000000..5de560c --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs @@ -0,0 +1,84 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderAssociationHandleEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using System.Threading; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// Provides association storage in the association handle itself, but embedding signed and encrypted association + /// details in the handle. + /// </summary> + public class ProviderAssociationHandleEncoder : IProviderAssociationStore { + /// <summary> + /// The name of the bucket in which to store keys that encrypt association data into association handles. + /// </summary> + internal const string AssociationHandleEncodingSecretBucket = "https://localhost/dnoa/association_handles"; + + /// <summary> + /// The crypto key store used to persist encryption keys. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderAssociationHandleEncoder"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The crypto key store.</param> + public ProviderAssociationHandleEncoder(ICryptoKeyStore cryptoKeyStore) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); + this.cryptoKeyStore = cryptoKeyStore; + } + + /// <summary> + /// Encodes the specified association data bag. + /// </summary> + /// <param name="secret">The symmetric secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + var associationDataBag = new AssociationDataBag { + Secret = secret, + IsPrivateAssociation = privateAssociation, + ExpiresUtc = expiresUtc, + }; + + var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket, expiresUtc - DateTime.UtcNow); + return formatter.Serialize(associationDataBag); + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="privateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + public Association Deserialize(IProtocolMessage containingMessage, bool privateAssociation, string handle) { + var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket); + AssociationDataBag bag; + try { + bag = formatter.Deserialize(containingMessage, handle); + } catch (ProtocolException ex) { + Logger.OpenId.Error("Rejecting an association because deserialization of the encoded handle failed.", ex); + return null; + } + + ErrorUtilities.VerifyProtocol(bag.IsPrivateAssociation == privateAssociation, "Unexpected association type."); + Association assoc = Association.Deserialize(handle, bag.ExpiresUtc, bag.Secret); + return assoc.IsExpired ? null : assoc; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationKeyStorage.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationKeyStorage.cs new file mode 100644 index 0000000..9801aac --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationKeyStorage.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------- +// <copyright file="ProviderAssociationKeyStorage.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Provider { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// An association storage mechanism that stores the association secrets in a private store, + /// and returns randomly generated association handles to refer to these secrets. + /// </summary> + internal class ProviderAssociationKeyStorage : IProviderAssociationStore { + /// <summary> + /// The bucket to use when recording shared associations. + /// </summary> + internal const string SharedAssociationBucket = "https://localhost/dnoa/shared_associations"; + + /// <summary> + /// The bucket to use when recording private associations. + /// </summary> + internal const string PrivateAssociationBucket = "https://localhost/dnoa/private_associations"; + + /// <summary> + /// The backing crypto key store. + /// </summary> + private readonly ICryptoKeyStore cryptoKeyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="ProviderAssociationKeyStorage"/> class. + /// </summary> + /// <param name="cryptoKeyStore">The store where association secrets will be recorded.</param> + internal ProviderAssociationKeyStorage(ICryptoKeyStore cryptoKeyStore) { + Contract.Requires<ArgumentNullException>(cryptoKeyStore != null); + this.cryptoKeyStore = cryptoKeyStore; + } + + /// <summary> + /// Stores an association and returns a handle for it. + /// </summary> + /// <param name="secret">The association secret.</param> + /// <param name="expiresUtc">The UTC time that the association should expire.</param> + /// <param name="privateAssociation">A value indicating whether this is a private association.</param> + /// <returns> + /// The association handle that represents this association. + /// </returns> + public string Serialize(byte[] secret, DateTime expiresUtc, bool privateAssociation) { + string handle; + this.cryptoKeyStore.StoreKey( + privateAssociation ? PrivateAssociationBucket : SharedAssociationBucket, + handle = OpenIdUtilities.GenerateRandomAssociationHandle(), + new CryptoKey(secret, expiresUtc)); + return handle; + } + + /// <summary> + /// Retrieves an association given an association handle. + /// </summary> + /// <param name="containingMessage">The OpenID message that referenced this association handle.</param> + /// <param name="isPrivateAssociation">A value indicating whether a private association is expected.</param> + /// <param name="handle">The association handle.</param> + /// <returns> + /// An association instance, or <c>null</c> if the association has expired or the signature is incorrect (which may be because the OP's symmetric key has changed). + /// </returns> + /// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception> + public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) { + var key = this.cryptoKeyStore.GetKey(isPrivateAssociation ? PrivateAssociationBucket : SharedAssociationBucket, handle); + if (key != null) { + return Association.Deserialize(handle, key.ExpiresUtc, key.Key); + } + + return null; + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs index d5fa4a9..130e6dd 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs @@ -24,6 +24,11 @@ namespace DotNetOpenAuth.OpenId.Provider { internal const bool ProtectDownlevelReplayAttacksDefault = true; /// <summary> + /// The default value for the <see cref="EncodeAssociationSecretsInHandles"/> property. + /// </summary> + internal const bool EncodeAssociationSecretsInHandlesDefault = true; + + /// <summary> /// The default value for the <see cref="SignOutgoingExtensions"/> property. /// </summary> internal const bool SignOutgoingExtensionsDefault = true; @@ -102,6 +107,14 @@ namespace DotNetOpenAuth.OpenId.Provider { public UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification { get; set; } /// <summary> + /// Gets or sets a value indicating whether the Provider should ease the burden of storing associations + /// by encoding them in signed, encrypted form into the association handles themselves, storing only + /// a few rotating, private symmetric keys in the Provider's store instead. + /// </summary> + /// <value>The default value for this property is <c>true</c>.</value> + public bool EncodeAssociationSecretsInHandles { get; set; } + + /// <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. diff --git a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs index 4fa2d64..c13c4bc 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; + using System.Collections.Generic; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging.Bindings; @@ -18,112 +19,97 @@ namespace DotNetOpenAuth.OpenId.Provider { /// out of the box on most single-server web sites. It is highly recommended /// that high traffic web sites consider using a database to store the information /// used by an OpenID Provider and write a custom implementation of the - /// <see cref="IProviderApplicationStore"/> interface to use instead of this + /// <see cref="IOpenIdApplicationStore"/> interface to use instead of this /// class. /// </remarks> - public class StandardProviderApplicationStore : IProviderApplicationStore { + public class StandardProviderApplicationStore : IOpenIdApplicationStore { /// <summary> /// The nonce store to use. /// </summary> private readonly INonceStore nonceStore; /// <summary> - /// The association store to use. + /// The crypto key store where symmetric keys are persisted. /// </summary> - private readonly IAssociationStore<AssociationRelyingPartyType> associationStore; + private readonly ICryptoKeyStore cryptoKeyStore; /// <summary> /// Initializes a new instance of the <see cref="StandardProviderApplicationStore"/> class. /// </summary> public StandardProviderApplicationStore() { this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - this.associationStore = new AssociationMemoryStore<AssociationRelyingPartyType>(); + this.cryptoKeyStore = new MemoryCryptoKeyStore(); } - #region IAssociationStore<AssociationRelyingPartyType> Members - - /// <summary> - /// Saves an <see cref="Association"/> for later recall. - /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - public void StoreAssociation(AssociationRelyingPartyType distinguishingFactor, Association association) { - this.associationStore.StoreAssociation(distinguishingFactor, association); - } + #region INonceStore Members /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. + /// Stores a given nonce and timestamp. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> + /// <param name="nonce">A series of random characters.</param> + /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. + /// The timestamp may also be used by the data store to clear out old nonces.</param> /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// True if the nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp. /// </returns> /// <remarks> - /// In the event that multiple associations exist for the given - /// <paramref name="distinguishingFactor"/>, it is important for the - /// implementation for this method to use the <paramref name="securityRequirements"/> - /// to pick the best (highest grade or longest living as the host's policy may dictate) - /// association that fits the security requirements. - /// Associations that are returned that do not meet the security requirements will be - /// ignored and a new association created. + /// The nonce must be stored for no less than the maximum time window a message may + /// be processed within before being discarded as an expired message. + /// If the binding element is applicable to your channel, this expiration window + /// is retrieved or set using the + /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. /// </remarks> - public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, SecuritySettings securityRequirements) { - return this.associationStore.GetAssociation(distinguishingFactor, securityRequirements); + public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { + return this.nonceStore.StoreNonce(context, nonce, timestampUtc); } + #endregion + + #region ICryptoKeyStore + /// <summary> - /// Gets the association for a given key and handle. + /// Gets the key in a given bucket and handle. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// The cryptographic key, or <c>null</c> if no matching key was found. /// </returns> - public Association GetAssociation(AssociationRelyingPartyType distinguishingFactor, string handle) { - return this.associationStore.GetAssociation(distinguishingFactor, handle); + public CryptoKey GetKey(string bucket, string handle) { + return this.cryptoKeyStore.GetKey(bucket, handle); } /// <summary> - /// Removes a specified handle that may exist in the store. + /// Gets a sequence of existing keys within a given bucket. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <param name="bucket">The bucket name. Case sensitive.</param> /// <returns> - /// True if the association existed in this store previous to this call. + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - public bool RemoveAssociation(AssociationRelyingPartyType distinguishingFactor, string handle) { - return this.associationStore.RemoveAssociation(distinguishingFactor, handle); + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.cryptoKeyStore.GetKeys(bucket); } - #endregion - - #region INonceStore Members + /// <summary> + /// Stores a cryptographic key. + /// </summary> + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + this.cryptoKeyStore.StoreKey(bucket, handle, key); + } /// <summary> - /// Stores a given nonce and timestamp. + /// Removes the key. /// </summary> - /// <param name="context">The context, or namespace, within which the <paramref name="nonce"/> must be unique.</param> - /// <param name="nonce">A series of random characters.</param> - /// <param name="timestampUtc">The timestamp that together with the nonce string make it unique. - /// The timestamp may also be used by the data store to clear out old nonces.</param> - /// <returns> - /// True if the nonce+timestamp (combination) was not previously in the database. - /// False if the nonce was stored previously with the same timestamp. - /// </returns> - /// <remarks> - /// The nonce must be stored for no less than the maximum time window a message may - /// be processed within before being discarded as an expired message. - /// If the binding element is applicable to your channel, this expiration window - /// is retrieved or set using the - /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. - /// </remarks> - public bool StoreNonce(string context, string nonce, DateTime timestampUtc) { - return this.nonceStore.StoreNonce(context, nonce, timestampUtc); + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.cryptoKeyStore.RemoveKey(bucket, handle); } #endregion diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs index 1ae2726..9a43506 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs @@ -23,7 +23,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The storage to use for saving and retrieving associations. May be null. /// </summary> - private readonly IAssociationStore<Uri> associationStore; + private readonly IRelyingPartyAssociationStore associationStore; /// <summary> /// Backing field for the <see cref="Channel"/> property. @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="channel">The channel the relying party is using.</param> /// <param name="associationStore">The association store. May be null for dumb mode relying parties.</param> /// <param name="securitySettings">The security settings.</param> - internal AssociationManager(Channel channel, IAssociationStore<Uri> associationStore, RelyingPartySecuritySettings securitySettings) { + internal AssociationManager(Channel channel, IRelyingPartyAssociationStore associationStore, RelyingPartySecuritySettings securitySettings) { Contract.Requires<ArgumentNullException>(channel != null); Contract.Requires<ArgumentNullException>(securitySettings != null); @@ -93,7 +93,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets the storage to use for saving and retrieving associations. May be null. /// </summary> - internal IAssociationStore<Uri> AssociationStoreTestHook { + internal IRelyingPartyAssociationStore AssociationStoreTestHook { get { return this.associationStore; } } @@ -196,7 +196,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var associateSuccessfulResponse = associateResponse as AssociateSuccessfulResponse; var associateUnsuccessfulResponse = associateResponse as AssociateUnsuccessfulResponse; if (associateSuccessfulResponse != null) { - Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null); + Association association = associateSuccessfulResponse.CreateAssociation(associateRequest, null, null); this.associationStore.StoreAssociation(provider.Uri, association); return association; } else if (associateUnsuccessfulResponse != null) { diff --git a/src/DotNetOpenAuth/OpenId/Associations.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/Associations.cs index cf49d1c..b171bec 100644 --- a/src/DotNetOpenAuth/OpenId/Associations.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/Associations.cs @@ -4,7 +4,7 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.OpenId { +namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 3a17263..5293876 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -212,7 +212,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(IDictionary<string, string> arguments) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgsMustBeSigned = true; foreach (var pair in arguments) { @@ -239,7 +239,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(string key, string value) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgsMustBeSigned = true; this.returnToArgs.Add(key, value); @@ -260,7 +260,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void SetCallbackArgument(string key, string value) { - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgsMustBeSigned = true; this.returnToArgs[key] = value; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs new file mode 100644 index 0000000..02ed3b0 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/CryptoKeyStoreAsRelyingPartyAssociationStore.cs @@ -0,0 +1,87 @@ +//----------------------------------------------------------------------- +// <copyright file="CryptoKeyStoreAsRelyingPartyAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// Wraps a standard <see cref="ICryptoKeyStore"/> so that it behaves as an association store. + /// </summary> + internal class CryptoKeyStoreAsRelyingPartyAssociationStore : IRelyingPartyAssociationStore { + /// <summary> + /// The underlying key store. + /// </summary> + private readonly ICryptoKeyStore keyStore; + + /// <summary> + /// Initializes a new instance of the <see cref="CryptoKeyStoreAsRelyingPartyAssociationStore"/> class. + /// </summary> + /// <param name="keyStore">The key store.</param> + internal CryptoKeyStoreAsRelyingPartyAssociationStore(ICryptoKeyStore keyStore) { + Contract.Requires<ArgumentNullException>(keyStore != null); + Contract.Ensures(this.keyStore == keyStore); + this.keyStore = keyStore; + } + + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="association">The association to store.</param> + public void StoreAssociation(Uri providerEndpoint, Association association) { + var cryptoKey = new CryptoKey(association.SerializePrivateData(), association.Expires); + this.keyStore.StoreKey(providerEndpoint.AbsoluteUri, association.Handle, cryptoKey); + } + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + public Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { + var matches = from cryptoKey in this.keyStore.GetKeys(providerEndpoint.AbsoluteUri) + where cryptoKey.Value.ExpiresUtc > DateTime.UtcNow + orderby cryptoKey.Value.ExpiresUtc descending + let assoc = Association.Deserialize(cryptoKey.Key, cryptoKey.Value.ExpiresUtc, cryptoKey.Value.Key) + where assoc.HashBitLength >= securityRequirements.MinimumHashBitLength + where assoc.HashBitLength <= securityRequirements.MaximumHashBitLength + select assoc; + return matches.FirstOrDefault(); + } + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// </returns> + public Association GetAssociation(Uri providerEndpoint, string handle) { + var cryptoKey = this.keyStore.GetKey(providerEndpoint.AbsoluteUri, handle); + return cryptoKey != null ? Association.Deserialize(handle, cryptoKey.ExpiresUtc, cryptoKey.Key) : null; + } + + /// <summary> + /// Removes a specified handle that may exist in the store. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// True if the association existed in this store previous to this call. + /// </returns> + public bool RemoveAssociation(Uri providerEndpoint, string handle) { + this.keyStore.RemoveKey(providerEndpoint.AbsoluteUri, handle); + return true; // return value isn't used by DNOA. + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs deleted file mode 100644 index 77838c8..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyApplicationStore.cs +++ /dev/null @@ -1,21 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IRelyingPartyApplicationStore.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using DotNetOpenAuth.Messaging.Bindings; - using DotNetOpenAuth.OpenId.ChannelElements; - - /// <summary> - /// A hybrid of all the store interfaces that a Relying Party requires in order - /// to operate in "smart" mode. - /// </summary> - public interface IRelyingPartyApplicationStore : IAssociationStore<Uri>, INonceStore { - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs new file mode 100644 index 0000000..21a2c53 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyAssociationStore.cs @@ -0,0 +1,153 @@ +//----------------------------------------------------------------------- +// <copyright file="IRelyingPartyAssociationStore.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using System.Diagnostics.Contracts; + + /// <summary> + /// Stores <see cref="Association"/>s for lookup by their handle, keeping + /// associations separated by a given OP Endpoint. + /// </summary> + /// <remarks> + /// Expired associations should be periodically cleared out of an association store. + /// This should be done frequently enough to avoid a memory leak, but sparingly enough + /// to not be a performance drain. Because this balance can vary by host, it is the + /// responsibility of the host to initiate this cleaning. + /// </remarks> + [ContractClass(typeof(IRelyingPartyAssociationStoreContract))] + public interface IRelyingPartyAssociationStore { + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// If the new association conflicts (in OP endpoint and association handle) with an existing association, + /// (which should never happen by the way) implementations may overwrite the previously saved association. + /// </remarks> + void StoreAssociation(Uri providerEndpoint, Association association); + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="providerEndpoint"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> + Association GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements); + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns>The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle.</returns> + Association GetAssociation(Uri providerEndpoint, string handle); + + /// <summary>Removes a specified handle that may exist in the store.</summary> + /// <param name="providerEndpoint">The OP Endpoint with which the association is established.</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// Deprecated. The return value is insignificant. + /// Previously: True if the association existed in this store previous to this call. + /// </returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> + bool RemoveAssociation(Uri providerEndpoint, string handle); + } + + /// <summary> + /// Code Contract for the <see cref="IRelyingPartyAssociationStore"/> class. + /// </summary> + [ContractClassFor(typeof(IRelyingPartyAssociationStore))] + internal abstract class IRelyingPartyAssociationStoreContract : IRelyingPartyAssociationStore { + #region IAssociationStore Members + + /// <summary> + /// Saves an <see cref="Association"/> for later recall. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for providers).</param> + /// <param name="association">The association to store.</param> + /// <remarks> + /// TODO: what should implementations do on association handle conflict? + /// </remarks> + void IRelyingPartyAssociationStore.StoreAssociation(Uri providerEndpoint, Association association) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(association != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the best association (the one with the longest remaining life) for a given key. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="securityRequirements">The security requirements that the returned association must meet.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// </returns> + /// <remarks> + /// In the event that multiple associations exist for the given + /// <paramref name="providerEndpoint"/>, it is important for the + /// implementation for this method to use the <paramref name="securityRequirements"/> + /// to pick the best (highest grade or longest living as the host's policy may dictate) + /// association that fits the security requirements. + /// Associations that are returned that do not meet the security requirements will be + /// ignored and a new association created. + /// </remarks> + Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, SecuritySettings securityRequirements) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires<ArgumentNullException>(securityRequirements != null); + throw new NotImplementedException(); + } + + /// <summary> + /// Gets the association for a given key and handle. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be recalled.</param> + /// <returns> + /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. + /// </returns> + Association IRelyingPartyAssociationStore.GetAssociation(Uri providerEndpoint, string handle) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + /// <summary> + /// Removes a specified handle that may exist in the store. + /// </summary> + /// <param name="providerEndpoint">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> + /// <param name="handle">The handle of the specific association that must be deleted.</param> + /// <returns> + /// True if the association existed in this store previous to this call. + /// </returns> + /// <remarks> + /// No exception should be thrown if the association does not exist in the store + /// before this call. + /// </remarks> + bool IRelyingPartyAssociationStore.RemoveAssociation(Uri providerEndpoint, string handle) { + Contract.Requires<ArgumentNullException>(providerEndpoint != null); + Contract.Requires(!String.IsNullOrEmpty(handle)); + throw new NotImplementedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs index 5978c21..652fa6b 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxRelyingParty.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Initializes a new instance of the <see cref="OpenIdAjaxRelyingParty"/> class. /// </summary> /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> - public OpenIdAjaxRelyingParty(IRelyingPartyApplicationStore applicationStore) + public OpenIdAjaxRelyingParty(IOpenIdApplicationStore applicationStore) : base(applicationStore) { Reporting.RecordFeatureUse(this); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs index 456b6bb..eccdacf 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdLogin.cs @@ -982,7 +982,7 @@ idselector_input_id = '" + this.ClientID + @"'; private OpenIdLogin renderControl; /// <summary> - /// Initializes a new instance of the <see cref="OpenIdLogin.InPlaceControl"/> class. + /// Initializes a new instance of the <see cref="InPlaceControl"/> class. /// </summary> /// <param name="renderControl">The render control.</param> internal InPlaceControl(OpenIdLogin renderControl) { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs index 8684bd1..9cafb74 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs @@ -524,7 +524,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// If set, this property must be set in each Page Load event /// as it is not persisted across postbacks. /// </remarks> - public IRelyingPartyApplicationStore CustomApplicationStore { get; set; } + public IOpenIdApplicationStore CustomApplicationStore { get; set; } #endregion @@ -759,7 +759,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // If we're in stateful mode, first use the explicitly given one on this control if there // is one. Then try the configuration file specified one. Finally, use the default // in-memory one that's built into OpenIdRelyingParty. - IRelyingPartyApplicationStore store = this.Stateless ? null : + IOpenIdApplicationStore store = this.Stateless ? null : (this.CustomApplicationStore ?? DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore)); var rp = new OpenIdRelyingParty(store); try { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 57c1f05..aec59b8 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -100,21 +100,21 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. /// </summary> /// <param name="applicationStore">The application store. If <c>null</c>, the relying party will always operate in "dumb mode".</param> - public OpenIdRelyingParty(IRelyingPartyApplicationStore applicationStore) + public OpenIdRelyingParty(IOpenIdApplicationStore applicationStore) : this(applicationStore, applicationStore) { } /// <summary> /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. /// </summary> - /// <param name="associationStore">The association store. If null, the relying party will always operate in "dumb mode".</param> + /// <param name="cryptoKeyStore">The association store. If null, the relying party will always operate in "dumb mode".</param> /// <param name="nonceStore">The nonce store to use. If null, the relying party will always operate in "dumb mode".</param> [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")] - private OpenIdRelyingParty(IAssociationStore<Uri> associationStore, INonceStore nonceStore) { + private OpenIdRelyingParty(ICryptoKeyStore cryptoKeyStore, INonceStore nonceStore) { // If we are a smart-mode RP (supporting associations), then we MUST also be // capable of storing nonces to prevent replay attacks. // If we're a dumb-mode RP, then 2.0 OPs are responsible for preventing replays. - Contract.Requires<ArgumentException>(associationStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); + Contract.Requires<ArgumentException>(cryptoKeyStore == null || nonceStore != null, OpenIdStrings.AssociationStoreRequiresNonceStore); this.securitySettings = DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.SecuritySettings.CreateSecuritySettings(); @@ -137,10 +137,14 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.SecuritySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; } - this.channel = new OpenIdChannel(associationStore, nonceStore, this.SecuritySettings); - this.AssociationManager = new AssociationManager(this.Channel, associationStore, this.SecuritySettings); + if (cryptoKeyStore == null) { + cryptoKeyStore = new MemoryCryptoKeyStore(); + } + + this.channel = new OpenIdChannel(cryptoKeyStore, nonceStore, this.SecuritySettings); + this.AssociationManager = new AssociationManager(this.Channel, new CryptoKeyStoreAsRelyingPartyAssociationStore(cryptoKeyStore), this.SecuritySettings); - Reporting.RecordFeatureAndDependencyUse(this, associationStore, nonceStore); + Reporting.RecordFeatureAndDependencyUse(this, cryptoKeyStore, nonceStore); } /// <summary> @@ -160,17 +164,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// HttpApplication state dictionary to store associations and nonces. /// </summary> [EditorBrowsable(EditorBrowsableState.Advanced)] - public static IRelyingPartyApplicationStore HttpApplicationStore { + public static IOpenIdApplicationStore HttpApplicationStore { get { - Contract.Ensures(Contract.Result<IRelyingPartyApplicationStore>() != null); + Contract.Ensures(Contract.Result<IOpenIdApplicationStore>() != null); HttpContext context = HttpContext.Current; - ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name); - var store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey]; + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IOpenIdApplicationStore).Name); + var store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]; if (store == null) { context.Application.Lock(); try { - if ((store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey]) == null) { + if ((store = (IOpenIdApplicationStore)context.Application[ApplicationStoreKey]) == null) { context.Application[ApplicationStoreKey] = store = new StandardRelyingPartyApplicationStore(); } } finally { diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index f22645f..866f942 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -301,7 +301,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="store">The store to pass to the relying party constructor.</param> /// <returns>The instantiated relying party.</returns> - protected override OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store) { + protected override OpenIdRelyingParty CreateRelyingParty(IOpenIdApplicationStore store) { return new OpenIdAjaxRelyingParty(store); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 7cda8e0..b1106e6 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -792,7 +792,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <returns>The instantiated relying party.</returns> protected OpenIdRelyingParty CreateRelyingParty() { - IRelyingPartyApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); + IOpenIdApplicationStore store = this.Stateless ? null : DotNetOpenAuthSection.Configuration.OpenId.RelyingParty.ApplicationStore.CreateInstance(OpenIdRelyingParty.HttpApplicationStore); return this.CreateRelyingParty(store); } @@ -801,7 +801,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="store">The store to pass to the relying party constructor.</param> /// <returns>The instantiated relying party.</returns> - protected virtual OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store) { + protected virtual OpenIdRelyingParty CreateRelyingParty(IOpenIdApplicationStore store) { return new OpenIdRelyingParty(store); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs index 5cfa191..fc334b0 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -174,7 +174,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { if (this.response.ReturnToParametersSignatureValidated) { return this.GetUntrustedCallbackArgument(key); } else { - Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); return null; } } @@ -214,7 +214,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { if (this.response.ReturnToParametersSignatureValidated) { return this.GetUntrustedCallbackArguments(); } else { - Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IRelyingPartyAssociationStore).Name, typeof(OpenIdRelyingParty).Name); return EmptyDictionary<string, string>.Instance; } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs deleted file mode 100644 index 6d55e39..0000000 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PrivateSecretManager.cs +++ /dev/null @@ -1,122 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="PrivateSecretManager.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OpenId.RelyingParty { - using System; - using System.Diagnostics.Contracts; - using DotNetOpenAuth.Configuration; - using DotNetOpenAuth.Messaging; - - /// <summary> - /// Manages signing at the RP using private secrets. - /// </summary> - internal class PrivateSecretManager { - /// <summary> - /// The optimal length for a private secret used for signing using the HMACSHA256 class. - /// </summary> - /// <remarks> - /// The 64-byte length is optimized for highest security when used with HMACSHA256. - /// See HMACSHA256.HMACSHA256(byte[]) documentation for more information. - /// </remarks> - private const int OptimalPrivateSecretLength = 64; - - /// <summary> - /// The URI to use for private associations at this RP. - /// </summary> - private static readonly Uri SecretUri = new Uri("https://localhost/dnoa/secret"); - - /// <summary> - /// The security settings that apply to this Relying Party. - /// </summary> - private RelyingPartySecuritySettings securitySettings; - - /// <summary> - /// The association store - /// </summary> - private IAssociationStore<Uri> store; - - /// <summary> - /// Initializes a new instance of the <see cref="PrivateSecretManager"/> class. - /// </summary> - /// <param name="securitySettings">The security settings.</param> - /// <param name="store">The association store.</param> - internal PrivateSecretManager(RelyingPartySecuritySettings securitySettings, IAssociationStore<Uri> store) { - Contract.Requires<ArgumentNullException>(securitySettings != null); - Contract.Requires<ArgumentNullException>(store != null); - - this.securitySettings = securitySettings; - this.store = store; - } - - /// <summary> - /// Gets the handle of the association to use for private signatures. - /// </summary> - /// <returns> - /// An string made up of plain ASCII characters. - /// </returns> - internal string CurrentHandle { - get { - Association association = this.GetAssociation(); - return association.Handle; - } - } - - /// <summary> - /// Used to verify a signature previously written. - /// </summary> - /// <param name="buffer">The data whose signature is to be verified.</param> - /// <param name="handle">The handle to the private association used to sign the data.</param> - /// <returns> - /// The signature for the given buffer using the provided handle. - /// </returns> - /// <exception cref="ProtocolException">Thrown when an association with the given handle could not be found. - /// This most likely happens if the association was near the end of its life and the user took too long to log in.</exception> - internal byte[] Sign(byte[] buffer, string handle) { - Contract.Requires<ArgumentNullException>(buffer != null); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle)); - - Association association = this.store.GetAssociation(SecretUri, handle); - ErrorUtilities.VerifyProtocol(association != null, OpenIdStrings.PrivateRPSecretNotFound, handle); - return association.Sign(buffer); - } - - /// <summary> - /// Creates the new association handle. - /// </summary> - /// <returns>The ASCII-encoded handle name.</returns> - private static string CreateNewAssociationHandle() { - string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); - string handle = "{" + DateTime.UtcNow.Ticks + "}{" + uniq + "}"; - return handle; - } - - /// <summary> - /// Gets an association to use for signing new data. - /// </summary> - /// <returns> - /// The association, which may have previously existed or - /// may have been created as a result of this call. - /// </returns> - private Association GetAssociation() { - Association privateAssociation = this.store.GetAssociation(SecretUri, this.securitySettings); - if (privateAssociation == null || !privateAssociation.HasUsefulLifeRemaining) { - int secretLength = HmacShaAssociation.GetSecretLength(Protocol.Default, Protocol.Default.Args.SignatureAlgorithm.Best); - byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); - privateAssociation = HmacShaAssociation.Create(CreateNewAssociationHandle(), secret, this.securitySettings.PrivateSecretMaximumAge); - if (!privateAssociation.HasUsefulLifeRemaining) { - Logger.OpenId.WarnFormat( - "Brand new private association has a shorter lifespan ({0}) than the maximum allowed authentication time for a user ({1}). This may lead to login failures.", - this.securitySettings.PrivateSecretMaximumAge, - DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - } - - this.store.StoreAssociation(SecretUri, privateAssociation); - } - - return privateAssociation; - } - } -} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs index fdb6931..f17b260 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; + using System.Collections.Generic; using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OpenId.ChannelElements; @@ -14,7 +15,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// An in-memory store for Relying Parties, suitable for single server, single process /// ASP.NET web sites. /// </summary> - public class StandardRelyingPartyApplicationStore : IRelyingPartyApplicationStore { + public class StandardRelyingPartyApplicationStore : IOpenIdApplicationStore { /// <summary> /// The nonce store to use. /// </summary> @@ -23,65 +24,59 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The association store to use. /// </summary> - private readonly IAssociationStore<Uri> associationStore; + private readonly ICryptoKeyStore keyStore; /// <summary> /// Initializes a new instance of the <see cref="StandardRelyingPartyApplicationStore"/> class. /// </summary> public StandardRelyingPartyApplicationStore() { this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); - this.associationStore = new AssociationMemoryStore<Uri>(); + this.keyStore = new MemoryCryptoKeyStore(); } - #region IAssociationStore<Uri> Members + #region ICryptoKeyStore Members /// <summary> - /// Saves an <see cref="Association"/> for later recall. + /// Gets the key in a given bucket and handle. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for providers).</param> - /// <param name="association">The association to store.</param> - public void StoreAssociation(Uri distinguishingFactor, Association association) { - this.associationStore.StoreAssociation(distinguishingFactor, association); + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + /// <returns> + /// The cryptographic key, or <c>null</c> if no matching key was found. + /// </returns> + public CryptoKey GetKey(string bucket, string handle) { + return this.keyStore.GetKey(bucket, handle); } /// <summary> - /// Gets the best association (the one with the longest remaining life) for a given key. + /// Gets a sequence of existing keys within a given bucket. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securityRequirements">The security settings.</param> + /// <param name="bucket">The bucket name. Case sensitive.</param> /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. + /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>. /// </returns> - public Association GetAssociation(Uri distinguishingFactor, SecuritySettings securityRequirements) { - return this.associationStore.GetAssociation(distinguishingFactor, securityRequirements); + public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) { + return this.keyStore.GetKeys(bucket); } /// <summary> - /// Gets the association for a given key and handle. + /// Stores a cryptographic key. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be recalled.</param> - /// <returns> - /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key and handle. - /// </returns> - public Association GetAssociation(Uri distinguishingFactor, string handle) { - return this.associationStore.GetAssociation(distinguishingFactor, handle); + /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param> + /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param> + /// <param name="key">The key to store.</param> + /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception> + public void StoreKey(string bucket, string handle, CryptoKey key) { + this.keyStore.StoreKey(bucket, handle, key); } /// <summary> - /// Removes a specified handle that may exist in the store. + /// Removes the key. /// </summary> - /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="handle">The handle of the specific association that must be deleted.</param> - /// <returns> - /// True if the association existed in this store previous to this call. - /// </returns> - /// <remarks> - /// No exception should be thrown if the association does not exist in the store - /// before this call. - /// </remarks> - public bool RemoveAssociation(Uri distinguishingFactor, string handle) { - return this.associationStore.RemoveAssociation(distinguishingFactor, handle); + /// <param name="bucket">The bucket name. Case sensitive.</param> + /// <param name="handle">The key handle. Case sensitive.</param> + public void RemoveKey(string bucket, string handle) { + this.keyStore.RemoveKey(bucket, handle); } #endregion diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 74fe658..145a394 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -543,7 +543,7 @@ namespace DotNetOpenAuth.OpenId { private static readonly char[] PathEndingCharacters = new char[] { '?', '#' }; /// <summary> - /// Initializes a new instance of the <see cref="UriIdentifier.SimpleUri"/> class. + /// Initializes a new instance of the <see cref="SimpleUri"/> class. /// </summary> /// <param name="value">The value.</param> internal SimpleUri(string value) { @@ -691,7 +691,7 @@ namespace DotNetOpenAuth.OpenId { private string standardScheme; /// <summary> - /// Initializes a new instance of the <see cref="UriIdentifier.NonPathCompressingUriParser"/> class. + /// Initializes a new instance of the <see cref="NonPathCompressingUriParser"/> class. /// </summary> /// <param name="standardScheme">The standard scheme that this parser will be subverting.</param> public NonPathCompressingUriParser(string standardScheme) diff --git a/src/DotNetOpenAuth/Reporting.cs b/src/DotNetOpenAuth/Reporting.cs index 03d7460..03565b8 100644 --- a/src/DotNetOpenAuth/Reporting.cs +++ b/src/DotNetOpenAuth/Reporting.cs @@ -237,6 +237,29 @@ namespace DotNetOpenAuth { /// </summary> /// <param name="value">The object whose type is the feature to set as used.</param> /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> + internal static void RecordFeatureAndDependencyUse(object value, object dependency1) { + Contract.Requires(value != null); + + // In release builds, just quietly return. + if (value == null) { + return; + } + + if (Enabled && Configuration.IncludeFeatureUsage) { + StringBuilder builder = new StringBuilder(); + builder.Append(value.GetType().Name); + builder.Append(" "); + builder.Append(dependency1 != null ? dependency1.GetType().Name : "(null)"); + observedFeatures.Add(builder.ToString()); + Touch(); + } + } + + /// <summary> + /// Records the use of a feature by object type. + /// </summary> + /// <param name="value">The object whose type is the feature to set as used.</param> + /// <param name="dependency1">Some dependency used by <paramref name="value"/>.</param> /// <param name="dependency2">Some dependency used by <paramref name="value"/>.</param> internal static void RecordFeatureAndDependencyUse(object value, object dependency1, object dependency2) { Contract.Requires(value != null); @@ -652,7 +675,7 @@ namespace DotNetOpenAuth { private bool dirty; /// <summary> - /// Initializes a new instance of the <see cref="Reporting.PersistentHashSet"/> class. + /// Initializes a new instance of the <see cref="PersistentHashSet"/> class. /// </summary> /// <param name="storage">The storage location.</param> /// <param name="fileName">Name of the file.</param> @@ -798,7 +821,7 @@ namespace DotNetOpenAuth { private bool dirty; /// <summary> - /// Initializes a new instance of the <see cref="Reporting.PersistentCounter"/> class. + /// Initializes a new instance of the <see cref="PersistentCounter"/> class. /// </summary> /// <param name="storage">The storage location.</param> /// <param name="fileName">Name of the file.</param> diff --git a/src/DotNetOpenAuth/Util.cs b/src/DotNetOpenAuth/Util.cs index e04533c..0317c4d 100644 --- a/src/DotNetOpenAuth/Util.cs +++ b/src/DotNetOpenAuth/Util.cs @@ -204,7 +204,7 @@ namespace DotNetOpenAuth { private readonly Func<T, string> toString; /// <summary> - /// Initializes a new instance of the Util.DelayedToString class. + /// Initializes a new instance of the DelayedToString class. /// </summary> /// <param name="obj">The object that may be serialized to string form.</param> /// <param name="toString">The method that will serialize the object if called upon.</param> |