Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography/GostSignatureEncoding.cs

129 lines
3.9 KiB
C#

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Math;
using System;
using System.Security.Cryptography;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography;
public static class GostSignatureEncoding
{
public static bool IsDer(ReadOnlySpan<byte> signature)
{
if (signature.Length < 2 || signature[0] != 0x30)
{
return false;
}
var lengthByte = signature[1];
if ((lengthByte & 0x80) == 0)
{
var total = lengthByte + 2;
return total == signature.Length;
}
var lengthBytes = lengthByte & 0x7F;
if (lengthBytes is 0 or > 4 || signature.Length < 2 + lengthBytes)
{
return false;
}
var totalLength = 0;
for (var i = 0; i < lengthBytes; i++)
{
totalLength = (totalLength << 8) | signature[2 + i];
}
return totalLength + 2 + lengthBytes == signature.Length;
}
public static byte[] ToRaw(ReadOnlySpan<byte> der, int coordinateLength)
{
if (!IsDer(der))
{
throw new CryptographicException(_t("crypto.gost.not_der"));
}
var sequence = Asn1Sequence.GetInstance(Asn1Object.FromByteArray(der.ToArray()));
if (sequence.Count != 2)
{
throw new CryptographicException(_t("crypto.gost.invalid_der"));
}
var r = NormalizeCoordinate(((DerInteger)sequence[0]).PositiveValue.ToByteArrayUnsigned(), coordinateLength);
var s = NormalizeCoordinate(((DerInteger)sequence[1]).PositiveValue.ToByteArrayUnsigned(), coordinateLength);
var raw = new byte[coordinateLength * 2];
s.CopyTo(raw.AsSpan(0, coordinateLength));
r.CopyTo(raw.AsSpan(coordinateLength));
return raw;
}
public static byte[] ToDer(ReadOnlySpan<byte> raw, int coordinateLength)
{
if (raw.Length != coordinateLength * 2)
{
throw new CryptographicException(_t("crypto.gost.raw_length", coordinateLength * 2));
}
var s = raw[..coordinateLength].ToArray();
var r = raw[coordinateLength..].ToArray();
var derSequence = new DerSequence(
new DerInteger(new BigInteger(1, r)),
new DerInteger(new BigInteger(1, s)));
return derSequence.GetDerEncoded();
}
public static (BigInteger r, BigInteger s) DecodeComponents(ReadOnlySpan<byte> signature, int coordinateLength)
{
if (IsDer(signature))
{
var sequence = Asn1Sequence.GetInstance(Asn1Object.FromByteArray(signature.ToArray()));
if (sequence.Count != 2)
{
throw new CryptographicException(_t("crypto.gost.invalid_der"));
}
return (((DerInteger)sequence[0]).PositiveValue, ((DerInteger)sequence[1]).PositiveValue);
}
if (signature.Length == coordinateLength * 2)
{
var s = new byte[coordinateLength];
var r = new byte[coordinateLength];
signature[..coordinateLength].CopyTo(s);
signature[coordinateLength..].CopyTo(r);
return (new BigInteger(1, r), new BigInteger(1, s));
}
throw new CryptographicException(_t("crypto.gost.neither_format"));
}
private static byte[] NormalizeCoordinate(ReadOnlySpan<byte> value, int coordinateLength)
{
var trimmed = TrimLeadingZeros(value);
if (trimmed.Length > coordinateLength)
{
throw new CryptographicException(_t("crypto.gost.coordinate_overflow"));
}
var output = new byte[coordinateLength];
trimmed.CopyTo(output.AsSpan(coordinateLength - trimmed.Length));
return output;
}
private static ReadOnlySpan<byte> TrimLeadingZeros(ReadOnlySpan<byte> value)
{
var index = 0;
while (index < value.Length - 1 && value[index] == 0)
{
index++;
}
return value[index..];
}
}