Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement additional asymmetric signature and encryption overloads #73502

Merged
merged 3 commits into from Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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