Skip to content

Commit

Permalink
Implement additional asymmetric signature and encryption overloads
Browse files Browse the repository at this point in the history
  • Loading branch information
vcsjones committed Aug 8, 2022
1 parent 8db080e commit 8a26f96
Show file tree
Hide file tree
Showing 8 changed files with 1,830 additions and 0 deletions.
Expand Up @@ -7,6 +7,7 @@
using System.Linq;
using System.Security.Cryptography.Tests;
using System.Text;
using Microsoft.DotNet.XUnitExtensions;
using Xunit;

namespace System.Security.Cryptography.EcDsa.Tests
Expand Down Expand Up @@ -131,12 +132,20 @@ public abstract partial class ECDsaTests : ECDsaTestsBase
{
protected bool VerifyData(ECDsa ecdsa, byte[] data, byte[] signature, HashAlgorithmName hashAlgorithm) =>
VerifyData(ecdsa, data, 0, data.Length, signature, hashAlgorithm);

protected abstract bool VerifyData(ECDsa ecdsa, byte[] data, int offset, int count, byte[] signature, HashAlgorithmName hashAlgorithm);

protected byte[] SignData(ECDsa ecdsa, byte[] data, HashAlgorithmName hashAlgorithm) =>
SignData(ecdsa, data, 0, data.Length, hashAlgorithm);

protected abstract byte[] SignData(ECDsa ecdsa, byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm);

protected virtual byte[] SignHash(ECDsa ecdsa, byte[] hash, int offset, int count) =>
throw new SkipTestException("SignHash not implemented.");

protected virtual bool VerifyHash(ECDsa ecdsa, byte[] hash, int offset, int count, byte[] signature) =>
throw new SkipTestException("VerifyHash not implemented.");

public static IEnumerable<object[]> RealImplementations() =>
new[] {
new ECDsa[] { ECDsaFactory.Create() },
Expand Down Expand Up @@ -200,6 +209,40 @@ protected virtual void UseAfterDispose(ECDsa ecdsa, byte[] data, byte[] sig)
() => VerifyData(ecdsa, data, sig, HashAlgorithmName.SHA256));
}

[ConditionalTheory]
[MemberData(nameof(RealImplementations))]
public void SignHash_Roundtrip(ECDsa ecdsa)
{
byte[] hash = RandomNumberGenerator.GetBytes(32);
byte[] signature = SignHash(ecdsa, hash, 0, hash.Length);

Assert.True(VerifyHash(ecdsa, hash, 0, hash.Length, signature), nameof(VerifyHash));
}

[ConditionalTheory]
[MemberData(nameof(RealImplementations))]
public void SignHash_TamperedSignature(ECDsa ecdsa)
{
byte[] hash = RandomNumberGenerator.GetBytes(32);
byte[] signature = SignHash(ecdsa, hash, 0, hash.Length);

signature[0] ^= 0xFF;

Assert.False(VerifyHash(ecdsa, hash, 0, hash.Length, signature), nameof(VerifyHash));
}

[ConditionalTheory]
[MemberData(nameof(RealImplementations))]
public void SignHash_DifferentHashes(ECDsa ecdsa)
{
byte[] hash = RandomNumberGenerator.GetBytes(32);
byte[] signature = SignHash(ecdsa, hash, 0, hash.Length);

hash[0] ^= 0xFF;

Assert.False(VerifyHash(ecdsa, hash, 0, hash.Length, signature), nameof(VerifyHash));
}

