diff options
Diffstat (limited to 'src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs')
-rw-r--r-- | src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs | 159 |
1 files changed, 136 insertions, 23 deletions
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs index ccc9baf..4994468 100644 --- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs +++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs @@ -18,36 +18,79 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.OAuthWrap.Messages; + /// <summary> + /// A collection of message parts that will be serialized into a single string, + /// to be set into a larger message. + /// </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; - protected HashAlgorithm hasher; - + /// <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; - [MessagePart("t", IsRequired = true, AllowEmpty = false)] - private string BagType { - get { return this.GetType().Name; } - } - + /// <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) : 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."); @@ -60,6 +103,14 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { 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) { @@ -75,36 +126,73 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { 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 == null && encrypted == null), "A secret is required when signing or encrypting is required."); + Contract.Requires<ArgumentException>(symmetricSecret != null || (!signed && !encrypted), "A secret is required when signing or encrypting is required."); if (symmetricSecret != null) { - this.hasher = new HMACSHA256(symmetricSecret); + this.symmetricHasher = new HMACSHA256(symmetricSecret); } this.symmetricSecret = symmetricSecret; } - [MessagePart("sig")] - private string Signature { get; set; } - + /// <summary> + /// Gets or sets the nonce. + /// </summary> + /// <value>The nonce.</value> [MessagePart] internal string Nonce { get; set; } + /// <summary> + /// Gets or sets the UTC creation date of this token. + /// </summary> + /// <value>The UTC creation date.</value> [MessagePart("timestamp", IsRequired = true, Encoder = typeof(TimestampEncoder))] internal DateTime UtcCreationDate { get; set; } + /// <summary> + /// Gets the type of this instance. + /// </summary> + /// <value>The type of the bag.</value> + /// <remarks> + /// 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; } + } + + /// <summary> + /// Gets or sets the signature. + /// </summary> + /// <value>The signature.</value> + [MessagePart("sig")] + private string 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 (decodeOnceOnly != null) { + if (this.decodeOnceOnly != null) { this.Nonce = Convert.ToBase64String(MessagingUtilities.GetNonCryptoRandomData(NonceLength)); } - if (signed) { + if (this.signed) { this.Signature = this.CalculateSignature(); } @@ -113,28 +201,33 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { byte[] encoded = Encoding.UTF8.GetBytes(value); - if (compressed) { + if (this.compressed) { encoded = MessagingUtilities.Compress(encoded); } - if (encrypted) { + 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"); byte[] encoded = Convert.FromBase64String(value); - if (encrypted) { + if (this.encrypted) { encoded = this.Decrypt(encoded); } - if (compressed) { + if (this.compressed) { encoded = MessagingUtilities.Decompress(encoded); } @@ -145,12 +238,12 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { var fields = MessageDescriptions.GetAccessor(this); serializer.Deserialize(HttpUtility.ParseQueryString(value).ToDictionary(), fields); - if (signed) { + if (this.signed) { // Verify that the verification code was issued by this authorization server. ErrorUtilities.VerifyProtocol(this.IsSignatureValid(), Protocol.bad_verification_code); } - if (maximumAge.HasValue) { + if (this.maximumAge.HasValue) { // Has this verification code expired? DateTime expirationDate = this.UtcCreationDate + this.maximumAge.Value; if (expirationDate < DateTime.UtcNow) { @@ -159,7 +252,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } // Has this verification code already been used to obtain an access/refresh token? - if (decodeOnceOnly != null) { + 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, this.Nonce, this.UtcCreationDate)) { @@ -169,6 +262,12 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } } + /// <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(); @@ -184,17 +283,21 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { /// </summary> /// <returns>The calculated signature.</returns> private string CalculateSignature() { - Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.hasher != null); + Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.symmetricHasher != null); byte[] bytesToSign = this.GetBytesToSign(); if (this.asymmetricSigning != null) { byte[] signature = this.asymmetricSigning.SignData(bytesToSign, this.hasherForAsymmetricSigning); return Convert.ToBase64String(signature); } else { - return Convert.ToBase64String(this.hasher.ComputeHash(bytesToSign)); + return Convert.ToBase64String(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); @@ -207,6 +310,11 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { 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); @@ -217,6 +325,11 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements { } } + /// <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); |