diff options
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); |