diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2011-05-08 06:39:14 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2011-05-08 06:39:14 -0700 |
commit | eb34013591399e60071055e9a56e6ba7ed10a0e0 (patch) | |
tree | 688143a2723eb6ac5fbe083a1b3c5ad2a3cce42b /src | |
parent | ebcf20f3fe65b7839fdc834c861c5b11978e14ae (diff) | |
download | DotNetOpenAuth-eb34013591399e60071055e9a56e6ba7ed10a0e0.zip DotNetOpenAuth-eb34013591399e60071055e9a56e6ba7ed10a0e0.tar.gz DotNetOpenAuth-eb34013591399e60071055e9a56e6ba7ed10a0e0.tar.bz2 |
Refactored UriStyleMessageFormatter into a class hierarchy that will make it easy to serialize DataBag instances differently.
Diffstat (limited to 'src')
-rw-r--r-- | src/DotNetOpenAuth/DotNetOpenAuth.csproj | 1 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/DataBag.cs | 7 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs | 302 | ||||
-rw-r--r-- | src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs | 261 |
4 files changed, 315 insertions, 256 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 1dfe4ed..ce09c0e 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -320,6 +320,7 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="InfoCard\WellKnownIssuers.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" /> diff --git a/src/DotNetOpenAuth/Messaging/DataBag.cs b/src/DotNetOpenAuth/Messaging/DataBag.cs index 91ccc13..f9c60c2 100644 --- a/src/DotNetOpenAuth/Messaging/DataBag.cs +++ b/src/DotNetOpenAuth/Messaging/DataBag.cs @@ -81,14 +81,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> @@ -104,8 +103,8 @@ namespace DotNetOpenAuth.Messaging { /// This ensures that one token cannot be misused as another kind of token. /// </remarks> [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/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs new file mode 100644 index 0000000..89d69d3 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs @@ -0,0 +1,302 @@ +//----------------------------------------------------------------------- +// <copyright file="DataBagFormatterBase.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; + using System.IO; + + /// <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 abstract class DataBagFormatterBase<T> : IDataBagFormatter<T> where T : DataBag, new() { + /// <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. + /// </summary> + protected static readonly MessageDescriptionCollection MessageDescriptions = new MessageDescriptionCollection(); + + /// <summary> + /// The symmetric secret used for signing/encryption of verification codes and refresh tokens. + /// </summary> + private readonly byte[] symmetricSecret; + + /// <summary> + /// The hashing algorithm to use while signing when using a symmetric secret. + /// </summary> + private readonly HashAlgorithm symmetricHasher; + + /// <summary> + /// The crypto to use for signing access tokens. + /// </summary> + private readonly RSACryptoServiceProvider asymmetricSigning; + + /// <summary> + /// The crypto to use for encrypting access tokens. + /// </summary> + 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; + + /// <summary> + /// The nonce store to use to ensure that this instance is only decoded once. + /// </summary> + private readonly INonceStore decodeOnceOnly; + + /// <summary> + /// The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>. + /// </summary> + private readonly TimeSpan? maximumAge; + + /// <summary> + /// A value indicating whether the data in this instance will be protected against eavesdropping. + /// </summary> + private readonly bool encrypted; + + /// <summary> + /// A value indicating whether the data in this instance will be GZip'd. + /// </summary> + 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> + private DataBagFormatterBase(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. + /// </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="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + 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. + /// </summary> + /// <param name="symmetricSecret">The symmetric secret to use for signing and encrypting.</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="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param> + protected DataBagFormatterBase(byte[] symmetricSecret = null, bool signed = false, bool encrypted = false, bool compressed = false, 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."); + + if (symmetricSecret != null) { + this.symmetricHasher = new HMACSHA256(symmetricSecret); + } + + this.symmetricSecret = symmetricSecret; + } + + /// <summary> + /// Serializes the specified message. + /// </summary> + /// <param name="message">The message to serialize. Must not be null.</param> + /// <returns>A non-null, non-empty value.</returns> + public string Serialize(T message) { + message.UtcCreationDate = DateTime.UtcNow; + + if (this.decodeOnceOnly != null) { + message.Nonce = MessagingUtilities.GetNonCryptoRandomData(NonceLength); + } + + byte[] encoded = this.SerializeCore(message); + + if (this.compressed) { + encoded = MessagingUtilities.Compress(encoded); + } + + if (this.encrypted) { + encoded = this.Encrypt(encoded); + } + + if (this.signed) { + message.Signature = this.CalculateSignature(encoded); + } + + int capacity = this.signed ? 4 + message.Signature.Length + 4 + encoded.Length : encoded.Length; + var finalStream = new MemoryStream(capacity); + var writer = new BinaryWriter(finalStream); + if (this.signed) { + writer.WriteBuffer(message.Signature); + } + + writer.WriteBuffer(encoded); + writer.Flush(); + + return Convert.ToBase64String(finalStream.ToArray()); + } + + protected abstract byte[] SerializeCore(T message); + + protected abstract void DeserializeCore(T message, byte[] data); + + /// <summary> + /// Deserializes a <see cref="DataBag"/>. + /// </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) { + var message = new T { ContainingMessage = containingMessage }; + byte[] data = Convert.FromBase64String(value); + + byte[] signature = null; + if (this.signed) { + 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), MessagingStrings.SignatureInvalid); + } + + if (this.encrypted) { + data = this.Decrypt(data); + } + + if (this.compressed) { + data = MessagingUtilities.Decompress(data); + } + + 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? + DateTime expirationDate = message.UtcCreationDate + this.maximumAge.Value; + if (expirationDate < DateTime.UtcNow) { + throw new ExpiredMessageException(expirationDate, containingMessage); + } + } + + // Has message verification code already been used to obtain an access/refresh token? + if (this.decodeOnceOnly != null) { + ErrorUtilities.VerifyInternal(this.maximumAge.HasValue, "Oops! How can we validate a nonce without a maximum message age?"); + string context = "{" + GetType().FullName + "}"; + if (!this.decodeOnceOnly.StoreNonce(context, Convert.ToBase64String(message.Nonce), message.UtcCreationDate)) { + Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", message.Nonce, message.UtcCreationDate); + throw new ReplayedMessageException(containingMessage); + } + } + + ((IMessage)message).EnsureValidMessage(); + + return message; + } + + /// <summary> + /// Determines whether the signature on this instance is valid. + /// </summary> + /// <param name="message">The message whose signature is to be checked.</param> + /// <returns> + /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. + /// </returns> + private bool IsSignatureValid(byte[] signedData, byte[] signature) { + Contract.Requires<ArgumentNullException>(signedData != null, "message"); + Contract.Requires<ArgumentNullException>(signature != null, "signature"); + + if (this.asymmetricSigning != null) { + return this.asymmetricSigning.VerifyData(signedData, this.hasherForAsymmetricSigning, signature); + } else { + return MessagingUtilities.AreEquivalentConstantTime(signature, this.CalculateSignature(signedData)); + } + } + + /// <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(byte[] bytesToSign) { + Contract.Requires<ArgumentNullException>(bytesToSign != null, "bytesToSign"); + Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.symmetricHasher != null); + Contract.Ensures(Contract.Result<byte[]>() != null); + + if (this.asymmetricSigning != null) { + return this.asymmetricSigning.SignData(bytesToSign, this.hasherForAsymmetricSigning); + } else { + return this.symmetricHasher.ComputeHash(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); + + if (this.asymmetricEncrypting != null) { + return this.asymmetricEncrypting.EncryptWithRandomSymmetricKey(value); + } else { + return MessagingUtilities.Encrypt(value, this.symmetricSecret); + } + } + + /// <summary> + /// 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); + + if (this.asymmetricEncrypting != null) { + return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value); + } else { + return MessagingUtilities.Decrypt(value, this.symmetricSecret); + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs index 844642e..24f696f 100644 --- a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs +++ b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs @@ -20,86 +20,7 @@ namespace DotNetOpenAuth.Messaging { /// 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() { - /// <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. - /// </summary> - private static readonly MessageDescriptionCollection MessageDescriptions = new MessageDescriptionCollection(); - - /// <summary> - /// The symmetric secret used for signing/encryption of verification codes and refresh tokens. - /// </summary> - private readonly byte[] symmetricSecret; - - /// <summary> - /// The hashing algorithm to use while signing when using a symmetric secret. - /// </summary> - private readonly HashAlgorithm symmetricHasher; - - /// <summary> - /// The crypto to use for signing access tokens. - /// </summary> - private readonly RSACryptoServiceProvider asymmetricSigning; - - /// <summary> - /// The crypto to use for encrypting access tokens. - /// </summary> - 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; - - /// <summary> - /// The nonce store to use to ensure that this instance is only decoded once. - /// </summary> - private readonly INonceStore decodeOnceOnly; - - /// <summary> - /// The maximum age of a token that can be decoded; useful only when <see cref="decodeOnceOnly"/> is <c>true</c>. - /// </summary> - private readonly TimeSpan? maximumAge; - - /// <summary> - /// A value indicating whether the data in this instance will be protected against eavesdropping. - /// </summary> - private readonly bool encrypted; - - /// <summary> - /// A value indicating whether the data in this instance will be GZip'd. - /// </summary> - 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> - private 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; - } - + internal class UriStyleMessageFormatter<T> : DataBagFormatterBase<T> where T : DataBag, new() { /// <summary> /// Initializes a new instance of the <see cref="UriStyleMessageFormatter<T>"/> class. /// </summary> @@ -108,11 +29,8 @@ namespace DotNetOpenAuth.Messaging { /// <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(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(); + 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> @@ -124,185 +42,24 @@ namespace DotNetOpenAuth.Messaging { /// <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(byte[] symmetricSecret = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : this(signed, encrypted, compressed, maximumAge, decodeOnceOnly) { + protected internal UriStyleMessageFormatter(byte[] symmetricSecret = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : base(symmetricSecret, signed, encrypted, compressed, maximumAge, decodeOnceOnly) { Contract.Requires<ArgumentException>(symmetricSecret != null || (!signed && !encrypted), "A secret is required when signing or encrypting is required."); - - if (symmetricSecret != null) { - this.symmetricHasher = new HMACSHA256(symmetricSecret); - } - - this.symmetricSecret = symmetricSecret; } - /// <summary> - /// Serializes the specified message. - /// </summary> - /// <param name="message">The message to serialize. Must not be null.</param> - /// <returns>A non-null, non-empty value.</returns> - public string Serialize(T message) { - message.UtcCreationDate = DateTime.UtcNow; - - if (this.decodeOnceOnly != null) { - message.Nonce = MessagingUtilities.GetNonCryptoRandomData(NonceLength); - } - - if (this.signed) { - message.Signature = this.CalculateSignature(message); - } - + protected override byte[] SerializeCore(T message) { var fields = MessageSerializer.Get(message.GetType()).Serialize(MessageDescriptions.GetAccessor(message)); string value = MessagingUtilities.CreateQueryString(fields); - - byte[] encoded = Encoding.UTF8.GetBytes(value); - - if (this.compressed) { - encoded = MessagingUtilities.Compress(encoded); - } - - if (this.encrypted) { - encoded = this.Encrypt(encoded); - } - - return Convert.ToBase64String(encoded); + return Encoding.UTF8.GetBytes(value); } - /// <summary> - /// Deserializes a <see cref="DataBag"/>. - /// </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) { - var message = new T { ContainingMessage = containingMessage }; - byte[] data = Convert.FromBase64String(value); - - if (this.encrypted) { - data = this.Decrypt(data); - } - - if (this.compressed) { - data = MessagingUtilities.Decompress(data); - } - - value = Encoding.UTF8.GetString(data); + 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); - - if (this.signed) { - // Verify that the verification code was issued by message authorization server. - ErrorUtilities.VerifyProtocol(this.IsSignatureValid(message), MessagingStrings.SignatureInvalid); - } - - if (this.maximumAge.HasValue) { - // Has message verification code expired? - DateTime expirationDate = message.UtcCreationDate + this.maximumAge.Value; - if (expirationDate < DateTime.UtcNow) { - throw new ExpiredMessageException(expirationDate, containingMessage); - } - } - - // Has message verification code already been used to obtain an access/refresh token? - if (this.decodeOnceOnly != null) { - ErrorUtilities.VerifyInternal(this.maximumAge.HasValue, "Oops! How can we validate a nonce without a maximum message age?"); - string context = "{" + GetType().FullName + "}"; - if (!this.decodeOnceOnly.StoreNonce(context, Convert.ToBase64String(message.Nonce), message.UtcCreationDate)) { - Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", message.Nonce, message.UtcCreationDate); - throw new ReplayedMessageException(containingMessage); - } - } - - ((IMessage)message).EnsureValidMessage(); - - return message; - } - - /// <summary> - /// Determines whether the signature on this instance is valid. - /// </summary> - /// <param name="message">The message whose signature is to be checked.</param> - /// <returns> - /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. - /// </returns> - private bool IsSignatureValid(DataBag message) { - Contract.Requires<ArgumentNullException>(message != null, "message"); - - if (this.asymmetricSigning != null) { - byte[] bytesToSign = this.GetBytesToSign(message); - return this.asymmetricSigning.VerifyData(bytesToSign, this.hasherForAsymmetricSigning, message.Signature); - } else { - return MessagingUtilities.AreEquivalentConstantTime(message.Signature, this.CalculateSignature(message)); - } - } - - /// <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); - Contract.Ensures(Contract.Result<byte[]>() != null); - - byte[] bytesToSign = this.GetBytesToSign(message); - if (this.asymmetricSigning != null) { - return this.asymmetricSigning.SignData(bytesToSign, this.hasherForAsymmetricSigning); - } else { - return this.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); - - if (this.asymmetricEncrypting != null) { - return this.asymmetricEncrypting.EncryptWithRandomSymmetricKey(value); - } else { - return MessagingUtilities.Encrypt(value, this.symmetricSecret); - } - } - - /// <summary> - /// 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); - - if (this.asymmetricEncrypting != null) { - return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value); - } else { - return MessagingUtilities.Decrypt(value, this.symmetricSecret); - } } } } |