summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2010-06-01 21:58:28 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2010-06-01 21:58:28 -0700
commitf27fb6698ac61d5ce023e52fd902dbb09d643b06 (patch)
tree07dd87c078bbbce1caf45af48633663e516ad0b3
parent63ea38240513d1c72b83d9df1c5e313bacf0dd21 (diff)
downloadDotNetOpenAuth-f27fb6698ac61d5ce023e52fd902dbb09d643b06.zip
DotNetOpenAuth-f27fb6698ac61d5ce023e52fd902dbb09d643b06.tar.gz
DotNetOpenAuth-f27fb6698ac61d5ce023e52fd902dbb09d643b06.tar.bz2
Added capability to use asymmetric signing for the access token so that resource servers don't have the ability to mint access tokens.
But resource servers can still mint verification codes and refresh tokens since they are signed using the shared secret, so that needs to be fixed.
-rw-r--r--samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs12
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs2
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs13
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs5
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs41
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs7
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs2
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs2
-rw-r--r--src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs7
9 files changed, 74 insertions, 17 deletions
diff --git a/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs b/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs
index 6a36a83..695ba74 100644
--- a/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs
+++ b/samples/OAuthServiceProvider/Code/OAuth2AuthorizationServer.cs
@@ -13,13 +13,17 @@
internal class OAuth2AuthorizationServer : IAuthorizationServer {
private static readonly byte[] secret;
+ private static readonly RSAParameters asymmetricKey;
+
private readonly INonceStore nonceStore = new DatabaseNonceStore();
- static OAuth2AuthorizationServer()
- {
+ static OAuth2AuthorizationServer() {
+ // For this sample, we just generate random secrets.
RandomNumberGenerator crypto = new RNGCryptoServiceProvider();
secret = new byte[16];
crypto.GetBytes(secret);
+
+ asymmetricKey = new RSACryptoServiceProvider().ExportParameters(true);
}
#region Implementation of IAuthorizationServer
@@ -32,6 +36,10 @@
get { return this.nonceStore; }
}
+ public RSAParameters? AccessTokenSigningPrivateKey {
+ get { return asymmetricKey; }
+ }
+
public IConsumerDescription GetClient(string clientIdentifier) {
var consumerRow = Global.DataContext.OAuthConsumers.SingleOrDefault(
consumerCandidate => consumerCandidate.ConsumerKey == clientIdentifier);
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs
index d102ee6..4fa33ae 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessRequestBindingElement.cs
@@ -49,7 +49,7 @@
tokenRequest.AuthorizationDescription = RefreshToken.Decode(this.AuthorizationServer.Secret, tokenRequest.CodeOrToken, message);
break;
case CodeOrTokenType.AccessToken:
- tokenRequest.AuthorizationDescription = AccessToken.Decode(this.AuthorizationServer.Secret, tokenRequest.CodeOrToken, message);
+ tokenRequest.AuthorizationDescription = AccessToken.Decode(this.AuthorizationServer.Secret, tokenRequest.CodeOrToken, this.AuthorizationServer.AccessTokenSigningPrivateKey, message);
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 3d3ad44..d9a1149 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AccessToken.cs
@@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Security.Cryptography;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
@@ -18,13 +19,13 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
/// Initializes a new instance of the <see cref="AccessToken"/> class.
/// </summary>
/// <param name="channel">The channel.</param>
- private AccessToken(byte[] secret)
- : base(secret, true, true) {
+ private AccessToken(byte[] secret, RSAParameters? asymmetricSignatureKey = null)
+ : base(secret, asymmetricSignatureKey, true, true) {
Contract.Requires<ArgumentNullException>(secret != null, "channel");
}
- internal AccessToken(byte[] secret, IAuthorizationDescription authorization, TimeSpan? lifetime)
- : this(secret) {
+ internal AccessToken(byte[] secret, IAuthorizationDescription authorization, TimeSpan? lifetime, RSAParameters? asymmetricSignatureKey = null)
+ : this(secret, asymmetricSignatureKey) {
Contract.Requires<ArgumentNullException>(secret != null, "secret");
Contract.Requires<ArgumentNullException>(authorization != null, "authorization");
@@ -37,12 +38,12 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
internal TimeSpan? Lifetime { get; set; }
- internal static AccessToken Decode(byte[] secret, string value, IProtocolMessage containingMessage = null) {
+ internal static AccessToken Decode(byte[] secret, string value, RSAParameters? asymmetricSignatureKey = null, IProtocolMessage containingMessage = null) {
Contract.Requires<ArgumentNullException>(secret != null, "secret");
Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(value));
Contract.Ensures(Contract.Result<AccessToken>() != null);
- var self = new AccessToken(secret);
+ var self = new AccessToken(secret, asymmetricSignatureKey);
self.Decode(value, containingMessage);
return self;
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs
index 0c14e51..6c2707a 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/AuthorizationDataBag.cs
@@ -9,13 +9,14 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Security.Cryptography;
using System.Text;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.Messaging.Bindings;
internal abstract class AuthorizationDataBag : DataBag, IAuthorizationDescription {
- 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) {
+ protected AuthorizationDataBag(byte[] secret, RSAParameters? asymmetricSignatureKey = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
+ : base(secret, asymmetricSignatureKey, signed, encrypted, compressed, maximumAge, decodeOnceOnly) {
}
[MessagePart]
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs
index eb7e069..72dbf79 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/DataBag.cs
@@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
using System.Diagnostics.Contracts;
using System.Linq;
using System.Security.Cryptography;
+ using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;
using DotNetOpenAuth.Messaging;
@@ -24,6 +25,10 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
private readonly byte[] secret;
+ private readonly RSACryptoServiceProvider asymmetricSigning;
+
+ private readonly HashAlgorithm hasherForAsymmetricSigning;
+
private readonly bool signed;
private readonly INonceStore decodeOnceOnly;
@@ -34,12 +39,18 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
private readonly bool compressed;
- protected DataBag(byte[] secret = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
+ protected DataBag(byte[] secret = null, RSAParameters? asymmetricSignatureKey = null, bool signed = false, bool encrypted = false, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
: base(Protocol.Default.Version) {
Contract.Requires<ArgumentException>(secret != null || (signed == null && encrypted == null), "A secret is required when signing or encrypting is required.");
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.");
+ if (asymmetricSignatureKey.HasValue) {
+ this.asymmetricSigning = new RSACryptoServiceProvider();
+ this.asymmetricSigning.ImportParameters(asymmetricSignatureKey.Value);
+ this.hasherForAsymmetricSigning = new SHA1CryptoServiceProvider();
+ }
+
if (secret != null) {
this.Hasher = new HMACSHA256(secret);
}
@@ -120,7 +131,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
if (signed) {
// Verify that the verification code was issued by this authorization server.
- ErrorUtilities.VerifyProtocol(string.Equals(this.Signature, this.CalculateSignature(), StringComparison.Ordinal), Protocol.bad_verification_code);
+ ErrorUtilities.VerifyProtocol(this.IsSignatureValid(), Protocol.bad_verification_code);
}
if (maximumAge.HasValue) {
@@ -146,6 +157,16 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
get { return this.GetType().Name; }
}
+ private bool IsSignatureValid() {
+ if (this.asymmetricSigning != null) {
+ byte[] bytesToSign = this.GetBytesToSign();
+ byte[] signature = Convert.FromBase64String(this.Signature);
+ return this.asymmetricSigning.VerifyData(bytesToSign, this.hasherForAsymmetricSigning, signature);
+ } else {
+ return string.Equals(this.Signature, this.CalculateSignature(), StringComparison.Ordinal);
+ }
+ }
+
/// <summary>
/// Calculates the signature for the data in this verification code.
/// </summary>
@@ -153,11 +174,25 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
private string CalculateSignature() {
Contract.Requires<InvalidOperationException>(this.Hasher != 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));
+ }
+ }
+
+ 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");
- return this.Hasher.ComputeHash(fieldsCopy);
+
+ var sortedData = new SortedDictionary<string, string>(fieldsCopy, StringComparer.OrdinalIgnoreCase);
+ string value = MessagingUtilities.CreateQueryString(sortedData);
+ byte[] bytesToSign = Encoding.UTF8.GetBytes(value);
+ return bytesToSign;
}
}
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs
index bcb6c05..4430d51 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/OAuthWrapAuthorizationServerChannel.cs
@@ -69,7 +69,12 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
public virtual AccessTokenSuccessResponse PrepareAccessToken(IAccessTokenRequest request, TimeSpan? accessTokenLifetime = null, bool includeRefreshToken = true) {
Contract.Requires<ArgumentNullException>(request != null, "request");
- var accessToken = new AccessToken(this.AuthorizationServer.Secret, request.AuthorizationDescription, accessTokenLifetime);
+ var accessToken = new AccessToken(
+ this.AuthorizationServer.Secret,
+ request.AuthorizationDescription,
+ accessTokenLifetime,
+ this.AuthorizationServer.AccessTokenSigningPrivateKey);
+
var response = new AccessTokenSuccessResponse(request) {
Scope = request.AuthorizationDescription.Scope,
AccessToken = accessToken.Encode(),
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs
index d813453..b988bf3 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/RefreshToken.cs
@@ -18,7 +18,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
/// </summary>
/// <param name="channel">The channel.</param>
private RefreshToken(byte[] secret)
- : base(secret, true, true) {
+ : base(secret, null, true, true) {
Contract.Requires<ArgumentNullException>(secret != null, "secret");
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs
index 17f7948..ef71934 100644
--- a/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/ChannelElements/VerificationCode.cs
@@ -37,7 +37,7 @@ namespace DotNetOpenAuth.OAuthWrap.ChannelElements {
/// </summary>
/// <param name="channel">The channel.</param>
private VerificationCode(byte[] secret, INonceStore nonceStore)
- : base(secret, true, true, false, MaximumMessageAge, nonceStore) {
+ : base(secret, null, true, true, false, MaximumMessageAge, nonceStore) {
Contract.Requires<ArgumentNullException>(secret != null, "secret");
Contract.Requires<ArgumentNullException>(nonceStore != null, "nonceStore");
}
diff --git a/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs b/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs
index 8d5bea4..83fbf6f 100644
--- a/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs
+++ b/src/DotNetOpenAuth/OAuthWrap/IAuthorizationServer.cs
@@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OAuthWrap {
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
+ using System.Security.Cryptography;
using System.Text;
using DotNetOpenAuth.OAuth.ChannelElements;
using DotNetOpenAuth.OAuthWrap.ChannelElements;
@@ -51,6 +52,8 @@ namespace DotNetOpenAuth.OAuthWrap {
byte[] Secret { get; }
+ RSAParameters? AccessTokenSigningPrivateKey { get; }
+
INonceStore VerificationCodeNonceStore { get; }
}
@@ -72,6 +75,10 @@ namespace DotNetOpenAuth.OAuthWrap {
}
}
+ RSAParameters? IAuthorizationServer.AccessTokenSigningPrivateKey {
+ get { throw new NotImplementedException(); }
+ }
+
INonceStore IAuthorizationServer.VerificationCodeNonceStore {
get {
Contract.Ensures(Contract.Result<INonceStore>() != null);