summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2011-05-27 08:02:10 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2011-05-27 08:02:10 -0700
commitbe0feb422080002f7984e9a4e0161425df00d137 (patch)
tree44b0bb946f823182697778fa1bf02e0ce4926bcf /src
parentf922bee2328747e7890778473a2d1c943eaabeb1 (diff)
downloadDotNetOpenAuth-be0feb422080002f7984e9a4e0161425df00d137.zip
DotNetOpenAuth-be0feb422080002f7984e9a4e0161425df00d137.tar.gz
DotNetOpenAuth-be0feb422080002f7984e9a4e0161425df00d137.tar.bz2
Allowed for cycling of symmetric cryptographic keys by replacing the effectively constant byte[] secret with a new ICryptoKeyStore throughout the OAuth 2 and OpenID stacks.
And StyleCop fixes.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd2
-rw-r--r--src/DotNetOpenAuth/Configuration/MessagingElement.cs4
-rw-r--r--src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs3
-rw-r--r--src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs10
-rw-r--r--src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs4
-rw-r--r--src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs98
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingUtilities.cs78
-rw-r--r--src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs10
-rw-r--r--src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs2
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs8
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs18
-rw-r--r--src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs19
-rw-r--r--src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs20
-rw-r--r--src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs5
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs17
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs1
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs29
-rw-r--r--src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs33
18 files changed, 251 insertions, 110 deletions
diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
index 8b6d6c1..065b5ee 100644
--- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
+++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd
@@ -236,7 +236,7 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
- <xs:attribute name="privateSecretMaximumAge" type="xs:string">
+ <xs:attribute name="privateSecretMaximumAge" type="xs:string" default="28.00:00:00">
<xs:annotation>
<xs:documentation>
The maximum age of a secret used for private signing or encryption before it is renewed.
diff --git a/src/DotNetOpenAuth/Configuration/MessagingElement.cs b/src/DotNetOpenAuth/Configuration/MessagingElement.cs
index d85f799..1c46bcf 100644
--- a/src/DotNetOpenAuth/Configuration/MessagingElement.cs
+++ b/src/DotNetOpenAuth/Configuration/MessagingElement.cs
@@ -73,8 +73,8 @@ namespace DotNetOpenAuth.Configuration {
/// Gets or sets the maximum lifetime of a private symmetric secret,
/// that may be used for signing or encryption.
/// </summary>
- /// <value>The default value is 7 days.</value>
- [ConfigurationProperty(PrivateSecretMaximumAgeConfigName, DefaultValue = "07:00:00")]
+ /// <value>The default value is 28 days (twice the age of the longest association).</value>
+ [ConfigurationProperty(PrivateSecretMaximumAgeConfigName, DefaultValue = "28.00:00:00")]
public TimeSpan PrivateSecretMaximumAge {
get { return (TimeSpan)this[PrivateSecretMaximumAgeConfigName]; }
set { this[PrivateSecretMaximumAgeConfigName] = value; }
diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs
index 8a922f7..0d8e8b4 100644
--- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs
+++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs
@@ -35,6 +35,9 @@ namespace DotNetOpenAuth.Configuration {
/// </summary>
private const string AssociationsConfigName = "associations";
+ /// <summary>
+ /// The name of the @encodeAssociationSecretsInHandles attribute.
+ /// </summary>
private const string EncodeAssociationSecretsInHandlesConfigName = "encodeAssociationSecretsInHandles";
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs b/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs
index 08c1219..d44d9bb 100644
--- a/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs
+++ b/src/DotNetOpenAuth/Messaging/BinaryDataBagFormatter.cs
@@ -34,15 +34,17 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Initializes a new instance of the <see cref="UriStyleMessageFormatter&lt;T&gt;"/> class.
/// </summary>
- /// <param name="symmetricSecret">The symmetric secret to use for signing and encrypting.</param>
+ /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param>
+ /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param>
/// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param>
/// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param>
/// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param>
+ /// <param name="minimumAge">The minimum age.</param>
/// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param>
/// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param>
- protected internal BinaryDataBagFormatter(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.");
+ protected internal BinaryDataBagFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
+ : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) {
+ Contract.Requires<ArgumentException>((cryptoKeyStore != null && bucket != null) || (!signed && !encrypted), "A secret is required when signing or encrypting is required.");
}
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs
index 167f7b0..e7dbf46 100644
--- a/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs
+++ b/src/DotNetOpenAuth/Messaging/Bindings/CryptoKeyCollisionException.cs
@@ -8,6 +8,10 @@ namespace DotNetOpenAuth.Messaging.Bindings {
using System;
using System.Security.Permissions;
+ /// <summary>
+ /// Thrown by a hosting application or web site when a cryptographic key is created with a
+ /// bucket and handle that conflicts with a previously stored and unexpired key.
+ /// </summary>
[Serializable]
public class CryptoKeyCollisionException : ArgumentException {
/// <summary>
diff --git a/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs
index 98f5e8c..b10a36f 100644
--- a/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs
+++ b/src/DotNetOpenAuth/Messaging/DataBagFormatterBase.cs
@@ -33,14 +33,19 @@ namespace DotNetOpenAuth.Messaging {
private const int NonceLength = 6;
/// <summary>
- /// The symmetric secret used for signing/encryption of verification codes and refresh tokens.
+ /// The minimum allowable lifetime for the key used to encrypt/decrypt or sign this databag.
/// </summary>
- private readonly byte[] symmetricSecret;
+ private readonly TimeSpan minimumAge = TimeSpan.FromDays(1);
/// <summary>
- /// The hashing algorithm to use while signing when using a symmetric secret.
+ /// The symmetric key store with the secret used for signing/encryption of verification codes and refresh tokens.
/// </summary>
- private readonly HashAlgorithm symmetricHasher;
+ private readonly ICryptoKeyStore cryptoKeyStore;
+
+ /// <summary>
+ /// The bucket for symmetric keys.
+ /// </summary>
+ private readonly string cryptoKeyBucket;
/// <summary>
/// The crypto to use for signing access tokens.
@@ -100,21 +105,24 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Initializes a new instance of the <see cref="UriStyleMessageFormatter&lt;T&gt;"/> class.
/// </summary>
- /// <param name="symmetricSecret">The symmetric secret to use for signing and encrypting.</param>
+ /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param>
+ /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param>
/// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param>
/// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param>
/// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param>
+ /// <param name="minimumAge">The required minimum lifespan within which this token must be decodable and verifiable; useful only when <paramref name="signed"/> and/or <paramref name="encrypted"/> is true.</param>
/// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param>
/// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param>
- protected DataBagFormatterBase(byte[] symmetricSecret = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
+ protected DataBagFormatterBase(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
: this(signed, encrypted, compressed, maximumAge, decodeOnceOnly) {
- Contract.Requires<ArgumentException>(symmetricSecret != null || (!signed && !encrypted), "A secret is required when signing or encrypting is required.");
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket) || cryptoKeyStore == null);
+ Contract.Requires<ArgumentException>(cryptoKeyStore != null || (!signed && !encrypted), "A secret is required when signing or encrypting is required.");
- if (symmetricSecret != null) {
- this.symmetricHasher = new HMACSHA256(symmetricSecret);
+ this.cryptoKeyStore = cryptoKeyStore;
+ this.cryptoKeyBucket = bucket;
+ if (minimumAge.HasValue) {
+ this.minimumAge = minimumAge.Value;
}
-
- this.symmetricSecret = symmetricSecret;
}
/// <summary>
@@ -154,12 +162,13 @@ namespace DotNetOpenAuth.Messaging {
encoded = MessagingUtilities.Compress(encoded);
}
+ string symmetricSecretHandle = null;
if (this.encrypted) {
- encoded = this.Encrypt(encoded);
+ encoded = this.Encrypt(encoded, out symmetricSecretHandle);
}
if (this.signed) {
- message.Signature = this.CalculateSignature(encoded);
+ message.Signature = this.CalculateSignature(encoded, symmetricSecretHandle);
}
int capacity = this.signed ? 4 + message.Signature.Length + 4 + encoded.Length : encoded.Length;
@@ -172,7 +181,13 @@ namespace DotNetOpenAuth.Messaging {
writer.WriteBuffer(encoded);
writer.Flush();
- return Convert.ToBase64String(finalStream.ToArray());
+ string payload = MessagingUtilities.ConvertToBase64WebSafeString(finalStream.ToArray());
+ string result = payload;
+ if (symmetricSecretHandle != null && (this.signed || this.encrypted)) {
+ result = MessagingUtilities.CombineKeyHandleAndPayload(symmetricSecretHandle, payload);
+ }
+
+ return result;
}
/// <summary>
@@ -182,8 +197,15 @@ namespace DotNetOpenAuth.Messaging {
/// <param name="value">The serialized form of the <see cref="DataBag"/> to deserialize. Must not be null or empty.</param>
/// <returns>The deserialized value. Never null.</returns>
public T Deserialize(IProtocolMessage containingMessage, string value) {
+ string symmetricSecretHandle = null;
+ if (this.encrypted && this.cryptoKeyStore != null) {
+ string valueWithoutHandle;
+ MessagingUtilities.ExtractKeyHandleAndPayload(containingMessage, "<TODO>", value, out symmetricSecretHandle, out valueWithoutHandle);
+ value = valueWithoutHandle;
+ }
+
var message = new T { ContainingMessage = containingMessage };
- byte[] data = Convert.FromBase64String(value);
+ byte[] data = MessagingUtilities.FromBase64WebSafeString(value);
byte[] signature = null;
if (this.signed) {
@@ -193,11 +215,11 @@ namespace DotNetOpenAuth.Messaging {
data = dataReader.ReadBuffer();
// Verify that the verification code was issued by message authorization server.
- ErrorUtilities.VerifyProtocol(this.IsSignatureValid(data, signature), MessagingStrings.SignatureInvalid);
+ ErrorUtilities.VerifyProtocol(this.IsSignatureValid(data, signature, symmetricSecretHandle), MessagingStrings.SignatureInvalid);
}
if (this.encrypted) {
- data = this.Decrypt(data);
+ data = this.Decrypt(data, symmetricSecretHandle);
}
if (this.compressed) {
@@ -249,17 +271,18 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
/// <param name="signedData">The signed data.</param>
/// <param name="signature">The signature.</param>
+ /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param>
/// <returns>
/// <c>true</c> if the signature is valid; otherwise, <c>false</c>.
/// </returns>
- private bool IsSignatureValid(byte[] signedData, byte[] signature) {
+ private bool IsSignatureValid(byte[] signedData, byte[] signature, string symmetricSecretHandle) {
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));
+ return MessagingUtilities.AreEquivalentConstantTime(signature, this.CalculateSignature(signedData, symmetricSecretHandle));
}
}
@@ -267,18 +290,22 @@ namespace DotNetOpenAuth.Messaging {
/// Calculates the signature for the data in this verification code.
/// </summary>
/// <param name="bytesToSign">The bytes to sign.</param>
+ /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param>
/// <returns>
/// The calculated signature.
/// </returns>
- private byte[] CalculateSignature(byte[] bytesToSign) {
+ private byte[] CalculateSignature(byte[] bytesToSign, string symmetricSecretHandle) {
Contract.Requires<ArgumentNullException>(bytesToSign != null, "bytesToSign");
- Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.symmetricHasher != null);
+ Contract.Requires<InvalidOperationException>(this.asymmetricSigning != null || this.cryptoKeyStore != null);
Contract.Ensures(Contract.Result<byte[]>() != null);
if (this.asymmetricSigning != null) {
return this.asymmetricSigning.SignData(bytesToSign, this.hasherForAsymmetricSigning);
} else {
- return this.symmetricHasher.ComputeHash(bytesToSign);
+ var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle);
+ ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key.");
+ var symmetricHasher = new HMACSHA256(key.Key);
+ return symmetricHasher.ComputeHash(bytesToSign);
}
}
@@ -286,14 +313,20 @@ namespace DotNetOpenAuth.Messaging {
/// Encrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate.
/// </summary>
/// <param name="value">The value.</param>
- /// <returns>The encrypted value.</returns>
- private byte[] Encrypt(byte[] value) {
- Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.symmetricSecret != null);
+ /// <param name="symmetricSecretHandle">Receives the symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param>
+ /// <returns>
+ /// The encrypted value.
+ /// </returns>
+ private byte[] Encrypt(byte[] value, out string symmetricSecretHandle) {
+ Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.cryptoKeyStore != null);
if (this.asymmetricEncrypting != null) {
+ symmetricSecretHandle = null;
return this.asymmetricEncrypting.EncryptWithRandomSymmetricKey(value);
} else {
- return MessagingUtilities.Encrypt(value, this.symmetricSecret);
+ var cryptoKey = this.cryptoKeyStore.GetCurrentKey(this.cryptoKeyBucket, this.minimumAge);
+ symmetricSecretHandle = cryptoKey.Key;
+ return MessagingUtilities.Encrypt(value, cryptoKey.Value.Key);
}
}
@@ -301,14 +334,19 @@ namespace DotNetOpenAuth.Messaging {
/// Decrypts the specified value using either the symmetric or asymmetric encryption algorithm as appropriate.
/// </summary>
/// <param name="value">The value.</param>
- /// <returns>The decrypted value.</returns>
- private byte[] Decrypt(byte[] value) {
- Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || this.symmetricSecret != null);
+ /// <param name="symmetricSecretHandle">The symmetric secret handle. <c>null</c> when using an asymmetric algorithm.</param>
+ /// <returns>
+ /// The decrypted value.
+ /// </returns>
+ private byte[] Decrypt(byte[] value, string symmetricSecretHandle) {
+ Contract.Requires<InvalidOperationException>(this.asymmetricEncrypting != null || symmetricSecretHandle != null);
if (this.asymmetricEncrypting != null) {
return this.asymmetricEncrypting.DecryptWithRandomSymmetricKey(value);
} else {
- return MessagingUtilities.Decrypt(value, this.symmetricSecret);
+ var key = this.cryptoKeyStore.GetKey(this.cryptoKeyBucket, symmetricSecretHandle);
+ ErrorUtilities.VerifyProtocol(key != null, "Missing decryption key.");
+ return MessagingUtilities.Decrypt(value, key.Key);
}
}
}
diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
index b1807c9..8686c2e 100644
--- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
+++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs
@@ -29,20 +29,6 @@ namespace DotNetOpenAuth.Messaging {
/// </summary>
public static class MessagingUtilities {
/// <summary>
- /// The length of private symmetric secret handles.
- /// </summary>
- /// <remarks>
- /// This value needn't be high, as we only expect to have a small handful of unexpired secrets at a time,
- /// and handle recycling is permissible.
- /// </remarks>
- private const int SymmetricSecretHandleLength = 4;
-
- /// <summary>
- /// The default lifetime of a private secret.
- /// </summary>
- private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Configuration.Messaging.PrivateSecretMaximumAge;
-
- /// <summary>
/// The cryptographically strong random data generator used for creating secrets.
/// </summary>
/// <remarks>The random number generator is thread-safe.</remarks>
@@ -91,6 +77,20 @@ namespace DotNetOpenAuth.Messaging {
internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ";
/// <summary>
+ /// The length of private symmetric secret handles.
+ /// </summary>
+ /// <remarks>
+ /// This value needn't be high, as we only expect to have a small handful of unexpired secrets at a time,
+ /// and handle recycling is permissible.
+ /// </remarks>
+ private const int SymmetricSecretHandleLength = 4;
+
+ /// <summary>
+ /// The default lifetime of a private secret.
+ /// </summary>
+ private static readonly TimeSpan SymmetricSecretKeyLifespan = Configuration.DotNetOpenAuthSection.Configuration.Messaging.PrivateSecretMaximumAge;
+
+ /// <summary>
/// A character array containing just the = character.
/// </summary>
private static readonly char[] EqualsArray = new char[] { '=' };
@@ -427,6 +427,40 @@ namespace DotNetOpenAuth.Messaging {
}
/// <summary>
+ /// Encodes a symmetric key handle and the blob that is encrypted/signed with that key into a single string
+ /// that can be decoded by <see cref="ExtractKeyHandleAndPayload"/>.
+ /// </summary>
+ /// <param name="handle">The cryptographic key handle.</param>
+ /// <param name="payload">The encrypted/signed blob.</param>
+ /// <returns>The combined encoded value.</returns>
+ internal static string CombineKeyHandleAndPayload(string handle, string payload) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(handle));
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(payload));
+ Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<string>()));
+
+ return handle + "!" + payload;
+ }
+
+ /// <summary>
+ /// Extracts the key handle and encrypted blob from a string previously returned from <see cref="CombineKeyHandleAndPayload"/>.
+ /// </summary>
+ /// <param name="containingMessage">The containing message.</param>
+ /// <param name="messagePart">The message part.</param>
+ /// <param name="keyHandleAndBlob">The value previously returned from <see cref="CombineKeyHandleAndPayload"/>.</param>
+ /// <param name="handle">The crypto key handle.</param>
+ /// <param name="dataBlob">The encrypted/signed data.</param>
+ internal static void ExtractKeyHandleAndPayload(IProtocolMessage containingMessage, string messagePart, string keyHandleAndBlob, out string handle, out string dataBlob) {
+ Contract.Requires<ArgumentNullException>(containingMessage != null, "containingMessage");
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(messagePart));
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(keyHandleAndBlob));
+
+ int privateHandleIndex = keyHandleAndBlob.IndexOf('!');
+ ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, messagePart, keyHandleAndBlob);
+ handle = keyHandleAndBlob.Substring(0, privateHandleIndex);
+ dataBlob = keyHandleAndBlob.Substring(privateHandleIndex + 1);
+ }
+
+ /// <summary>
/// Gets a buffer of random data (not cryptographically strong).
/// </summary>
/// <param name="length">The length of the sequence to generate.</param>
@@ -779,8 +813,20 @@ namespace DotNetOpenAuth.Messaging {
Contract.Ensures(Contract.Result<byte[]>() != null);
// Restore the padding characters and original URL-unsafe characters.
- int missingPaddingCharacters = 4 - (base64WebSafe.Length % 4);
- ErrorUtilities.VerifyInternal(missingPaddingCharacters <= 2, "No more than two padding characters should be present for base64.");
+ int missingPaddingCharacters;
+ switch (base64WebSafe.Length % 4) {
+ case 3:
+ missingPaddingCharacters = 1;
+ break;
+ case 2:
+ missingPaddingCharacters = 2;
+ break;
+ case 0:
+ missingPaddingCharacters = 0;
+ break;
+ default:
+ throw ErrorUtilities.ThrowInternal("No more than two padding characters should be present for base64.");
+ }
var builder = new StringBuilder(base64WebSafe, base64WebSafe.Length + missingPaddingCharacters);
builder.Replace('-', '+').Replace('_', '/');
builder.Append('=', missingPaddingCharacters);
diff --git a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs
index b435f1b..8c66128 100644
--- a/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs
+++ b/src/DotNetOpenAuth/Messaging/UriStyleMessageFormatter.cs
@@ -36,15 +36,17 @@ namespace DotNetOpenAuth.Messaging {
/// <summary>
/// Initializes a new instance of the <see cref="UriStyleMessageFormatter&lt;T&gt;"/> class.
/// </summary>
- /// <param name="symmetricSecret">The symmetric secret to use for signing and encrypting.</param>
+ /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param>
+ /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param>
/// <param name="signed">A value indicating whether the data in this instance will be protected against tampering.</param>
/// <param name="encrypted">A value indicating whether the data in this instance will be protected against eavesdropping.</param>
/// <param name="compressed">A value indicating whether the data in this instance will be GZip'd.</param>
+ /// <param name="minimumAge">The minimum age.</param>
/// <param name="maximumAge">The maximum age of a token that can be decoded; useful only when <paramref name="decodeOnceOnly"/> is <c>true</c>.</param>
/// <param name="decodeOnceOnly">The nonce store to use to ensure that this instance is only decoded once.</param>
- protected internal UriStyleMessageFormatter(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.");
+ protected internal UriStyleMessageFormatter(ICryptoKeyStore cryptoKeyStore = null, string bucket = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? minimumAge = null, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
+ : base(cryptoKeyStore, bucket, signed, encrypted, compressed, minimumAge, maximumAge, decodeOnceOnly) {
+ Contract.Requires<ArgumentException>((cryptoKeyStore != null && !String.IsNullOrEmpty(bucket)) || (!signed && !encrypted), "A secret is required when signing or encrypting is required.");
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs
index a48c95a..82334ef 100644
--- a/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs
+++ b/src/DotNetOpenAuth/OAuth2/AuthorizationServer.cs
@@ -233,7 +233,7 @@ namespace DotNetOpenAuth.OAuth2 {
response.Scope.ResetContents(tokenRequest.AuthorizationDescription.Scope);
if (includeRefreshToken) {
- var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.Secret);
+ var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServerServices.CryptoKeyStore);
var refreshToken = new RefreshToken(tokenRequest.AuthorizationDescription);
response.RefreshToken = refreshTokenFormatter.Serialize(refreshToken);
}
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs
index 2404963..b772c0e 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AccessRequestBindingElement.cs
@@ -55,8 +55,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
public override MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
var response = message as ITokenCarryingRequest;
if (response != null) {
- switch (response.CodeOrTokenType)
- {
+ switch (response.CodeOrTokenType) {
case CodeOrTokenType.AuthorizationCode:
var codeFormatter = AuthorizationCode.CreateFormatter(this.AuthorizationServer);
var code = (AuthorizationCode)response.AuthorizationDescription;
@@ -70,8 +69,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
}
var accessTokenResponse = message as AccessTokenSuccessResponse;
- if (accessTokenResponse != null)
- {
+ if (accessTokenResponse != null) {
var directResponseMessage = (IDirectResponseProtocolMessage)accessTokenResponse;
var accessTokenRequest = (AccessTokenRequestBase)directResponseMessage.OriginatingRequest;
ErrorUtilities.VerifyProtocol(accessTokenRequest.GrantType != GrantType.None || accessTokenResponse.RefreshToken == null, OAuthStrings.NoGrantNoRefreshToken);
@@ -108,7 +106,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
tokenRequest.AuthorizationDescription = verificationCode;
break;
case CodeOrTokenType.RefreshToken:
- var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.Secret);
+ var refreshTokenFormatter = RefreshToken.CreateFormatter(this.AuthorizationServer.CryptoKeyStore);
var refreshToken = refreshTokenFormatter.Deserialize(message, tokenRequest.CodeOrToken);
tokenRequest.AuthorizationDescription = refreshToken;
break;
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs
index 76867a9..6067541 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/AuthorizationCode.cs
@@ -18,6 +18,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// </summary>
internal class AuthorizationCode : AuthorizationDataBag {
/// <summary>
+ /// The name of the bucket for symmetric keys used to sign authorization codes.
+ /// </summary>
+ internal const string AuthorizationCodeKeyBucket = "https://localhost/dnoa/oauth_authorization_code";
+
+ /// <summary>
/// The hash algorithm used on the callback URI.
/// </summary>
private readonly HashAlgorithm hasher = new SHA256Managed();
@@ -61,12 +66,13 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
Contract.Ensures(Contract.Result<IDataBagFormatter<AuthorizationCode>>() != null);
return new UriStyleMessageFormatter<AuthorizationCode>(
- authorizationServer.Secret,
- true,
- true,
- false,
- AuthorizationCodeBindingElement.MaximumMessageAge,
- authorizationServer.VerificationCodeNonceStore);
+ authorizationServer.CryptoKeyStore,
+ AuthorizationCodeKeyBucket,
+ signed: true,
+ encrypted: true,
+ compressed: false,
+ maximumAge: AuthorizationCodeBindingElement.MaximumMessageAge,
+ decodeOnceOnly: authorizationServer.VerificationCodeNonceStore);
}
/// <summary>
diff --git a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs
index 8feb3fb..4662719 100644
--- a/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs
+++ b/src/DotNetOpenAuth/OAuth2/ChannelElements/RefreshToken.cs
@@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
using System;
using System.Diagnostics.Contracts;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
/// <summary>
/// The refresh token issued to a client by an authorization server that allows the client
@@ -15,6 +16,11 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// </summary>
internal class RefreshToken : AuthorizationDataBag {
/// <summary>
+ /// The name of the bucket for symmetric keys used to sign refresh tokens.
+ /// </summary>
+ internal const string RefreshTokenKeyBucket = "https://localhost/dnoa/oauth_refresh_token";
+
+ /// <summary>
/// Initializes a new instance of the <see cref="RefreshToken"/> class.
/// </summary>
public RefreshToken() {
@@ -36,14 +42,15 @@ namespace DotNetOpenAuth.OAuth2.ChannelElements {
/// <summary>
/// Creates a formatter capable of serializing/deserializing a refresh token.
/// </summary>
- /// <param name="symmetricSecret">The symmetric secret used by the authorization server to sign/encrypt refresh tokens. Must not be null.</param>
- /// <returns>A DataBag formatter. Never null.</returns>
- internal static IDataBagFormatter<RefreshToken> CreateFormatter(byte[] symmetricSecret)
- {
- Contract.Requires<ArgumentNullException>(symmetricSecret != null, "symmetricSecret");
+ /// <param name="cryptoKeyStore">The crypto key store.</param>
+ /// <returns>
+ /// A DataBag formatter. Never null.
+ /// </returns>
+ internal static IDataBagFormatter<RefreshToken> CreateFormatter(ICryptoKeyStore cryptoKeyStore) {
+ Contract.Requires<ArgumentNullException>(cryptoKeyStore != null, "cryptoKeyStore");
Contract.Ensures(Contract.Result<IDataBagFormatter<RefreshToken>>() != null);
- return new UriStyleMessageFormatter<RefreshToken>(symmetricSecret, true, true);
+ return new UriStyleMessageFormatter<RefreshToken>(cryptoKeyStore, RefreshTokenKeyBucket, signed: true, encrypted: true);
}
}
}
diff --git a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs
index 9a62277..d35373b 100644
--- a/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs
+++ b/src/DotNetOpenAuth/OAuth2/IAuthorizationServer.cs
@@ -20,14 +20,14 @@ namespace DotNetOpenAuth.OAuth2 {
[ContractClass(typeof(IAuthorizationServerContract))]
public interface IAuthorizationServer {
/// <summary>
- /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens.
+ /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens.
/// </summary>
/// <remarks>
- /// This secret should be kept strictly confidential in the authorization server(s)
- /// and NOT shared with the resource server. Anyone with this secret can mint
+ /// This store should be kept strictly confidential in the authorization server(s)
+ /// and NOT shared with the resource server. Anyone with these secrets can mint
/// tokens to essentially grant themselves access to anything they want.
/// </remarks>
- byte[] Secret { get; }
+ ICryptoKeyStore CryptoKeyStore { get; }
/// <summary>
/// Gets the authorization code nonce store to use to ensure that authorization codes can only be used once.
@@ -92,17 +92,11 @@ namespace DotNetOpenAuth.OAuth2 {
}
/// <summary>
- /// Gets the secret used to symmetrically encrypt and sign authorization codes and refresh tokens.
+ /// Gets the store for storeing crypto keys used to symmetrically encrypt and sign authorization codes and refresh tokens.
/// </summary>
- /// <value></value>
- /// <remarks>
- /// This secret should be kept strictly confidential in the authorization server(s)
- /// and NOT shared with the resource server. Anyone with this secret can mint
- /// tokens to essentially grant themselves access to anything they want.
- /// </remarks>
- byte[] IAuthorizationServer.Secret {
+ ICryptoKeyStore IAuthorizationServer.CryptoKeyStore {
get {
- Contract.Ensures(Contract.Result<byte[]>() != null);
+ Contract.Ensures(Contract.Result<ICryptoKeyStore>() != null);
throw new NotImplementedException();
}
}
diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs
index 702e947..2e95436 100644
--- a/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs
+++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ReturnToSignatureBindingElement.cs
@@ -164,7 +164,10 @@ namespace DotNetOpenAuth.OpenId.ChannelElements {
/// Gets the return to signature.
/// </summary>
/// <param name="returnTo">The return to.</param>
- /// <returns>The generated signature.</returns>
+ /// <param name="cryptoKey">The crypto key.</param>
+ /// <returns>
+ /// The generated signature.
+ /// </returns>
/// <remarks>
/// Only the parameters in the return_to URI are signed, rather than the base URI
/// itself, in order that OPs that might change the return_to's implicit port :80 part
diff --git a/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs
index 3caf05e..72949d9 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/AssociationDataBag.cs
@@ -7,10 +7,12 @@
namespace DotNetOpenAuth.OpenId.Provider {
using System;
using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text;
using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
/// <summary>
/// A signed and encrypted serialization of an association.
@@ -77,10 +79,17 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// <summary>
/// Creates the formatter used for serialization of this type.
/// </summary>
- /// <param name="symmetricSecret">The OpenID Provider's private symmetric secret to use to encrypt and sign the association data.</param>
- /// <returns>A formatter for serialization.</returns>
- internal static IDataBagFormatter<AssociationDataBag> CreateFormatter(byte[] symmetricSecret) {
- return new BinaryDataBagFormatter<AssociationDataBag>(symmetricSecret, signed: true, encrypted: true);
+ /// <param name="cryptoKeyStore">The crypto key store used when signing or encrypting.</param>
+ /// <param name="bucket">The bucket in which symmetric keys are stored for signing/encrypting data.</param>
+ /// <param name="minimumAge">The minimum age.</param>
+ /// <returns>
+ /// A formatter for serialization.
+ /// </returns>
+ internal static IDataBagFormatter<AssociationDataBag> CreateFormatter(ICryptoKeyStore cryptoKeyStore, string bucket, TimeSpan? minimumAge = null) {
+ Contract.Requires<ArgumentNullException>(cryptoKeyStore != null, "cryptoKeyStore");
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(bucket));
+ Contract.Ensures(Contract.Result<IDataBagFormatter<AssociationDataBag>>() != null);
+ return new BinaryDataBagFormatter<AssociationDataBag>(cryptoKeyStore, bucket, signed: true, encrypted: true, minimumAge: minimumAge);
}
}
}
diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
index e8c8881..4a52728 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs
@@ -74,6 +74,7 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// Initializes a new instance of the <see cref="OpenIdProvider"/> class.
/// </summary>
/// <param name="nonceStore">The nonce store to use. Cannot be null.</param>
+ /// <param name="cryptoKeyStore">The crypto key store. Cannot be null.</param>
private OpenIdProvider(INonceStore nonceStore, ICryptoKeyStore cryptoKeyStore) {
Contract.Requires<ArgumentNullException>(nonceStore != null, "nonceStore");
Contract.Requires<ArgumentNullException>(cryptoKeyStore != null, "associationStore");
diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs
index 358daf4..73d8b56 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderAssociationHandleEncoder.cs
@@ -17,13 +17,20 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// details in the handle.
/// </summary>
public class ProviderAssociationHandleEncoder : IProviderAssociationStore {
+ /// <summary>
+ /// The name of the bucket in which to store keys that encrypt association data into association handles.
+ /// </summary>
internal const string AssociationHandleEncodingSecretBucket = "https://localhost/dnoa/association_handles";
+ /// <summary>
+ /// The crypto key store used to persist encryption keys.
+ /// </summary>
private readonly ICryptoKeyStore cryptoKeyStore;
/// <summary>
/// Initializes a new instance of the <see cref="ProviderAssociationHandleEncoder"/> class.
/// </summary>
+ /// <param name="cryptoKeyStore">The crypto key store.</param>
public ProviderAssociationHandleEncoder(ICryptoKeyStore cryptoKeyStore) {
Contract.Requires<ArgumentNullException>(cryptoKeyStore != null, "cryptoKeyStore");
this.cryptoKeyStore = cryptoKeyStore;
@@ -45,9 +52,8 @@ namespace DotNetOpenAuth.OpenId.Provider {
ExpiresUtc = expiresUtc,
};
- var encodingSecret = this.cryptoKeyStore.GetCurrentKey(AssociationHandleEncodingSecretBucket, expiresUtc - DateTime.UtcNow);
- var formatter = AssociationDataBag.CreateFormatter(encodingSecret.Value.Key);
- return encodingSecret.Key + "!" + formatter.Serialize(associationDataBag);
+ var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket, expiresUtc - DateTime.UtcNow);
+ return formatter.Serialize(associationDataBag);
}
/// <summary>
@@ -61,21 +67,12 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// </returns>
/// <exception cref="ProtocolException">Thrown if the association is not of the expected type.</exception>
public Association Deserialize(IProtocolMessage containingMessage, bool isPrivateAssociation, string handle) {
- int privateHandleIndex = handle.IndexOf('!');
- ErrorUtilities.VerifyProtocol(privateHandleIndex > 0, MessagingStrings.UnexpectedMessagePartValue, containingMessage.GetProtocol().openid.assoc_handle, handle);
- string privateHandle = handle.Substring(0, privateHandleIndex);
- string encodedHandle = handle.Substring(privateHandleIndex + 1);
- var encodingSecret = this.cryptoKeyStore.GetKey(AssociationHandleEncodingSecretBucket, privateHandle);
- if (encodingSecret == null) {
- Logger.OpenId.Error("Rejecting an association because the symmetric secret it was encoded with is missing or has expired.");
- return null;
- }
-
- var formatter = AssociationDataBag.CreateFormatter(encodingSecret.Key);
+ var formatter = AssociationDataBag.CreateFormatter(this.cryptoKeyStore, AssociationHandleEncodingSecretBucket);
AssociationDataBag bag;
try {
- bag = formatter.Deserialize(containingMessage, encodedHandle);
- } catch (ProtocolException) {
+ bag = formatter.Deserialize(containingMessage, handle);
+ } catch (ProtocolException ex) {
+ Logger.OpenId.Error("Rejecting an association because deserialization of the encoded handle failed.", ex);
return null;
}
diff --git a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs
index 265b555..c13c4bc 100644
--- a/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs
+++ b/src/DotNetOpenAuth/OpenId/Provider/StandardProviderApplicationStore.cs
@@ -6,6 +6,7 @@
namespace DotNetOpenAuth.OpenId.Provider {
using System;
+ using System.Collections.Generic;
using DotNetOpenAuth.Configuration;
using DotNetOpenAuth.Messaging.Bindings;
@@ -27,6 +28,9 @@ namespace DotNetOpenAuth.OpenId.Provider {
/// </summary>
private readonly INonceStore nonceStore;
+ /// <summary>
+ /// The crypto key store where symmetric keys are persisted.
+ /// </summary>
private readonly ICryptoKeyStore cryptoKeyStore;
/// <summary>
@@ -65,18 +69,45 @@ namespace DotNetOpenAuth.OpenId.Provider {
#region ICryptoKeyStore
+ /// <summary>
+ /// Gets the key in a given bucket and handle.
+ /// </summary>
+ /// <param name="bucket">The bucket name. Case sensitive.</param>
+ /// <param name="handle">The key handle. Case sensitive.</param>
+ /// <returns>
+ /// The cryptographic key, or <c>null</c> if no matching key was found.
+ /// </returns>
public CryptoKey GetKey(string bucket, string handle) {
return this.cryptoKeyStore.GetKey(bucket, handle);
}
- public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
+ /// <summary>
+ /// Gets a sequence of existing keys within a given bucket.
+ /// </summary>
+ /// <param name="bucket">The bucket name. Case sensitive.</param>
+ /// <returns>
+ /// A sequence of handles and keys, ordered by descending <see cref="CryptoKey.ExpiresUtc"/>.
+ /// </returns>
+ public IEnumerable<KeyValuePair<string, CryptoKey>> GetKeys(string bucket) {
return this.cryptoKeyStore.GetKeys(bucket);
}
+ /// <summary>
+ /// Stores a cryptographic key.
+ /// </summary>
+ /// <param name="bucket">The name of the bucket to store the key in. Case sensitive.</param>
+ /// <param name="handle">The handle to the key, unique within the bucket. Case sensitive.</param>
+ /// <param name="key">The key to store.</param>
+ /// <exception cref="CryptoKeyCollisionException">Thrown in the event of a conflict with an existing key in the same bucket and with the same handle.</exception>
public void StoreKey(string bucket, string handle, CryptoKey key) {
this.cryptoKeyStore.StoreKey(bucket, handle, key);
}
+ /// <summary>
+ /// Removes the key.
+ /// </summary>
+ /// <param name="bucket">The bucket name. Case sensitive.</param>
+ /// <param name="handle">The key handle. Case sensitive.</param>
public void RemoveKey(string bucket, string handle) {
this.cryptoKeyStore.RemoveKey(bucket, handle);
}