summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs')
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs159
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);