diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index d8714ca..1afd7dd 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -16,10 +16,6 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup .NET 6.0 - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - name: Setup .NET 8.0 uses: actions/setup-dotnet@v4 with: diff --git a/src/ScottBrady.IdentityModel/Tokens/EdDsa.cs b/src/ScottBrady.IdentityModel/Tokens/EdDsa.cs index f3614a4..7594f51 100644 --- a/src/ScottBrady.IdentityModel/Tokens/EdDsa.cs +++ b/src/ScottBrady.IdentityModel/Tokens/EdDsa.cs @@ -73,8 +73,8 @@ public static EdDsa Create(EdDsaParameters parameters) public override KeySizes[] LegalKeySizes => Parameters.Curve switch { - ExtendedSecurityAlgorithms.Curves.Ed25519 => new[] { new KeySizes(32, 32, 0) }, - ExtendedSecurityAlgorithms.Curves.Ed448 => new[] { new KeySizes(57, 57, 0) }, + ExtendedSecurityAlgorithms.Curves.Ed25519 => [new KeySizes(32, 32, 0)], + ExtendedSecurityAlgorithms.Curves.Ed448 => [new KeySizes(57, 57, 0)], _ => throw new NotSupportedException() }; diff --git a/src/ScottBrady.IdentityModel/Tokens/EdDsaParameters.cs b/src/ScottBrady.IdentityModel/Tokens/EdDsaParameters.cs index 9b37c72..805e408 100644 --- a/src/ScottBrady.IdentityModel/Tokens/EdDsaParameters.cs +++ b/src/ScottBrady.IdentityModel/Tokens/EdDsaParameters.cs @@ -35,8 +35,8 @@ internal EdDsaParameters(AsymmetricCipherKeyPair keyPair, string curve) : this(c } } - public byte[] D { get; set; } - public byte[] X { get; set; } + public byte[] D { get; init; } + public byte[] X { get; init; } public string Curve { get; } public void Validate() @@ -45,8 +45,8 @@ public void Validate() if (D != null) { - if (Curve == ExtendedSecurityAlgorithms.Curves.Ed25519 && (D.Length != 32 && D.Length != 32*2)) throw new CryptographicException("Invalid key length. Must be 32 bytes."); - if (Curve == ExtendedSecurityAlgorithms.Curves.Ed448 && (D.Length != 57 && D.Length != 57*2)) throw new CryptographicException("Invalid key length. Must be 57 bytes."); + if (Curve == ExtendedSecurityAlgorithms.Curves.Ed25519 && D.Length != 32 && D.Length != 32*2) throw new CryptographicException("Invalid key length. Must be 32 bytes."); + if (Curve == ExtendedSecurityAlgorithms.Curves.Ed448 && D.Length != 57 && D.Length != 57*2) throw new CryptographicException("Invalid key length. Must be 57 bytes."); } if (X != null) diff --git a/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs b/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs index 27dc509..9b09800 100644 --- a/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs +++ b/src/ScottBrady.IdentityModel/Tokens/EdDsaSignatureProvider.cs @@ -11,11 +11,14 @@ public EdDsaSignatureProvider(EdDsaSecurityKey key, string algorithm) : base(key, algorithm) { edDsaKey = key; + WillCreateSignatures = key.PrivateKeyStatus == PrivateKeyStatus.Exists; } protected override void Dispose(bool disposing) { } public override byte[] Sign(byte[] input) => edDsaKey.EdDsa.Sign(input); - + public override bool Verify(byte[] input, byte[] signature) => edDsaKey.EdDsa.Verify(input, signature); + public override bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength) => edDsaKey.EdDsa.Verify(input, inputOffset, inputLength, signature, signatureOffset, signatureLength); + public override bool Sign(ReadOnlySpan data, Span destination, out int bytesWritten) { var signature = edDsaKey.EdDsa.Sign(data.ToArray()); @@ -30,8 +33,4 @@ public override byte[] Sign(byte[] input, int offset, int count) Buffer.BlockCopy(input, offset, data, 0, count); return edDsaKey.EdDsa.Sign(data); } - - public override bool Verify(byte[] input, byte[] signature) => edDsaKey.EdDsa.Verify(input, signature); - public override bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength) - => edDsaKey.EdDsa.Verify(input, inputOffset, inputLength, signature, signatureOffset, signatureLength); } \ No newline at end of file diff --git a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs index 3acb966..ea49c1b 100644 --- a/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs +++ b/test/ScottBrady.IdentityModel.Tests/Tokens/EdDSA/EdDsaSignatureProviderTests.cs @@ -1,3 +1,4 @@ +using System; using System.Text; using FluentAssertions; using Microsoft.IdentityModel.Tokens; @@ -9,83 +10,100 @@ namespace ScottBrady.IdentityModel.Tests.Tokens.EdDSA; public class EdDsaSignatureProviderTests { - // privateKey = "FU1F1QTjYwfB-xkO6aknnBifE_Ywa94U04xpd-XJfBs" - + private readonly byte[] privateKey = Base64UrlEncoder.DecodeBytes("FU1F1QTjYwfB-xkO6aknnBifE_Ywa94U04xpd-XJfBs"); + private readonly byte[] publicKey = Base64UrlEncoder.DecodeBytes("60mR98SQlHUSeLeIu7TeJBTLRG10qlcDLU4AJjQdqMQ"); + private readonly byte[] plaintext = Encoding.UTF8.GetBytes("eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30"); + private readonly byte[] validSignature = Base64UrlEncoder.DecodeBytes("OyBxBr344Ny-0vRCeEMLSnuEO1IecybvJBivrjum4d-dgN5WLnEAGAO43MlZeRGn1F3fRXO_xlYot68PtDuiAA"); + [Fact] - public void ctor_ExpectPropertiesSet() + public void ctor_WithPrivateKey_ExpectPropertiesSet() { - var expectedSecurityKey = new EdDsaSecurityKey(EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519)); - var expectedAlgorithm = ExtendedSecurityAlgorithms.EdDsa; + var securityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { D = privateKey })); + var algorithm = ExtendedSecurityAlgorithms.EdDsa; + + var provider = new EdDsaSignatureProvider(securityKey, algorithm); - var provider = new EdDsaSignatureProvider(expectedSecurityKey, expectedAlgorithm); + provider.Key.Should().Be(securityKey); + provider.Algorithm.Should().Be(algorithm); + provider.WillCreateSignatures.Should().BeTrue(); + } + [Fact] + public void ctor_WithPublicKey_ExpectPropertiesSet() + { + var securityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { X = publicKey })); + var algorithm = ExtendedSecurityAlgorithms.EdDsa; - provider.Key.Should().Be(expectedSecurityKey); - provider.Algorithm.Should().Be(expectedAlgorithm); - } + var provider = new EdDsaSignatureProvider(securityKey, algorithm); + + provider.Key.Should().Be(securityKey); + provider.Algorithm.Should().Be(algorithm); + provider.WillCreateSignatures.Should().BeFalse(); + } + + [Fact] + public void Dispose_ExpectNoException() + { + new EdDsaSignatureProvider(new EdDsaSecurityKey(EdDsa.Create(ExtendedSecurityAlgorithms.Curves.Ed25519)), ExtendedSecurityAlgorithms.EdDsa).Dispose(); + } [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(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {D = Base64UrlEncoder.DecodeBytes(privateKey)})); - - var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); - - var signature = signatureProvider.Sign(Encoding.UTF8.GetBytes(plaintext)); - - signature.Should().BeEquivalentTo(Base64UrlEncoder.DecodeBytes(expectedSignature)); - } + var edDsaSecurityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { D = privateKey })); + var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); + + var signature = signatureProvider.Sign(plaintext); + + signature.Should().BeEquivalentTo(validSignature); + } [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(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = Base64UrlEncoder.DecodeBytes(publicKey)})); - - var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); - - var isValidSignature = signatureProvider.Verify( - Encoding.UTF8.GetBytes(plaintext), - Base64UrlEncoder.DecodeBytes(signature)); - - isValidSignature.Should().BeTrue(); - } + var edDsaSecurityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { X = publicKey })); + var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); + var isValidSignature = signatureProvider.Verify(plaintext, validSignature); + + isValidSignature.Should().BeTrue(); + } + [Fact] - public void VerifyWithOffsets_WhenJwtSignedWithEd25519Curve_ExpectTrue() + public void Verify_WithOffsets_WhenJwtSignedWithEd25519Curve_ExpectTrue() { - const string plaintext = - "eyJraWQiOiIxMjMiLCJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9.eyJhdWQiOiJ5b3UiLCJzdWIiOiJib2IiLCJpc3MiOiJtZSIsImV4cCI6MTU5MDg0MTg4N30"; - const string signature = - "OyBxBr344Ny-0vRCeEMLSnuEO1IecybvJBivrjum4d-dgN5WLnEAGAO43MlZeRGn1F3fRXO_xlYot68PtDuiAA"; - - const string publicKey = "60mR98SQlHUSeLeIu7TeJBTLRG10qlcDLU4AJjQdqMQ"; - var edDsaSecurityKey = new EdDsaSecurityKey(EdDsa.Create( - new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) {X = Base64UrlEncoder.DecodeBytes(publicKey)})); - - var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); - - var inputBytes = Encoding.UTF8.GetBytes(plaintext); - var signatureBytes = Base64UrlEncoder.DecodeBytes(signature); - - var isValidSignature = signatureProvider.Verify( - inputBytes, - 0, inputBytes.Length, - signatureBytes, 0,signatureBytes.Length); - - isValidSignature.Should().BeTrue(); - } + var edDsaSecurityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { X = publicKey })); + var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); + + var isValidSignature = signatureProvider.Verify(plaintext, 0, plaintext.Length, validSignature, 0, validSignature.Length); + + isValidSignature.Should().BeTrue(); + } + + [Fact] + public void Sign_WithSpan_WhenSigningWithEd25519Curve_ExpectCorrectSignature() + { + var edDsaSecurityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { D = privateKey })); + var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); + + Span signature = stackalloc byte[64]; + var isSuccess = signatureProvider.Sign(plaintext.AsSpan(), signature, out var bytesWritten); + + isSuccess.Should().BeTrue(); + signature.ToArray().Should().BeEquivalentTo(validSignature); + bytesWritten.Should().Be(64); + } + + [Fact] + public void Sign_WithOffset_WhenSigningWithEd25519Curve_ExpectCorrectSignature() + { + var edDsaSecurityKey = new EdDsaSecurityKey(EdDsa.Create(new EdDsaParameters(ExtendedSecurityAlgorithms.Curves.Ed25519) { D = privateKey })); + var signatureProvider = new EdDsaSignatureProvider(edDsaSecurityKey, ExtendedSecurityAlgorithms.EdDsa); + + var input = new byte[plaintext.Length + 1]; + Buffer.BlockCopy(plaintext, 0, input, 1, plaintext.Length); + var signature = signatureProvider.Sign(input, 1, plaintext.Length); + + signature.Should().BeEquivalentTo(validSignature); + } } \ No newline at end of file