[Theory]
[MemberData(nameof(RealImplementations))]
public void SignData_MaxOffset_ZeroLength_NoThrow(ECDsa ecdsa)
Expand Down
Expand Up @@ -9,13 +9,120 @@ namespace System.Security.Cryptography.EcDsa.Tests
[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/62547", TestPlatforms.Android)]
public sealed class ECDsaTests_Span : ECDsaTests
{
protected override bool VerifyData(ECDsa ecdsa, byte[] data, int offset, int count, byte[] signature, HashAlgorithmName hashAlgorithm) =>
ecdsa.VerifyData(new ReadOnlySpan<byte>(data, offset, count), signature, hashAlgorithm);

protected override byte[] SignData(ECDsa ecdsa, byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) =>
WithOutputArray(dest => ecdsa.SignData(new ReadOnlySpan<byte>(data, offset, count), dest, hashAlgorithm));

protected override byte[] SignHash(ECDsa ecdsa, byte[] hash, int offset, int count) =>
WithOutputArray(dest => ecdsa.SignHash(new ReadOnlySpan<byte>(hash, offset, count), dest));

protected override bool VerifyHash(ECDsa ecdsa, byte[] hash, int offset, int count, byte[] signature) =>
ecdsa.VerifyHash(new ReadOnlySpan<byte>(hash, offset, count), signature);

protected override void UseAfterDispose(ECDsa ecdsa, byte[] data, byte[] sig)
{
base.UseAfterDispose(ecdsa, data, sig);
byte[] hash = new byte[32];


Assert.Throws<ObjectDisposedException>(() => ecdsa.VerifyHash(hash.AsSpan(), sig.AsSpan()));
Assert.Throws<ObjectDisposedException>(() => ecdsa.SignData(hash.AsSpan(), Span<byte>.Empty, HashAlgorithmName.SHA256));
Assert.Throws<ObjectDisposedException>(() => ecdsa.SignHash(hash.AsSpan(), Span<byte>.Empty));
}

[Theory]
[MemberData(nameof(RealImplementations))]
public void SignData_InvalidArguments_Throws(ECDsa ecdsa)
{
Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
ecdsa.SignData(ReadOnlySpan<byte>.Empty, Span<byte>.Empty, new HashAlgorithmName(null)));

Assert.Throws<ArgumentException>("hashAlgorithm", () =>
ecdsa.SignData(ReadOnlySpan<byte>.Empty, Span<byte>.Empty, new HashAlgorithmName("")));

Assert.Throws<ArgumentOutOfRangeException>("signatureFormat",
() => ecdsa.SignData(ReadOnlySpan<byte>.Empty, Span<byte>.Empty, HashAlgorithmName.SHA256, (DSASignatureFormat)42));

Assert.ThrowsAny<CryptographicException>(() =>
ecdsa.SignData(ReadOnlySpan<byte>.Empty, Span<byte>.Empty, new HashAlgorithmName(Guid.NewGuid().ToString("N"))));
}

private static byte[] WithOutputArray(Func<byte[], int> func)
{
for (int length = 1; ; length = checked(length * 2))
{
byte[] result = new byte[length];

try
{
int written = func(result);
Array.Resize(ref result, written);
return result;
}
catch (ArgumentException ae) when (ae.ParamName == "destination")
{
continue;
}
}
}
}

