diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-23 22:30:23 -0700 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2010-06-24 17:08:57 -0700 |
commit | 507e04b91c97fa75900a60d79e35e547e3454965 (patch) | |
tree | 25f9767dd8c76c1df252f97ec44efbdc6f8bdb69 /src | |
parent | 6dd05a3312483393bb3c27b3c7e7b1d945d583a2 (diff) | |
download | DotNetOpenAuth-507e04b91c97fa75900a60d79e35e547e3454965.zip DotNetOpenAuth-507e04b91c97fa75900a60d79e35e547e3454965.tar.gz DotNetOpenAuth-507e04b91c97fa75900a60d79e35e547e3454965.tar.bz2 |
Abstracted out token serialization from the token class itself.
Diffstat (limited to 'src')
20 files changed, 457 insertions, 429 deletions
diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index 3100115..bca5705 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -312,6 +312,8 @@ http://opensource.org/licenses/ms-pl.html <Compile Include="OAuthWrap\ChannelElements\AccessToken.cs" /> <Compile Include="OAuthWrap\ChannelElements\AuthorizationDataBag.cs" /> <Compile Include="OAuthWrap\ChannelElements\AuthServerBindingElementBase.cs" /> + <Compile Include="OAuthWrap\ChannelElements\IDataBagFormatter.cs" /> + <Compile Include="OAuthWrap\ChannelElements\UriStyleMessageFormatter.cs" /> <Compile Include="OAuthWrap\ChannelElements\IAccessTokenRequest.cs" /> <Compile Include="OAuthWrap\ChannelElements\IAuthorizationDescription.cs" /> <Compile Include="OAuthWrap\ChannelElements\ITokenCarryingRequest.cs" /> diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index 76dab88..0767a4c 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -840,7 +840,7 @@ namespace DotNetOpenAuth.Messaging { MessageSerializer.DeserializeJsonAsFlatDictionary(dictionary, jsonReader); return dictionary; } - + /// <summary> /// Prepares a message for transmit by applying signatures, nonces, etc. /// </summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs b/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs index db6aa2d..9f56507 100644 --- a/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs +++ b/src/DotNetOpenAuth/OAuthWrap/AuthorizationServerBase.cs @@ -62,21 +62,19 @@ namespace DotNetOpenAuth.OAuthWrap { Contract.Requires<ArgumentNullException>(request != null, "request"); var tokenRequest = (ITokenCarryingRequest)request; - var accessToken = new AccessToken( - this.AuthorizationServer.AccessTokenSigningPrivateKey, - accessTokenEncryptingPublicKey, - tokenRequest.AuthorizationDescription, - accessTokenLifetime); + var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServer.AccessTokenSigningPrivateKey, accessTokenEncryptingPublicKey); + var accessToken = new AccessToken(tokenRequest.AuthorizationDescription, accessTokenLifetime); var response = new AccessTokenSuccessResponse(request) { Scope = tokenRequest.AuthorizationDescription.Scope, - AccessToken = accessToken.Encode(), + AccessToken = accessTokenFormatter.Serialize(accessToken), Lifetime = accessToken.Lifetime, }; if (includeRefreshToken) { - var refreshToken = new RefreshToken(this.AuthorizationServer.Secret, tokenRequest.AuthorizationDescription); - response.RefreshToken = refreshToken.Encode(); + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.Secret); + var refreshToken = new RefreshToken(tokenRequest.AuthorizationDescription); + response.RefreshToken = refreshTokenFormatter.Serialize(refreshToken); } return response; diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs index 1943021..770bb23 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs @@ -53,8 +53,10 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { var tokenRequest = message as ITokenCarryingRequest; if (tokenRequest != null) { - var tokenBag = (AuthorizationDataBag)tokenRequest.AuthorizationDescription; - tokenRequest.CodeOrToken = tokenBag.Encode(); + ErrorUtilities.VerifyInternal(tokenRequest.CodeOrTokenType == CodeOrTokenType.VerificationCode, "Only verification codes are expected here."); + var tokenBag = (VerificationCode)tokenRequest.AuthorizationDescription; + var formatter = VerificationCode.CreateFormatter(this.AuthorizationServer); + tokenRequest.CodeOrToken = formatter.Serialize(tokenBag); return MessageProtections.None; } @@ -85,10 +87,14 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { try { switch (tokenRequest.CodeOrTokenType) { case CodeOrTokenType.VerificationCode: - tokenRequest.AuthorizationDescription = VerificationCode.Decode(this.AuthorizationServer.Secret, this.AuthorizationServer.VerificationCodeNonceStore, tokenRequest.CodeOrToken, message); + var verificationCodeFormatter = VerificationCode.CreateFormatter(this.AuthorizationServer); + var verificationCode = verificationCodeFormatter.Deserialize(message, tokenRequest.CodeOrToken); + tokenRequest.AuthorizationDescription = verificationCode; break; case CodeOrTokenType.RefreshToken: - tokenRequest.AuthorizationDescription = RefreshToken.Decode(this.AuthorizationServer.Secret, tokenRequest.CodeOrToken, message); + var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.Secret); + var refreshToken = refreshTokenFormatter.Deserialize(message, tokenRequest.CodeOrToken); + tokenRequest.AuthorizationDescription = refreshToken; break; default: throw ErrorUtilities.ThrowInternal("Unexpected value for CodeOrTokenType: " + tokenRequest.CodeOrTokenType); diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs index 6181b60..de6ef98 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs @@ -21,12 +21,15 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="AccessToken"/> class. /// </summary> - /// <param name="signingKey">The signing key.</param> - /// <param name="encryptingKey">The encrypting key.</param> + public AccessToken() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AccessToken"/> class. + /// </summary> /// <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(RSAParameters signingKey, RSAParameters encryptingKey, IAuthorizationDescription authorization, TimeSpan? lifetime) - : this(signingKey, encryptingKey) { + internal AccessToken(IAuthorizationDescription authorization, TimeSpan? lifetime) { Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); this.ClientIdentifier = authorization.ClientIdentifier; @@ -37,37 +40,17 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } /// <summary> - /// Initializes a new instance of the <see cref="AccessToken"/> class. - /// </summary> - /// <param name="signingKey">The signing key.</param> - /// <param name="encryptingKey">The encrypting key.</param> - private AccessToken(RSAParameters signingKey, RSAParameters encryptingKey) - : base(signingKey, encryptingKey) { - } - - /// <summary> /// Gets or sets the lifetime of the access token. /// </summary> /// <value>The lifetime.</value> [MessagePart] internal TimeSpan? Lifetime { get; set; } - /// <summary> - /// Deserializes an access token. - /// </summary> - /// <param name="signingKey">The signing public key.</param> - /// <param name="encryptingKey">The encrypting private key.</param> - /// <param name="value">The access token.</param> - /// <param name="containingMessage">The message containing this token.</param> - /// <returns>The access token.</returns> - internal static AccessToken Decode(RSAParameters signingKey, RSAParameters encryptingKey, string value, IProtocolMessage containingMessage) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage"); - Contract.Ensures(Contract.Result<AccessToken>() != null); + internal static IDataBagFormatter<AccessToken> CreateFormatter(RSAParameters signingKey, RSAParameters encryptingKey) + { + Contract.Ensures(Contract.Result<IDataBagFormatter<AccessToken>>() != null); - var self = new AccessToken(signingKey, encryptingKey); - self.Decode(value, containingMessage); - return self; + return new UriStyleMessageFormatter<AccessToken>(signingKey, encryptingKey); } /// <summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs index ef7e390..134f1af 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs @@ -21,26 +21,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="AuthorizationDataBag"/> class. /// </summary> - /// <param name="secret">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 AuthorizationDataBag(byte[] secret, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : base(secret, signed, encrypted, compressed, maximumAge, decodeOnceOnly) { - } - - /// <summary> - /// Initializes a new instance of the <see cref="AuthorizationDataBag"/> class. - /// </summary> - /// <param name="signingKey">The asymmetric private key to use for signing the token.</param> - /// <param name="encryptingKey">The asymmetric public key to use for encrypting 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 AuthorizationDataBag(RSAParameters? signingKey = null, RSAParameters? encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) { + protected AuthorizationDataBag() { } /// <summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs index 6f6d5b6..a7ec958 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs @@ -24,126 +24,10 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// </summary> internal abstract class DataBag : MessageBase { /// <summary> - /// The message description cache to use for data bag types. - /// </summary> - private 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 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="DataBag"/> 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> - protected DataBag(bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + protected DataBag() : base(Protocol.Default.Version) { - 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="DataBag"/> class. - /// </summary> - /// <param name="signingKey">The asymmetric private key to use for signing the token.</param> - /// <param name="encryptingKey">The asymmetric public key to use for encrypting 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 DataBag(RSAParameters? signingKey = null, RSAParameters? encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) - : this(signingKey.HasValue, encryptingKey.HasValue, compressed, maximumAge, decodeOnceOnly) { - if (signingKey.HasValue) { - this.asymmetricSigning = new RSACryptoServiceProvider(); - this.asymmetricSigning.ImportParameters(signingKey.Value); - } - - if (encryptingKey.HasValue) { - this.asymmetricEncrypting = new RSACryptoServiceProvider(); - this.asymmetricEncrypting.ImportParameters(encryptingKey.Value); - } - - this.hasherForAsymmetricSigning = new SHA1CryptoServiceProvider(); - } - - /// <summary> - /// Initializes a new instance of the <see cref="DataBag"/> 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 DataBag(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> @@ -161,9 +45,16 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { internal DateTime UtcCreationDate { get; set; } /// <summary> + /// Gets or sets the signature. + /// </summary> + /// <value>The signature.</value> + [MessagePart("sig")] + internal byte[] Signature { get; set; } + + /// <summary> /// Gets or sets the message that delivered this DataBag instance to this host. /// </summary> - protected IProtocolMessage ContainingMessage { get; set; } + protected internal IProtocolMessage ContainingMessage { get; set; } /// <summary> /// Gets the type of this instance. @@ -176,173 +67,5 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { private string BagType { get { return this.GetType().Name; } } - - /// <summary> - /// Gets or sets the signature. - /// </summary> - /// <value>The signature.</value> - [MessagePart("sig")] - private byte[] Signature { get; set; } - - /// <summary> - /// Serializes this instance as a string that can be sent as part of a larger message. - /// </summary> - /// <returns>The serialized version of the data in this instance.</returns> - internal virtual string Encode() { - Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>())); - - this.UtcCreationDate = DateTime.UtcNow; - - if (this.decodeOnceOnly != null) { - this.Nonce = MessagingUtilities.GetNonCryptoRandomData(NonceLength); - } - - if (this.signed) { - this.Signature = this.CalculateSignature(); - } - - var fields = MessageSerializer.Get(this.GetType()).Serialize(MessageDescriptions.GetAccessor(this)); - 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); - } - - /// <summary> - /// Populates this instance with data from a given string. - /// </summary> - /// <param name="value">The value to deserialize from.</param> - /// <param name="containingMessage">The message that contained this token.</param> - protected virtual void Decode(string value, IProtocolMessage containingMessage) { - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage"); - - this.ContainingMessage = containingMessage; - byte[] encoded = Convert.FromBase64String(value); - - if (this.encrypted) { - encoded = this.Decrypt(encoded); - } - - if (this.compressed) { - encoded = MessagingUtilities.Decompress(encoded); - } - - value = Encoding.UTF8.GetString(encoded); - - // Deserialize into this newly created instance. - var serializer = MessageSerializer.Get(this.GetType()); - var fields = MessageDescriptions.GetAccessor(this); - serializer.Deserialize(HttpUtility.ParseQueryString(value).ToDictionary(), fields); - - if (this.signed) { - // Verify that the verification code was issued by this authorization server. - ErrorUtilities.VerifyProtocol(this.IsSignatureValid(), Protocol.bad_verification_code); - } - - if (this.maximumAge.HasValue) { - // Has this verification code expired? - DateTime expirationDate = this.UtcCreationDate + this.maximumAge.Value; - if (expirationDate < DateTime.UtcNow) { - throw new ExpiredMessageException(expirationDate, containingMessage); - } - } - - // Has this 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(this.Nonce), this.UtcCreationDate)) { - Logger.OpenId.ErrorFormat("Replayed nonce detected ({0} {1}). Rejecting message.", this.Nonce, this.UtcCreationDate); - throw new ReplayedMessageException(containingMessage); - } - } - } - - /// <summary> - /// Determines whether the signature on this instance is valid. - /// </summary> - /// <returns> - /// <c>true</c> if the signature is valid; otherwise, <c>false</c>. - /// </returns> - private bool IsSignatureValid() { - if (this.asymmetricSigning != null) { - byte[] bytesToSign = this.GetBytesToSign(); - return this.asymmetricSigning.VerifyData(bytesToSign, this.hasherForAsymmetricSigning, this.Signature); - } else { - return MessagingUtilities.AreEquivalent(this.Signature, this.CalculateSignature()); - } - } - - /// <summary> - /// Calculates the signature for the data in this verification code. - /// </summary> - /// <returns>The calculated signature.</returns> - private byte[] CalculateSignature() { - Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.symmetricHasher != null); - Contract.Ensures(Contract.Result<byte[]>() != null); - - byte[] bytesToSign = this.GetBytesToSign(); - if (this.asymmetricSigning != null) { - return this.asymmetricSigning.SignData(bytesToSign, this.hasherForAsymmetricSigning); - } else { - return this.symmetricHasher.ComputeHash(bytesToSign); - } - } - - /// <summary> - /// Gets the bytes to sign. - /// </summary> - /// <returns>A buffer of the bytes to sign.</returns> - private byte[] GetBytesToSign() { - // Sign the data, being sure to avoid any impact of the signature field itself. - var fields = MessageDescriptions.GetAccessor(this); - 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); - } - } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/IDataBagFormatter.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/IDataBagFormatter.cs new file mode 100644 index 0000000..8e1d76b --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/IDataBagFormatter.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="IDataBagFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap.ChannelElements { + using System; + using System.Diagnostics.Contracts; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// A serializer for <see cref="DataBag"/>-derived types + /// </summary> + /// <typeparam name="T">The DataBag-derived type that is to be serialized/deserialized.</typeparam> + [ContractClass(typeof(IDataBagFormatterContract<>))] + internal interface IDataBagFormatter<T> where T : DataBag, new() { + string Serialize(T message); + + T Deserialize(IProtocolMessage containingMessage, string data); + } + + /// <summary> + /// Contract class for the IDataBagFormatter interface. + /// </summary> + /// <typeparam name="T">The type of DataBag to serialize.</typeparam> + [ContractClassFor(typeof(IDataBagFormatter<>))] + internal abstract class IDataBagFormatterContract<T> : IDataBagFormatter<T> where T : DataBag, new() { + /// <summary> + /// Prevents a default instance of the <see cref="IDataBagFormatterContract<T>"/> class from being created. + /// </summary> + private IDataBagFormatterContract() { + } + + #region IDataBagFormatter<T> Members + + string IDataBagFormatter<T>.Serialize(T message) { + Contract.Requires<ArgumentNullException>(message != null, "message"); + Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>())); + + throw new System.NotImplementedException(); + } + + T IDataBagFormatter<T>.Deserialize(IProtocolMessage containingMessage, string data) { + Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage"); + Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(data)); + Contract.Ensures(Contract.Result<T>() != null); + + throw new System.NotImplementedException(); + } + + #endregion + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs index 029d583..ea08da0 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs @@ -175,7 +175,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { return (IDirectedProtocolMessage)this.Receive(fields, recipient); } - + return base.ReadFromRequestCore(request); } diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs index e95c5cc..55f3e69 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs @@ -20,11 +20,14 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="RefreshToken"/> class. /// </summary> - /// <param name="secret">The symmetric secret used to sign/encrypt refresh tokens.</param> + public RefreshToken() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="RefreshToken"/> class. + /// </summary> /// <param name="authorization">The authorization this refresh token should describe.</param> - internal RefreshToken(byte[] secret, IAuthorizationDescription authorization) - : this(secret) { - Contract.Requires<ArgumentNullException>(secret != null, "secret"); + internal RefreshToken(IAuthorizationDescription authorization) { Contract.Requires<ArgumentNullException>(authorization != null, "authorization"); this.ClientIdentifier = authorization.ClientIdentifier; @@ -33,31 +36,12 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { this.Scope = authorization.Scope; } - /// <summary> - /// Initializes a new instance of the <see cref="RefreshToken"/> class. - /// </summary> - /// <param name="secret">The symmetric secret used to sign/encrypt refresh tokens.</param> - private RefreshToken(byte[] secret) - : base(secret, true, true) { - Contract.Requires<ArgumentNullException>(secret != null, "secret"); - } - - /// <summary> - /// Deserializes a refresh token. - /// </summary> - /// <param name="secret">The symmetric secret used to sign and encrypt the token.</param> - /// <param name="value">The token.</param> - /// <param name="containingMessage">The message containing this token.</param> - /// <returns>The refresh token.</returns> - internal static RefreshToken Decode(byte[] secret, string value, IProtocolMessage containingMessage) { - Contract.Requires<ArgumentNullException>(secret != null, "secret"); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage"); - Contract.Ensures(Contract.Result<RefreshToken>() != null); + internal static IDataBagFormatter<RefreshToken> CreateFormatter(byte[] symmetricSecret) + { + Contract.Requires<ArgumentNullException>(symmetricSecret != null, "symmetricSecret"); + Contract.Ensures(Contract.Result<IDataBagFormatter<RefreshToken>>() != null); - var self = new RefreshToken(secret); - self.Decode(value, containingMessage); - return self; + return new UriStyleMessageFormatter<RefreshToken>(symmetricSecret, true, true); } } } diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/UriStyleMessageFormatter.cs new file mode 100644 index 0000000..12c532a --- /dev/null +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/UriStyleMessageFormatter.cs @@ -0,0 +1,306 @@ +//----------------------------------------------------------------------- +// <copyright file="UriStyleMessageFormatter.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuthWrap.ChannelElements { + 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> : 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> + 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. + /// </summary> + /// <param name="signingKey">The asymmetric private key to use for signing the token.</param> + /// <param name="encryptingKey">The asymmetric public key to use for encrypting 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> + internal UriStyleMessageFormatter(RSAParameters? signingKey = null, RSAParameters? encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null) + : this(signingKey.HasValue, encryptingKey.HasValue, compressed, maximumAge, decodeOnceOnly) { + if (signingKey.HasValue) { + this.asymmetricSigning = new RSACryptoServiceProvider(); + this.asymmetricSigning.ImportParameters(signingKey.Value); + } + + if (encryptingKey.HasValue) { + this.asymmetricEncrypting = new RSACryptoServiceProvider(); + this.asymmetricEncrypting.ImportParameters(encryptingKey.Value); + } + + 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> + 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) { + 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; + } + + 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); + } + + 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); + } + + public T Deserialize(IProtocolMessage containingMessage, string value) { + var message = new T(); + message.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); + + // 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); + } + + 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.AreEquivalent(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); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs index 24fe3a6..84380d9 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs @@ -25,18 +25,18 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// <summary> /// Initializes a new instance of the <see cref="VerificationCode"/> class. /// </summary> - /// <param name="secret">The symmetric secret to use in signing/encryption.</param> - /// <param name="nonceStore">The nonce store to use to ensure verification codes are used only once.</param> + public VerificationCode() { + } + + /// <summary> + /// Initializes a new instance of the <see cref="VerificationCode"/> class. + /// </summary> /// <param name="clientIdentifier">The client identifier.</param> /// <param name="callback">The callback the client used to obtain authorization.</param> /// <param name="scope">The scope.</param> /// <param name="username">The name on the account that authorized access.</param> - internal VerificationCode(byte[] secret, INonceStore nonceStore, string clientIdentifier, Uri callback, string scope, string username) - : this(secret, nonceStore) { - Contract.Requires<ArgumentNullException>(secret != null, "secret"); - Contract.Requires<ArgumentNullException>(nonceStore != null, "nonceStore"); + internal VerificationCode(string clientIdentifier, Uri callback, string scope, string username) { Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(clientIdentifier)); - Contract.Requires<ArgumentNullException>(secret != null, "secret"); Contract.Requires<ArgumentNullException>(callback != null, "callback"); this.ClientIdentifier = clientIdentifier; @@ -46,47 +46,22 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } /// <summary> - /// Initializes a new instance of the <see cref="VerificationCode"/> class. - /// </summary> - /// <param name="secret">The symmetric secret to use in signing/encryption.</param> - /// <param name="nonceStore">The nonce store to use to ensure verification codes are used only once.</param> - private VerificationCode(byte[] secret, INonceStore nonceStore) - : base(secret, true, true, false, MaximumMessageAge, nonceStore) { - Contract.Requires<ArgumentNullException>(secret != null, "secret"); - Contract.Requires<ArgumentNullException>(nonceStore != null, "nonceStore"); - } - - /// <summary> - /// Gets the maximum message age from the standard expiration binding element. - /// </summary> - private static TimeSpan MaximumMessageAge { - get { return StandardExpirationBindingElement.MaximumMessageAge; } - } - - /// <summary> /// Gets or sets the hash of the callback URL. /// </summary> [MessagePart("cb")] private byte[] CallbackHash { get; set; } - /// <summary> - /// Deserializes a verification code. - /// </summary> - /// <param name="secret">The symmetric secret used to sign and encrypt the token.</param> - /// <param name="nonceStore">The nonce store to use to ensure verification codes can only be exchanged once.</param> - /// <param name="value">The verification token.</param> - /// <param name="containingMessage">The message containing this verification code.</param> - /// <returns>The verification code.</returns> - internal static VerificationCode Decode(byte[] secret, INonceStore nonceStore, string value, IProtocolMessage containingMessage) { - Contract.Requires<ArgumentNullException>(secret != null, "secret"); - Contract.Requires<ArgumentNullException>(nonceStore != null, "nonceStore"); - Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value)); - Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage"); - Contract.Ensures(Contract.Result<VerificationCode>() != null); + internal static IDataBagFormatter<VerificationCode> CreateFormatter(IAuthorizationServer authorizationServer) { + Contract.Requires<ArgumentNullException>(authorizationServer != null, "authorizationServer"); + Contract.Ensures(Contract.Result<IDataBagFormatter<VerificationCode>>() != null); - var self = new VerificationCode(secret, nonceStore); - self.Decode(value, containingMessage); - return self; + return new UriStyleMessageFormatter<VerificationCode>( + authorizationServer.Secret, + true, + true, + false, + WebServerVerificationCodeBindingElement.MaximumMessageAge, + authorizationServer.VerificationCodeNonceStore); } /// <summary> diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebServerVerificationCodeBindingElement.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebServerVerificationCodeBindingElement.cs index 4c6e2de..1c71ebe 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebServerVerificationCodeBindingElement.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/WebServerVerificationCodeBindingElement.cs @@ -36,6 +36,13 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } /// <summary> + /// Gets the maximum message age from the standard expiration binding element. + /// </summary> + internal static TimeSpan MaximumMessageAge { + get { return StandardExpirationBindingElement.MaximumMessageAge; } + } + + /// <summary> /// Prepares a message for sending based on the rules of this channel binding element. /// </summary> /// <param name="message">The message to prepare for sending.</param> @@ -53,7 +60,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { var directResponse = (IDirectResponseProtocolMessage)response; var request = (EndUserAuthorizationRequest)directResponse.OriginatingRequest; ITokenCarryingRequest tokenCarryingResponse = response; - tokenCarryingResponse.AuthorizationDescription = new VerificationCode(this.AuthorizationServer.Secret, this.AuthorizationServer.VerificationCodeNonceStore, request.ClientIdentifier, request.Callback, request.Scope, response.AuthorizingUsername); + tokenCarryingResponse.AuthorizationDescription = new VerificationCode(request.ClientIdentifier, request.Callback, request.Scope, response.AuthorizingUsername); return MessageProtections.None; } diff --git a/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs index 4e692c6..06e8e90 100644 --- a/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/IAccessTokenAnalyzer.cs @@ -16,6 +16,7 @@ namespace DotNetOpenAuth.OAuthWrap { /// An interface that resource server hosts should implement if they accept access tokens /// issued by non-DotNetOpenAuth authorization servers. /// </summary> + [ContractClass((typeof(IAccessTokenAnalyzerContract)))] public interface IAccessTokenAnalyzer { /// <summary> /// Reads an access token to find out what data it authorizes access to. @@ -31,6 +32,7 @@ namespace DotNetOpenAuth.OAuthWrap { /// <summary> /// Code contract for the <see cref="IAccessTokenAnalyzer"/> interface. /// </summary> + [ContractClassFor(typeof(IAccessTokenAnalyzer))] internal abstract class IAccessTokenAnalyzerContract : IAccessTokenAnalyzer { /// <summary> /// Prevents a default instance of the <see cref="IAccessTokenAnalyzerContract"/> class from being created. diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/MessageBase.cs index 1a8094e..50e697a 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/MessageBase.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/MessageBase.cs @@ -13,6 +13,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// <summary> /// A common message base class for OAuth WRAP messages. /// </summary> + [Serializable] public class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage { /// <summary> /// A dictionary to contain extra message data. diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs index df6b07e..e36a1f1 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentRequest.cs @@ -15,6 +15,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { /// A message requesting user authorization to access protected data on behalf /// of an installed application or browser-hosted Javascript. /// </summary> + [Serializable] internal class UserAgentRequest : EndUserAuthorizationRequest { /// <summary> /// The type of message. diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs index a4f5b1a..a0e082b 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/UserAgent/UserAgentSuccessResponse.cs @@ -37,7 +37,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { } /// <summary> - /// Gets the access token. + /// Gets or sets the access token. /// </summary> /// <value>The access token.</value> [MessagePart(Protocol.access_token, IsRequired = true, AllowEmpty = false)] diff --git a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebServerRequest.cs b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebServerRequest.cs index 9024cb2..1843c64 100644 --- a/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebServerRequest.cs +++ b/src/DotNetOpenAuth/OAuthWrap/Messages/WebServer/WebServerRequest.cs @@ -12,6 +12,12 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { using System.Text; using DotNetOpenAuth.Messaging; + /// <summary> + /// A message sent by a web application Client to the AuthorizationServer + /// via the user agent to obtain authorization from the user and prepare + /// to issue an access token to the Consumer if permission is granted. + /// </summary> + [Serializable] public class WebServerRequest : EndUserAuthorizationRequest { /// <summary> /// The type of message. @@ -20,7 +26,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { private const string Type = "web_server"; /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. + /// Initializes a new instance of the <see cref="WebServerRequest"/> class. /// </summary> /// <param name="authorizationEndpoint">The Authorization Server's user authorization URL to direct the user to.</param> /// <param name="version">The protocol version.</param> @@ -31,7 +37,7 @@ namespace DotNetOpenAuth.OAuthWrap.Messages { } /// <summary> - /// Initializes a new instance of the <see cref="EndUserAuthorizationRequest"/> class. + /// Initializes a new instance of the <see cref="WebServerRequest"/> class. /// </summary> /// <param name="authorizationServer">The authorization server.</param> internal WebServerRequest(AuthorizationServerDescription authorizationServer) diff --git a/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs b/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs index 769fba1..3cd138c 100644 --- a/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/StandardAccessTokenAnalyzer.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuthWrap { + using System; using System.Security.Cryptography; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuthWrap.ChannelElements; @@ -46,7 +47,8 @@ namespace DotNetOpenAuth.OAuthWrap { /// A value indicating whether this access token is valid. /// </returns> public bool TryValidateAccessToken(IDirectedProtocolMessage message, string accessToken, out string user, out string scope) { - var token = AccessToken.Decode(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey, accessToken, message); + var accessTokenFormatter = AccessToken.CreateFormatter(this.AuthorizationServerPublicSigningKey, this.ResourceServerPrivateEncryptionKey); + var token = accessTokenFormatter.Deserialize(message, accessToken); user = token.User; scope = token.Scope; return true; diff --git a/src/DotNetOpenAuth/OAuthWrap/WebServerAuthorizationServer.cs b/src/DotNetOpenAuth/OAuthWrap/WebServerAuthorizationServer.cs index bfd53fd..868af77 100644 --- a/src/DotNetOpenAuth/OAuthWrap/WebServerAuthorizationServer.cs +++ b/src/DotNetOpenAuth/OAuthWrap/WebServerAuthorizationServer.cs @@ -60,18 +60,15 @@ namespace DotNetOpenAuth.OAuthWrap { this.Channel.Send(response); } - public bool TryPrepareAccessTokenResponse(out IDirectResponseProtocolMessage response) - { + public bool TryPrepareAccessTokenResponse(out IDirectResponseProtocolMessage response) { return this.TryPrepareAccessTokenResponse(this.Channel.GetRequestFromContext(), out response); } - public bool TryPrepareAccessTokenResponse(HttpRequestInfo httpRequestInfo, out IDirectResponseProtocolMessage response) - { + public bool TryPrepareAccessTokenResponse(HttpRequestInfo httpRequestInfo, out IDirectResponseProtocolMessage response) { Contract.Requires<ArgumentNullException>(httpRequestInfo != null, "httpRequestInfo"); var request = this.ReadAccessTokenRequest(httpRequestInfo); - if (request != null) - { + if (request != null) { // This convenience method only encrypts access tokens assuming that this auth server // doubles as the resource server. response = this.PrepareAccessTokenResponse(request, this.AuthorizationServer.AccessTokenSigningPrivateKey); |