summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth2/OAuth2
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-04-28 19:56:30 -0700
committerAndrew Arnott <andrewarnott@gmail.com>2012-04-28 20:01:00 -0700
commitea4325d172d8a2bc925ed362f1c35560b8c1f13e (patch)
treea04960f4cddd7f2223df83b03e89300c5051434c /src/DotNetOpenAuth.OAuth2/OAuth2
parent01d8c73f818d30b20f86630d35d230b5168215d1 (diff)
downloadDotNetOpenAuth-origin/jwt.zip
DotNetOpenAuth-origin/jwt.tar.gz
DotNetOpenAuth-origin/jwt.tar.bz2
Work toward support JWT access tokens.origin/jwt
Diffstat (limited to 'src/DotNetOpenAuth.OAuth2/OAuth2')
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JsonWebAlgorithms.cs141
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweHeader.cs74
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweRsaEncryptionAlgorithm.cs29
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwsHeader.cs36
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtEncryptionAlgorithm.cs41
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHeader.cs16
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHmacShaSigningAlgorithm.cs84
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtMessageBase.cs26
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtRsaShaSigningAlgorithm.cs82
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtSigningAlgorithm.cs33
-rw-r--r--src/DotNetOpenAuth.OAuth2/OAuth2/JsonWebTokenFormatter.cs217
11 files changed, 779 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JsonWebAlgorithms.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JsonWebAlgorithms.cs
new file mode 100644
index 0000000..fea18ba
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JsonWebAlgorithms.cs
@@ -0,0 +1,141 @@
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ internal static class JsonWebSignatureAlgorithms {
+ /// <summary>
+ /// HMAC using SHA-256 hash algorithm.
+ /// </summary>
+ internal const string HmacSha256 = "HS256";
+
+ /// <summary>
+ /// HMAC using SHA-384 hash algorithm.
+ /// </summary>
+ internal const string HmacSha384 = "HS384";
+
+ /// <summary>
+ /// HMAC using SHA-512 hash algorithm.
+ /// </summary>
+ internal const string HmacSha512 = "HS512";
+
+ /// <summary>
+ /// RSA using SHA-256 hash algorithm.
+ /// </summary>
+ internal const string RsaSha256 = "RS256";
+
+ /// <summary>
+ /// RSA using SHA-384 hash algorithm.
+ /// </summary>
+ internal const string RsaSha384 = "RS384";
+
+ /// <summary>
+ /// RSA using SHA-512 hash algorithm.
+ /// </summary>
+ internal const string RsaSha512 = "RS512";
+
+ /// <summary>
+ /// ECDSA using P-256 curve and SHA-256 hash algorithm.
+ /// </summary>
+ internal const string ECDsaSha256 = "ES256";
+
+ /// <summary>
+ /// ECDSA using P-384 curve and SHA-384 hash algorithm.
+ /// </summary>
+ internal const string ECDsaSha384 = "ES384";
+
+ /// <summary>
+ /// ECDSA using P-521 curve and SHA-512 hash algorithm.
+ /// </summary>
+ internal const string ECDsaSha512 = "ES512";
+
+ /// <summary>
+ /// No digital signature or HMAC value included.
+ /// </summary>
+ internal const string None = "none";
+ }
+
+ /// <summary>
+ /// The set of alg (algorithm) header parameter values that are defined by this
+ /// specification for use with JWE. These algorithms are used to encrypt the CEK,
+ /// which produces the JWE Encrypted Key.
+ /// </summary>
+ /// <remarks>
+ /// http://self-issued.info/docs/draft-ietf-jose-json-web-algorithms-01.html#EncAlgTable
+ /// </remarks>
+ internal static class JsonWebEncryptionAlgorithms {
+ /// <summary>
+ /// RSA using RSA-PKCS1-1.5 padding, as defined in RFC 3447 [RFC3447]
+ /// </summary>
+ internal const string RSA1_5 = "RSA1_5";
+
+ /// <summary>
+ /// RSA using Optimal Asymmetric Encryption Padding (OAEP), as defined in RFC 3447 [RFC3447]
+ /// </summary>
+ internal const string RSA_OAEP = "RSA-OAEP";
+
+ /// <summary>
+ /// Elliptic Curve Diffie-Hellman Ephemeral Static, as defined in RFC 6090
+ /// [RFC6090], and using the Concat KDF, as defined in [NIST‑800‑56A],
+ /// where the Digest Method is SHA-256 and all OtherInfo parameters are
+ /// the empty bit string
+ /// </summary>
+ internal const string ECDH_ES = "ECDH-ES";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) Key Wrap Algorithm using 128 bit keys,
+ /// as defined in RFC 3394 [RFC3394]
+ /// </summary>
+ internal const string A128KW = "A128KW";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) Key Wrap Algorithm using 256 bit keys,
+ /// as defined in RFC 3394 [RFC3394]
+ /// </summary>
+ internal const string A256KW = "A256KW";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) Key Wrap Algorithm using 512 bit keys,
+ /// as defined in RFC 3394 [RFC3394]
+ /// </summary>
+ internal const string A512KW = "A512KW";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) using 128 bit keys in Galois/Counter
+ /// Mode, as defined in [FIPS‑197] and [NIST‑800‑38D]
+ /// </summary>
+ internal const string A128GCM = "A128GCM";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) using 256 bit keys in Galois/Counter
+ /// Mode, as defined in [FIPS‑197] and [NIST‑800‑38D]
+ /// </summary>
+ internal const string A256GCM = "A256GCM";
+ }
+
+ /// <summary>
+ /// The set of enc (encryption method) header parameter values that are defined
+ /// by this specification for use with JWE. These algorithms are used to encrypt
+ /// the Plaintext, which produces the Ciphertext.
+ /// </summary>
+ /// <remarks>
+ /// http://self-issued.info/docs/draft-ietf-jose-json-web-algorithms-01.html#EncTable
+ /// </remarks>
+ internal static class JsonWebEncryptionMethods {
+ /// <summary>
+ /// Advanced Encryption Standard (AES) using 128 bit keys in Cipher Block Chaining mode, as defined in [FIPS‑197] and [NIST‑800‑38A]
+ /// </summary>
+ internal const string A128CBC = "A128CBC";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) using 256 bit keys in Cipher Block Chaining mode, as defined in [FIPS‑197] and [NIST‑800‑38A]
+ /// </summary>
+ internal const string A256CBC = "A256CBC";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) using 128 bit keys in Galois/Counter Mode, as defined in [FIPS‑197] and [NIST‑800‑38D]
+ /// </summary>
+ internal const string A128GCM = "A128GCM";
+
+ /// <summary>
+ /// Advanced Encryption Standard (AES) using 256 bit keys in Galois/Counter Mode, as defined in [FIPS‑197] and [NIST‑800‑38D]
+ /// </summary>
+ internal const string A256GCM = "A256GCM";
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweHeader.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweHeader.cs
new file mode 100644
index 0000000..2691da3
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweHeader.cs
@@ -0,0 +1,74 @@
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class JweHeader : JwtHeader {
+ private JweHeader() {
+ }
+
+ internal JweHeader(string algorithm, string encryptionMethod) {
+ Requires.NotNullOrEmpty(algorithm, "algorithm");
+ Requires.NotNullOrEmpty(encryptionMethod, "encryptionMethod");
+ this.Algorithm = algorithm;
+ this.EncryptionMethod = encryptionMethod;
+ }
+
+ /// <summary>
+ /// Gets or sets a value that identifies the cryptographic algorithm used to secure the JWS.
+ /// A list of defined alg values is presented in Section 3, Table 1 of the JSON Web Algorithms (JWA) [JWA]
+ /// specification. The processing of the alg header parameter requires that the value MUST be one that is
+ /// both supported and for which there exists a key for use with that algorithm associated with the party
+ /// that digitally signed or HMACed the content. The alg parameter value is case sensitive.
+ /// This header parameter is REQUIRED.
+ /// </summary>
+ [MessagePart("alg", IsRequired = true, AllowEmpty = false)]
+ internal string Algorithm { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value that identifies the symmetric encryption algorithm used to secure the Ciphertext.
+ /// A list of defined enc values is presented in Section 4, Table 3 of the JSON Web Algorithms (JWA) [JWA]
+ /// specification. The processing of the enc (encryption method) header parameter requires that the value
+ /// MUST be one that is supported. The enc value is case sensitive. This header parameter is REQUIRED.
+ /// </summary>
+ [MessagePart("enc", IsRequired = true, AllowEmpty = false)]
+ internal string EncryptionMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value that identifies the cryptographic algorithm used to safeguard the integrity of the
+ /// Ciphertext and the parameters used to create it. The int parameter uses the same values as the JWS alg
+ /// parameter; a list of defined JWS alg values is presented in Section 3, Table 1 of the JSON Web Algorithms
+ /// (JWA) [JWA] specification. This header parameter is REQUIRED when an AEAD algorithm is not used to encrypt
+ /// the Plaintext and MUST NOT be present when an AEAD algorithm is used.
+ /// </summary>
+ [MessagePart("int")]
+ internal string IntegrityAlgorithm { get; set; }
+
+ /// <summary>
+ /// Gets or sets a hint indicating which specific key owned by the signer should be used to validate the digital signature.
+ /// This allows signers to explicitly signal a change of key to recipients. The interpretation of the contents of the kid
+ /// parameter is unspecified. This header parameter is OPTIONAL.
+ /// </summary>
+ [MessagePart("kid")]
+ internal string KeyIdentity { get; set; }
+
+ /// <summary>
+ /// Gets or sets the initialization Vector (iv) value for algorithms requiring it, represented as a base64url encoded string.
+ /// This header parameter is OPTIONAL.
+ /// </summary>
+ [MessagePart("iv", Encoder = typeof(Base64WebEncoder))]
+ internal byte[] IV { get; set; }
+
+ /// <summary>
+ /// Gets or sets the compression algorithm (zip) applied to the Plaintext before encryption, if any.
+ /// This specification defines the value GZIP to refer to the encoding format produced by the file
+ /// compression program "gzip" (GNU zip) as described in [RFC1952]; this format is a Lempel-Ziv coding
+ /// (LZ77) with a 32 bit CRC. If no zip parameter is present, or its value is none, no compression is
+ /// applied to the Plaintext before encryption. The zip value is case sensitive. This header parameter is OPTIONAL.
+ /// </summary>
+ [MessagePart("zip")]
+ internal string CompressionAlgorithm { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweRsaEncryptionAlgorithm.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweRsaEncryptionAlgorithm.cs
new file mode 100644
index 0000000..0d2159d
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JweRsaEncryptionAlgorithm.cs
@@ -0,0 +1,29 @@
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+
+ internal class JweRsaEncryptionAlgorithm : JwtEncryptionAlgorithm {
+ private readonly RSACryptoServiceProvider recipientPublicKey;
+
+ private readonly bool useOaepPadding;
+
+ internal JweRsaEncryptionAlgorithm(RSACryptoServiceProvider recipientPublicKey, bool useOaepPadding = true)
+ : base(useOaepPadding ? JsonWebEncryptionAlgorithms.RSA_OAEP : JsonWebEncryptionAlgorithms.RSA1_5, JsonWebEncryptionMethods.A256CBC) {
+ Requires.NotNull(recipientPublicKey, "recipientPublicKey");
+ this.recipientPublicKey = recipientPublicKey;
+ this.useOaepPadding = useOaepPadding;
+ }
+
+ internal override void Encrypt(byte[] plainText, out byte[] cipherText, out byte[] integrityValue) {
+ cipherText = this.recipientPublicKey.Encrypt(plainText, this.useOaepPadding);
+ integrityValue = null; // RSA is an AEAD algorithm, so it doesn't need a separate integrity check.
+ }
+
+ internal override byte[] Decrypt(byte[] cipherText, byte[] integrityValue) {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwsHeader.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwsHeader.cs
new file mode 100644
index 0000000..7274de5
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwsHeader.cs
@@ -0,0 +1,36 @@
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class JwsHeader : JwtHeader {
+ private JwsHeader() {
+ }
+
+ internal JwsHeader(string algorithm) {
+ Requires.NotNullOrEmpty(algorithm, "algorithm");
+ this.Algorithm = algorithm;
+ }
+
+ /// <summary>
+ /// Gets or sets a value that identifies the cryptographic algorithm used to secure the JWS.
+ /// A list of defined alg values is presented in Section 3, Table 1 of the JSON Web Algorithms (JWA) [JWA]
+ /// specification. The processing of the alg header parameter requires that the value MUST be one that is
+ /// both supported and for which there exists a key for use with that algorithm associated with the party
+ /// that digitally signed or HMACed the content. The alg parameter value is case sensitive.
+ /// This header parameter is REQUIRED.
+ /// </summary>
+ [MessagePart("alg", IsRequired = true, AllowEmpty = false)]
+ internal string Algorithm { get; set; }
+
+ /// <summary>
+ /// Gets or sets a hint indicating which specific key owned by the signer should be used to validate the digital signature.
+ /// This allows signers to explicitly signal a change of key to recipients. The interpretation of the contents of the kid
+ /// parameter is unspecified. This header parameter is OPTIONAL.
+ /// </summary>
+ [MessagePart("kid")]
+ internal string KeyIdentity { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtEncryptionAlgorithm.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtEncryptionAlgorithm.cs
new file mode 100644
index 0000000..b3ac78b
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtEncryptionAlgorithm.cs
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------
+// <copyright file="JwtEncryptionAlgorithm.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal abstract class JwtEncryptionAlgorithm : IDisposable {
+ protected JwtEncryptionAlgorithm(string algorithmName, string encryptionMethod) {
+ Requires.NotNullOrEmpty(algorithmName, "algorithmName");
+ Requires.NotNullOrEmpty(encryptionMethod, "encryptionMethod");
+ this.Header = new JweHeader(algorithmName, encryptionMethod);
+ }
+
+ internal JweHeader Header { get; private set; }
+
+ internal abstract void Encrypt(byte[] plainText, out byte[] cipherText, out byte[] integrityValue);
+
+ internal abstract byte[] Decrypt(byte[] cipherText, byte[] integrityValue);
+
+ public void Dispose() {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing) {
+ }
+
+ protected void KeyDerivation(byte[] contentMasterKey, out byte[] contentEncryptionKey, out byte[] contentIntegrityKey) {
+ // Implementing this would be manual, or involve P/Invoke I think.
+ // http://msdn.microsoft.com/en-us/library/windows/desktop/aa375393(v=vs.85).aspx
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHeader.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHeader.cs
new file mode 100644
index 0000000..1553946
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHeader.cs
@@ -0,0 +1,16 @@
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class JwtHeader : JwtMessageBase {
+ internal JwtHeader() {
+ this.Type = "JWT";
+ }
+
+ [MessagePart("typ")]
+ internal string Type { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHmacShaSigningAlgorithm.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHmacShaSigningAlgorithm.cs
new file mode 100644
index 0000000..80d836c
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtHmacShaSigningAlgorithm.cs
@@ -0,0 +1,84 @@
+//-----------------------------------------------------------------------
+// <copyright file="JwtHmacShaSigningAlgorithm.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class JwtHmacShaSigningAlgorithm : JwtSigningAlgorithm {
+ private readonly HashAlgorithm algorithm;
+
+ internal enum Algorithm {
+ HmacSha256,
+ HmacSha384,
+ HmacSha512,
+ }
+
+ private JwtHmacShaSigningAlgorithm(string algorithmName, HMAC algorithm)
+ : base(algorithmName) {
+ Requires.NotNull(algorithm, "algorithm");
+ this.algorithm = algorithm;
+ }
+
+ internal static JwtSigningAlgorithm Create(Algorithm algorithm, string keyHandle, byte[] key) {
+ Requires.NotNull(key, "key");
+
+ string webAlgorithmName, cryptoName;
+ switch (algorithm) {
+ case Algorithm.HmacSha256:
+ cryptoName = "HMAC-SHA256";
+ webAlgorithmName = JsonWebSignatureAlgorithms.HmacSha256;
+ break;
+ case Algorithm.HmacSha384:
+ cryptoName = "HMAC-SHA384";
+ webAlgorithmName = JsonWebSignatureAlgorithms.HmacSha384;
+ break;
+ case Algorithm.HmacSha512:
+ cryptoName = "HMAC-SHA512";
+ webAlgorithmName = JsonWebSignatureAlgorithms.HmacSha512;
+ break;
+ default:
+ Requires.InRange(false, "algorithm");
+ throw Assumes.NotReachable();
+ }
+
+ HMAC hmac = null;
+ try {
+ hmac = HMAC.Create(cryptoName);
+ hmac.Key = key;
+ var result = new JwtHmacShaSigningAlgorithm(webAlgorithmName, hmac);
+ result.Header.KeyIdentity = keyHandle;
+ return result;
+ } catch {
+ if (hmac != null) {
+ hmac.Dispose();
+ }
+
+ throw;
+ }
+ }
+
+ internal override byte[] Sign(byte[] securedInput) {
+ return algorithm.ComputeHash(securedInput);
+ }
+
+ internal override bool Verify(byte[] securedInput, byte[] signature) {
+ return MessagingUtilities.AreEquivalentConstantTime(this.Sign(securedInput), signature);
+ }
+
+ protected override void Dispose(bool disposing) {
+ if (disposing) {
+ this.algorithm.Dispose();
+ }
+
+ base.Dispose();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtMessageBase.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtMessageBase.cs
new file mode 100644
index 0000000..04b9655
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtMessageBase.cs
@@ -0,0 +1,26 @@
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class JwtMessageBase : IMessage {
+ private static readonly Version version = new Version(1, 0);
+
+ private readonly Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ public Version Version {
+ get { return version; }
+ }
+
+ public IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ public virtual void EnsureValidMessage() {
+ // The JWT spec mandates that any unexpected data in the JWT header or claims set cause a rejection.
+ ErrorUtilities.VerifyProtocol(this.ExtraData.Count == 0, "Unrecognized data in JWT access token with key '{0}'. Token rejected.", this.ExtraData.First().Key);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtRsaShaSigningAlgorithm.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtRsaShaSigningAlgorithm.cs
new file mode 100644
index 0000000..48c1b60
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtRsaShaSigningAlgorithm.cs
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------
+// <copyright file="JwtRsaShaSigningAlgorithm.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ internal class JwtRsaShaSigningAlgorithm : JwtSigningAlgorithm {
+ private readonly RSACryptoServiceProvider algorithm;
+
+ private readonly HashAlgorithm hashAlgorithm;
+
+ internal enum HashSize {
+ Sha256,
+ Sha384,
+ Sha512,
+ }
+
+ internal JwtRsaShaSigningAlgorithm(string algorithmName, RSACryptoServiceProvider algorithm, HashAlgorithm hashAlgorithm)
+ : base(algorithmName) {
+ Requires.NotNull(algorithm, "algorithm");
+ Requires.NotNull(hashAlgorithm, "hashAlgorithm");
+ this.algorithm = algorithm;
+ this.hashAlgorithm = hashAlgorithm;
+ }
+
+ internal static JwtRsaShaSigningAlgorithm Create(RSACryptoServiceProvider algorithm, HashSize hashSize) {
+ Requires.NotNull(algorithm, "algorithm");
+
+ string webAlgorithmName, cryptoName;
+ switch (hashSize) {
+ case HashSize.Sha256:
+ webAlgorithmName = JsonWebSignatureAlgorithms.RsaSha256;
+ cryptoName = "SHA256";
+ break;
+ case HashSize.Sha384:
+ webAlgorithmName = JsonWebSignatureAlgorithms.RsaSha384;
+ cryptoName = "SHA384";
+ break;
+ case HashSize.Sha512:
+ webAlgorithmName = JsonWebSignatureAlgorithms.RsaSha512;
+ cryptoName = "SHA512";
+ break;
+ default:
+ Requires.InRange(false, "algorithm");
+ throw Assumes.NotReachable();
+ }
+
+ HashAlgorithm hashAlgorithm = HashAlgorithm.Create(cryptoName);
+ try {
+ return new JwtRsaShaSigningAlgorithm(webAlgorithmName, algorithm, hashAlgorithm);
+ } catch {
+ hashAlgorithm.Dispose();
+ throw;
+ }
+ }
+
+ internal override byte[] Sign(byte[] securedInput) {
+ return algorithm.SignData(securedInput, this.hashAlgorithm);
+ }
+
+ internal override bool Verify(byte[] securedInput, byte[] signature) {
+ return algorithm.VerifyData(securedInput, this.hashAlgorithm, signature);
+ }
+
+ protected override void Dispose(bool disposing) {
+ if (disposing) {
+ // We only own the hash algorithm -- not the RSA algorithm.
+ this.hashAlgorithm.Dispose();
+ }
+
+ base.Dispose();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtSigningAlgorithm.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtSigningAlgorithm.cs
new file mode 100644
index 0000000..116c1ca
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/Crypto/JwtSigningAlgorithm.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="JwtSigningAlgorithm.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth2.Crypto {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ internal abstract class JwtSigningAlgorithm : IDisposable {
+ protected JwtSigningAlgorithm(string algorithmName) {
+ Requires.NotNullOrEmpty(algorithmName, "algorithmName");
+ this.Header = new JwsHeader(algorithmName);
+ }
+
+ internal JwsHeader Header { get; private set; }
+
+ internal abstract byte[] Sign(byte[] securedInput);
+
+ internal abstract bool Verify(byte[] securedInput, byte[] signature);
+
+ public void Dispose() {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing) {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth2/OAuth2/JsonWebTokenFormatter.cs b/src/DotNetOpenAuth.OAuth2/OAuth2/JsonWebTokenFormatter.cs
new file mode 100644
index 0000000..8016533
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth2/OAuth2/JsonWebTokenFormatter.cs
@@ -0,0 +1,217 @@
+namespace DotNetOpenAuth.OAuth2 {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Runtime.Serialization;
+ using System.Runtime.Serialization.Json;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OAuth2.ChannelElements;
+ using DotNetOpenAuth.OAuth2.Crypto;
+
+ internal class JsonWebTokenFormatter : DataBagFormatterBase<AccessToken> {
+ private static readonly Encoding JwtCharacterEncoding = Encoding.UTF8;
+
+ private static readonly MessageDescriptionCollection messageDescriptions = new MessageDescriptionCollection();
+
+ private static readonly IMessageFactory jwtHeaderMessageFactory = ConstructJwtHeaderMessageFactory();
+
+ private static IMessageFactory ConstructJwtHeaderMessageFactory() {
+ var factory = new StandardMessageFactory();
+ factory.AddMessageTypes(new MessageDescription[] {
+ messageDescriptions.Get(typeof(JwsHeader), new Version(1, 0)),
+ messageDescriptions.Get(typeof(JweHeader), new Version(1, 0)),
+ });
+
+ return factory;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JsonWebTokenFormatter"/> class.
+ /// </summary>
+ /// <param name="signingKey">The crypto service provider with the asymmetric key to use for signing or verifying the token.</param>
+ /// <param name="encryptingKey">The crypto service provider with the asymmetric key to use for encrypting or decrypting 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 <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 JsonWebTokenFormatter(RSACryptoServiceProvider signingKey = null, RSACryptoServiceProvider encryptingKey = null, bool compressed = false, TimeSpan? maximumAge = null, INonceStore decodeOnceOnly = null)
+ : base(signingKey, encryptingKey, compressed, maximumAge, decodeOnceOnly) {
+ this.UseOaepPadding = true;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JsonWebTokenFormatter"/> class.
+ /// </summary>
+ /// <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 JsonWebTokenFormatter(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) {
+ Requires.True((cryptoKeyStore != null && !string.IsNullOrEmpty(bucket)) || (!signed && !encrypted), null);
+ this.UseOaepPadding = true;
+ }
+
+ internal bool UseOaepPadding { get; set; }
+
+ public override string Serialize(AccessToken message) {
+ this.BeforeSerialize(message);
+
+ var claimsSet = new JwtClaims() {
+ IssuedAt = message.UtcIssued,
+ Principal = message.User,
+ Scope = message.Scope,
+ Id = message.Nonce,
+ };
+ if (message.Lifetime.HasValue) {
+ claimsSet.NotAfter = message.UtcIssued + message.Lifetime.Value;
+ }
+
+ byte[] encodedPayload = MessagingUtilities.SerializeAsJsonBytes(claimsSet, messageDescriptions);
+
+ // First sign, then encrypt the payload, JWT style.
+ string jwt = this.CreateJsonWebEncryptionToken(JwtCharacterEncoding.GetBytes(this.CreateJsonWebSignatureToken(encodedPayload)));
+ return jwt;
+ }
+
+ protected override byte[] SerializeCore(AccessToken message) {
+ throw new NotImplementedException();
+ }
+
+ public override void Deserialize(AccessToken message, IProtocolMessage containingMessage, string value, string messagePartName) {
+ string[] segments = value.Split(new [] {'.'}, 4);
+ ErrorUtilities.VerifyProtocol(segments.Length > 1, "Invalid JWT. No periods found.");
+
+ string encodedHeader = segments[0];
+ byte[] decodedHeader = MessagingUtilities.FromBase64WebSafeString(encodedHeader);
+ //jwtHeaderMessageFactory.GetNewRequestMessage(new MessageReceivingEndpoint(new Uri ("http://localhost/"), HttpDeliveryMethods.PostRequest), );
+ var jwtHeader = new JwtHeader();
+ MessagingUtilities.DeserializeFromJson(decodedHeader, jwtHeader, messageDescriptions, JwtCharacterEncoding);
+ // TODO: instantiate the appropriate JwtHeader type.
+ // TODO: Verify that ExtraData is empty.
+
+
+
+ throw new NotImplementedException();
+
+ this.AfterDeserialize(message, containingMessage);
+ }
+
+ protected override void DeserializeCore(AccessToken message, byte[] data) {
+ throw new NotImplementedException();
+ }
+
+ private static string SerializeSegment(IMessage message) {
+ return MessagingUtilities.ConvertToBase64WebSafeString(MessagingUtilities.SerializeAsJsonBytes(message, messageDescriptions, JwtCharacterEncoding));
+ }
+
+ private string CreateJsonWebSignatureToken(byte[] payload) {
+ Requires.NotNull(payload, "payload");
+ Requires.ValidState(this.SigningKey != null, "An RSA signing key must be set first.");
+
+ string encodedPayload = MessagingUtilities.ConvertToBase64WebSafeString(payload);
+
+ KeyValuePair<string, CryptoKey> handleAndKey = this.CryptoKeyStore.GetKeys(this.CryptoKeyBucket).First();
+ using (var algorithm = JwtRsaShaSigningAlgorithm.Create(this.SigningKey, JwtRsaShaSigningAlgorithm.HashSize.Sha256)) {
+ string encodedHeader = SerializeSegment(algorithm.Header);
+
+ var builder = new StringBuilder(encodedHeader.Length + 1 + encodedPayload.Length);
+ builder.Append(encodedHeader);
+ builder.Append(".");
+ builder.Append(encodedPayload);
+ string securedInput = builder.ToString();
+
+ string encodedSignature = MessagingUtilities.ConvertToBase64WebSafeString(algorithm.Sign(JwtCharacterEncoding.GetBytes(securedInput)));
+ builder.Append(".");
+ builder.Append(encodedSignature);
+
+ return builder.ToString();
+ }
+ }
+
+ private string CreateJsonWebEncryptionToken(byte[] payload) {
+ Requires.NotNull(payload, "payload");
+ ErrorUtilities.VerifyInternal(this.Encrypted, "We shouldn't generate a JWE if we're not encrypting!");
+ ErrorUtilities.VerifySupported(this.EncryptingKey != null, "Only asymmetric encryption is supported.");
+
+ string encodedPayload = MessagingUtilities.ConvertToBase64WebSafeString(payload);
+
+ var header = new JweHeader(this.UseOaepPadding ? JsonWebEncryptionAlgorithms.RSA_OAEP : JsonWebEncryptionAlgorithms.RSA1_5, JsonWebEncryptionMethods.A256CBC);
+
+ var symmetricAlgorithm = SymmetricAlgorithm.Create("AES");
+ symmetricAlgorithm.KeySize = 256;
+ symmetricAlgorithm.Mode = CipherMode.CBC;
+ header.IV = symmetricAlgorithm.IV;
+
+ byte[] contentMasterKey = symmetricAlgorithm.Key;
+ byte[] encryptedKey = this.EncryptingKey.Encrypt(contentMasterKey, this.UseOaepPadding);
+ string encodedEncryptedKey = MessagingUtilities.ConvertToBase64WebSafeString(encryptedKey);
+
+ byte[] plaintext = payload;
+ if (this.Compressed) {
+ header.CompressionAlgorithm = "GZIP";
+ plaintext = MessagingUtilities.Compress(payload, MessagingUtilities.CompressionMethod.Gzip);
+ }
+
+ var ciphertextStream = new MemoryStream();
+ using (var encryptor = symmetricAlgorithm.CreateEncryptor()) {
+ using (var cryptoStream = new CryptoStream(ciphertextStream, encryptor, CryptoStreamMode.Write)) {
+ cryptoStream.Write(plaintext, 0, plaintext.Length);
+ cryptoStream.Flush();
+ }
+ }
+
+ string encodedCiphertext = MessagingUtilities.ConvertToBase64WebSafeString(ciphertextStream.ToArray());
+ string encodedHeader = SerializeSegment(header);
+
+ var builder = new StringBuilder(encodedHeader.Length + 1 + encodedPayload.Length);
+ builder.Append(encodedHeader);
+ builder.Append(".");
+ builder.Append(encodedEncryptedKey);
+ builder.Append(".");
+ builder.Append(encodedCiphertext);
+ builder.Append(".");
+ builder.Append(String.Empty); // the Encoded JWE Integrity Value is always empty because we use an AEAD encryption algorithm.
+ string securedInput = builder.ToString();
+
+ return builder.ToString();
+ }
+
+ private class JwtClaims : JwtMessageBase {
+ [MessagePart("exp", Encoder = typeof(TimestampEncoder))]
+ internal DateTime NotAfter { get; set; }
+
+ [MessagePart("nbf", Encoder = typeof(TimestampEncoder))]
+ internal DateTime NotBefore { get; set; }
+
+ [MessagePart("iat", Encoder = typeof(TimestampEncoder))]
+ internal DateTime IssuedAt { get; set; }
+
+ [MessagePart("iss")]
+ internal string Issuer { get; set; }
+
+ [MessagePart("aud")]
+ internal string Audience { get; set; }
+
+ [MessagePart("prn")]
+ internal string Principal { get; set; }
+
+ [MessagePart("jti")]
+ internal byte[] Id { get; set; }
+
+ [MessagePart("typ")]
+ internal string Type { get; set; }
+
+ [MessagePart("scope", Encoder = typeof(ScopeEncoder))]
+ internal HashSet<string> Scope { get; set; }
+ }
+ }
+}