[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/62547", TestPlatforms.Android)]
public sealed class ECDsaTests_AllocatingSpan : ECDsaTests
{
protected override bool VerifyData(ECDsa ecdsa, byte[] data, int offset, int count, byte[] signature, HashAlgorithmName hashAlgorithm) =>
ecdsa.VerifyData(new ReadOnlySpan<byte>(data, offset, count), signature, hashAlgorithm);

protected override byte[] SignData(ECDsa ecdsa, byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) =>
ecdsa.SignData(new ReadOnlySpan<byte>(data, offset, count), hashAlgorithm);

protected override byte[] SignHash(ECDsa ecdsa, byte[] hash, int offset, int count) =>
ecdsa.SignHash(new ReadOnlySpan<byte>(hash, offset, count));

protected override bool VerifyHash(ECDsa ecdsa, byte[] hash, int offset, int count, byte[] signature) =>
ecdsa.VerifyHash(new ReadOnlySpan<byte>(hash, offset, count), signature);

protected override void UseAfterDispose(ECDsa ecdsa, byte[] data, byte[] sig)
{
base.UseAfterDispose(ecdsa, data, sig);
byte[] hash = new byte[32];

Assert.Throws<ObjectDisposedException>(() => ecdsa.VerifyHash(hash.AsSpan(), sig.AsSpan()));
Assert.Throws<ObjectDisposedException>(() => ecdsa.SignData(hash.AsSpan(), HashAlgorithmName.SHA256));
Assert.Throws<ObjectDisposedException>(() => ecdsa.SignHash(hash.AsSpan()));
}

[Theory]
[MemberData(nameof(RealImplementations))]
public void SignData_InvalidArguments_Throws(ECDsa ecdsa)
{
AssertExtensions.Throws<ArgumentNullException>("hashAlgorithm", () => ecdsa.SignData(ReadOnlySpan<byte>.Empty, new HashAlgorithmName(null)));
AssertExtensions.Throws<ArgumentException>("hashAlgorithm", () => ecdsa.SignData(ReadOnlySpan<byte>.Empty, new HashAlgorithmName("")));
Assert.Throws<ArgumentOutOfRangeException>("signatureFormat", () => ecdsa.SignData(ReadOnlySpan<byte>.Empty, HashAlgorithmName.SHA256, (DSASignatureFormat)42));
Assert.ThrowsAny<CryptographicException>(() => ecdsa.SignData(ReadOnlySpan<byte>.Empty, new HashAlgorithmName(Guid.NewGuid().ToString("N"))));
}
}

[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
[ActiveIssue("https://github.com/dotnet/runtime/issues/62547", TestPlatforms.Android)]
public sealed class ECDsaTests_TrySpan : ECDsaTests
{
protected override bool VerifyData(ECDsa ecdsa, byte[] data, int offset, int count, byte[] signature, HashAlgorithmName hashAlgorithm) =>
ecdsa.VerifyData(new ReadOnlySpan<byte>(data, offset, count), signature, hashAlgorithm);

protected override byte[] SignData(ECDsa ecdsa, byte[] data, int offset, int count, HashAlgorithmName hashAlgorithm) =>
TryWithOutputArray(dest => ecdsa.TrySignData(new ReadOnlySpan<byte>(data, offset, count), dest, hashAlgorithm, out int bytesWritten) ? (true, bytesWritten) : (false, 0));

protected override byte[] SignHash(ECDsa ecdsa, byte[] hash, int offset, int count) =>
TryWithOutputArray(dest => ecdsa.TrySignHash(new ReadOnlySpan<byte>(hash, offset, count), dest, out int bytesWritten) ? (true, bytesWritten) : (false, 0));

protected override bool VerifyHash(ECDsa ecdsa, byte[] hash, int offset, int count, byte[] signature) =>
ecdsa.VerifyHash(new ReadOnlySpan<byte>(hash, offset, count), signature);

protected override void UseAfterDispose(ECDsa ecdsa, byte[] data, byte[] sig)
{
base.UseAfterDispose(ecdsa, data, sig);
Expand Down
Expand Up @@ -8,6 +8,45 @@ namespace System.Security.Cryptography.Rsa.Tests
{
[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
public sealed class EncryptDecrypt_Span : EncryptDecrypt
{
protected override byte[] Encrypt(RSA rsa, byte[] data, RSAEncryptionPadding padding) =>
WithOutputArray(dest => rsa.Encrypt(data, dest, padding));

protected override byte[] Decrypt(RSA rsa, byte[] data, RSAEncryptionPadding padding) =>
WithOutputArray(dest => rsa.Decrypt(data, dest, padding));

private static byte[] WithOutputArray(Func<byte[], int> func)
{
for (int length = 1; ; length = checked(length * 2))
{
byte[] result = new byte[length];

try
{
int written = func(result);
Array.Resize(ref result, written);
return result;
}
catch (ArgumentException ae) when (ae.ParamName == "destination")
{
continue;
}
}
}
}

[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
public sealed class EncryptDecrypt_AllocatingSpan : EncryptDecrypt
{
protected override byte[] Encrypt(RSA rsa, byte[] data, RSAEncryptionPadding padding) =>
rsa.Encrypt(new ReadOnlySpan<byte>(data), padding);

protected override byte[] Decrypt(RSA rsa, byte[] data, RSAEncryptionPadding padding) =>
rsa.Decrypt(new ReadOnlySpan<byte>(data), padding);
}

[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
public sealed class EncryptDecrypt_TrySpan : EncryptDecrypt
{
protected override byte[] Encrypt(RSA rsa, byte[] data, RSAEncryptionPadding padding) =>
TryWithOutputArray(dest => rsa.TryEncrypt(data, dest, padding, out int bytesWritten) ? (true, bytesWritten) : (false, 0));
Expand Down
Expand Up @@ -5,7 +5,56 @@

namespace System.Security.Cryptography.Rsa.Tests
{
public sealed class SignVerify_AllocatingSpan : SignVerify
{
protected override byte[] SignData(RSA rsa, byte[] data, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
rsa.SignData(new ReadOnlySpan<byte>(data), hashAlgorithm, padding);

protected override byte[] SignHash(RSA rsa, byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
rsa.SignHash(new ReadOnlySpan<byte>(hash), hashAlgorithm, padding);

protected override bool VerifyData(RSA rsa, byte[] data, byte[] signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
rsa.VerifyData(new ReadOnlySpan<byte>(data), (ReadOnlySpan<byte>)signature, hashAlgorithm, padding);

protected override bool VerifyHash(RSA rsa, byte[] hash, byte[] signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
rsa.VerifyHash(new ReadOnlySpan<byte>(hash), (ReadOnlySpan<byte>)signature, hashAlgorithm, padding);
}

public sealed class SignVerify_Span : SignVerify
{
protected override byte[] SignData(RSA rsa, byte[] data, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
WithOutputArray(dest => rsa.SignData(data, dest, hashAlgorithm, padding));

protected override byte[] SignHash(RSA rsa, byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
WithOutputArray(dest => rsa.SignHash(hash, dest, hashAlgorithm, padding));

protected override bool VerifyData(RSA rsa, byte[] data, byte[] signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
rsa.VerifyData((ReadOnlySpan<byte>)data, (ReadOnlySpan<byte>)signature, hashAlgorithm, padding);

protected override bool VerifyHash(RSA rsa, byte[] hash, byte[] signature, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
rsa.VerifyHash((ReadOnlySpan<byte>)hash, (ReadOnlySpan<byte>)signature, hashAlgorithm, padding);

private static byte[] WithOutputArray(Func<byte[], int> func)
{
for (int length = 1; ; length = checked(length * 2))
{
byte[] result = new byte[length];

try
{
int written = func(result);
Array.Resize(ref result, written);
return result;
}
catch (ArgumentException ae) when (ae.ParamName == "destination")
{
continue;
}
}
}
}

public sealed class SignVerify_TrySpan : SignVerify
{
protected override byte[] SignData(RSA rsa, byte[] data, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding) =>
TryWithOutputArray(dest => rsa.TrySignData(data, dest, hashAlgorithm, padding, out int bytesWritten) ? (true, bytesWritten) : (false, 0));
Expand Down

0 comments on commit 8a26f96

Please sign in to comment.