Skip to content

Commit

Permalink
Expanded support for Ed25519 with JWT signing
Browse files Browse the repository at this point in the history
  • Loading branch information
scottbrady91 committed May 30, 2020
1 parent 795e1b3 commit dd3a662
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/ScottBrady.IdentityModel/Assembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("ScottBrady.IdentityModel.Tests")]
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,14 @@ public static class ExtendedSecurityAlgorithms

// https://tools.ietf.org/html/rfc8037#section-5
public const string EdDsa = "EdDSA";

public class Curves
{
// https://tools.ietf.org/html/rfc8037#section-5
public const string Ed25519 = "Ed25519";
public const string Ed448 = "Ed448";
public const string X25519 = "X25519";
public const string X448 = "X448";
}
}
}
4 changes: 2 additions & 2 deletions src/ScottBrady.IdentityModel/ScottBrady.IdentityModel.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Authors>Scott Brady</Authors>
<Description>Token helpers for Branca and PASETO.</Description>
<Description>Token helpers for Branca, PASETO, and Ed25519.</Description>
<PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/scottbrady91/IdentityModel</PackageProjectUrl>
<Copyright>Copyright 2020 (c) Scott Brady</Copyright>
<PackageTags>Branca PASETO Base62</PackageTags>
<IncludeSymbols>true</IncludeSymbols>
<Version>1.0.0</Version>
<Version>1.1.0</Version>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
</PropertyGroup>

Expand Down
16 changes: 13 additions & 3 deletions src/ScottBrady.IdentityModel/Tokens/EdDsaSecurityKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using ScottBrady.IdentityModel.Crypto;

namespace ScottBrady.IdentityModel.Tokens
{
public class EdDsaSecurityKey : AsymmetricSecurityKey
{
public EdDsaSecurityKey(Ed25519PrivateKeyParameters keyParameters)
private EdDsaSecurityKey()
{
CryptoProviderFactory.CustomCryptoProvider = new ExtendedCryptoProvider();
}

public EdDsaSecurityKey(Ed25519PrivateKeyParameters keyParameters) : this()
{
KeyParameters = keyParameters ?? throw new ArgumentNullException(nameof(keyParameters));
Curve = ExtendedSecurityAlgorithms.Curves.Ed25519;
}
public EdDsaSecurityKey(Ed25519PublicKeyParameters keyParameters)

public EdDsaSecurityKey(Ed25519PublicKeyParameters keyParameters) : this()
{
KeyParameters = keyParameters ?? throw new ArgumentNullException(nameof(keyParameters));
Curve = ExtendedSecurityAlgorithms.Curves.Ed25519;
}

public virtual AsymmetricKeyParameter KeyParameters { get; }
public string Curve { get; }

public override int KeySize => throw new NotImplementedException();

[Obsolete("HasPrivateKey method is deprecated, please use PrivateKeyStatus.")]
Expand Down
36 changes: 36 additions & 0 deletions src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Signers;

namespace ScottBrady.IdentityModel.Tokens
{
internal class EdDsaSignatureProvider : SignatureProvider
{
private readonly EdDsaSecurityKey edDsaKey;

public EdDsaSignatureProvider(EdDsaSecurityKey key, string algorithm)
: base(key, algorithm)
{
edDsaKey = key;
}

protected override void Dispose(bool disposing) { }

public override byte[] Sign(byte[] input)
{
var signer = new Ed25519Signer();
signer.Init(true, edDsaKey.KeyParameters);
signer.BlockUpdate(input, 0, input.Length);

return signer.GenerateSignature();
}

public override bool Verify(byte[] input, byte[] signature)
{
var validator = new Ed25519Signer();
validator.Init(false, edDsaKey.KeyParameters);
validator.BlockUpdate(input, 0, input.Length);

return validator.VerifySignature(signature);
}
}
}
28 changes: 28 additions & 0 deletions src/ScottBrady.IdentityModel/Tokens/ExtendedCryptoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using Microsoft.IdentityModel.Tokens;
using ScottBrady.IdentityModel.Tokens;

namespace ScottBrady.IdentityModel.Crypto
{
internal class ExtendedCryptoProvider : ICryptoProvider
{
public bool IsSupportedAlgorithm(string algorithm, params object[] args)
=> algorithm == ExtendedSecurityAlgorithms.EdDsa;

public object Create(string algorithm, params object[] args)
{
if (algorithm == ExtendedSecurityAlgorithms.EdDsa && args[0] is EdDsaSecurityKey key)
{
return new EdDsaSignatureProvider(key, algorithm);
}

throw new NotSupportedException();
}

public void Release(object cryptoInstance)
{
if (cryptoInstance is IDisposable disposableObject)
disposableObject.Dispose();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using FluentAssertions;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using ScottBrady.IdentityModel.Crypto;
using ScottBrady.IdentityModel.Tokens;
using Xunit;

namespace ScottBrady.IdentityModel.Tests.Tokens
{
public class EdDsaSecurityKeyTests
{
[Fact]
public void ctor_WhenKeyParametersAreNull_ExpectArgumentNullException()
=> Assert.Throws<ArgumentNullException>(() => new EdDsaSecurityKey((Ed25519PublicKeyParameters) null));

[Fact]
public void ctor_WhenEd25519PrivateKey_ExpectKeySetAndCorrectCurve()
{
var keyPair = GenerateEd25519KeyPair();

var securityKey = new EdDsaSecurityKey((Ed25519PrivateKeyParameters) keyPair.Private);

securityKey.CryptoProviderFactory.CustomCryptoProvider.Should().BeOfType<ExtendedCryptoProvider>();
securityKey.KeyParameters.Should().Be(keyPair.Private);
securityKey.Curve.Should().Be(ExtendedSecurityAlgorithms.Curves.Ed25519);
securityKey.PrivateKeyStatus.Should().Be(PrivateKeyStatus.Exists);

#pragma warning disable 618
securityKey.HasPrivateKey.Should().BeTrue();
#pragma warning restore 618
}

[Fact]
public void ctor_WhenEd25519PublicKey_ExpectKeySetAndCorrectCurve()
{
var keyPair = GenerateEd25519KeyPair();

var securityKey = new EdDsaSecurityKey((Ed25519PublicKeyParameters) keyPair.Public);

securityKey.CryptoProviderFactory.CustomCryptoProvider.Should().BeOfType<ExtendedCryptoProvider>();
securityKey.KeyParameters.Should().Be(keyPair.Public);
securityKey.Curve.Should().Be(ExtendedSecurityAlgorithms.Curves.Ed25519);
securityKey.PrivateKeyStatus.Should().Be(PrivateKeyStatus.DoesNotExist);

#pragma warning disable 618
securityKey.HasPrivateKey.Should().BeFalse();
#pragma warning restore 618
}

private static AsymmetricCipherKeyPair GenerateEd25519KeyPair()
{
var keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
return keyPairGenerator.GenerateKeyPair();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using FluentAssertions;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using ScottBrady.IdentityModel.Crypto;
using ScottBrady.IdentityModel.Tokens;
using Xunit;

namespace ScottBrady.IdentityModel.Tests.Tokens
{
public class EdDsaSignatureProviderTests
{
// privateKey = "FU1F1QTjYwfB-xkO6aknnBifE_Ywa94U04xpd-XJfBs"

[Fact]
public void ctor_ExpectPropertiesSet()
{
var keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var keyPair = keyPairGenerator.GenerateKeyPair();

var expectedSecurityKey = new EdDsaSecurityKey((Ed25519PublicKeyParameters) keyPair.Public);
var expectedAlgorithm = ExtendedSecurityAlgorithms.EdDsa;

var provider = new EdDsaSignatureProvider(expectedSecurityKey, expectedAlgorithm);

provider.Key.Should().Be(expectedSecurityKey);
provider.Algorithm.Should().Be(expectedAlgorithm);
}

[Fact]
public void Sign_WhenSigningWithEd25519Curve_ExpectCorrectSignature()
{
const string plaintext =
"eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30";
const string expectedSignature =
"OyBxBr344Ny-0vRCeEMLSnuEO1IecybvJBivrjum4d-dgN5WLnEAGAO43MlZeRGn1F3fRXO_xlYot68PtDuiAA";

const string privateKey = "FU1F1QTjYwfB-xkO6aknnBifE_Ywa94U04xpd-XJfBs";
var edDsaSecurityKey = new EdDsaSecurityKey(new Ed25519PrivateKeyParameters(Base64UrlEncoder.DecodeBytes(privateKey), 0));

var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa);

var signature = signatureProvider.Sign(System.Text.Encoding.UTF8.GetBytes(plaintext));

signature.Should().BeEquivalentTo(Base64UrlEncoder.DecodeBytes(expectedSignature));
}

[Fact]
public void Verify_WhenJwtSignedWithEd25519Curve_ExpectTrue()
{
const string plaintext =
"eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30";
const string signature =
"OyBxBr344Ny-0vRCeEMLSnuEO1IecybvJBivrjum4d-dgN5WLnEAGAO43MlZeRGn1F3fRXO_xlYot68PtDuiAA";

const string publicKey = "60mR98SQlHUSeLeIu7TeJBTLRG10qlcDLU4AJjQdqMQ";
var edDsaSecurityKey = new EdDsaSecurityKey(new Ed25519PublicKeyParameters(Base64UrlEncoder.DecodeBytes(publicKey), 0));

var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa);

var isValidSignature = signatureProvider.Verify(
System.Text.Encoding.UTF8.GetBytes(plaintext),
Base64UrlEncoder.DecodeBytes(signature));

isValidSignature.Should().BeTrue();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.IO;
using System.Security.Cryptography;
using FluentAssertions;
using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using ScottBrady.IdentityModel.Crypto;
using ScottBrady.IdentityModel.Tokens;
using Xunit;

namespace ScottBrady.IdentityModel.Tests.Tokens
{
public class ExtendedCryptoProviderTests
{
private ExtendedCryptoProvider sut = new ExtendedCryptoProvider();

[Theory]
[InlineData("eddsa")]
[InlineData("RS256")]
[InlineData("EDDSA")]
public void IsSupportedAlgorithm_WhenNotSupportedAlgorithm_ExpectFalse(string algorithm)
=> sut.IsSupportedAlgorithm(algorithm);

[Theory]
[InlineData(ExtendedSecurityAlgorithms.EdDsa)]
public void IsSupportedAlgorithm_WhenSupportedAlgorithm_ExpectTrue(string algorithm)
=> sut.IsSupportedAlgorithm(algorithm);

[Fact]
public void Release_WhenObjectImplementsIDisposable_ExpectObjectDisposed()
{
var memoryStream = new MemoryStream();
sut.Release(memoryStream);
Assert.Throws<ObjectDisposedException>(() => memoryStream.Read(Span<byte>.Empty));
}

[Fact]
public void Release_WhenObjectDoesNotImplementIDisposable_ExpectNoOp()
{
var uri = new Uri("urn:test");
sut.Release(uri);
}

[Fact]
public void Create_WhenAlgorithmIsNotEdDsaButHasEdDsaSecurityKey_ExpectNotSupportedException()
{
var keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var keyPair = keyPairGenerator.GenerateKeyPair();

var securityKey = new EdDsaSecurityKey((Ed25519PublicKeyParameters) keyPair.Public);

Assert.Throws<NotSupportedException>(() => sut.Create(SecurityAlgorithms.RsaSha256, securityKey));
}

[Fact]
public void Create_WhenAlgorithmIsEdDsaButIsNotEdDsaSecurityKey_ExpectNotSupportedException()
{
var securityKey = new RsaSecurityKey(RSA.Create());

Assert.Throws<NotSupportedException>(() => sut.Create(ExtendedSecurityAlgorithms.EdDsa, securityKey));
}

[Fact]
public void Create_WhenAlgorithmIsEdDsaWithEdDsaSecurityKey_ExpectEdDsaSignatureProvider()
{
var keyPairGenerator = new Ed25519KeyPairGenerator();
keyPairGenerator.Init(new Ed25519KeyGenerationParameters(new SecureRandom()));
var keyPair = keyPairGenerator.GenerateKeyPair();

var securityKey = new EdDsaSecurityKey((Ed25519PublicKeyParameters) keyPair.Public);

var signatureProvider = sut.Create(ExtendedSecurityAlgorithms.EdDsa, securityKey);

var edDsaSignatureProvider = Assert.IsType<EdDsaSignatureProvider>(signatureProvider);
edDsaSignatureProvider.Algorithm.Should().Be(ExtendedSecurityAlgorithms.EdDsa);
edDsaSignatureProvider.Key.Should().Be(securityKey);
}
}
}
Loading

0 comments on commit dd3a662

Please sign in to comment.