using System; using System.Security.Cryptography; using FrostFS.Refs; using FrostFS.SDK.ClientV2.Mappers.GRPC; using FrostFS.SDK.Cryptography; using FrostFS.SDK.ProtosV2.Interfaces; using FrostFS.Session; using Google.Protobuf; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Signers; using Org.BouncyCastle.Math; namespace FrostFS.SDK.ClientV2; public static class Verifier { public const int RFC6979SignatureSize = 64; private static BigInteger[] DecodeSignature(byte[] sig) { if (sig.Length != RFC6979SignatureSize) throw new FormatException($"Wrong signature size, expect={RFC6979SignatureSize}, actual={sig.Length}"); var rs = new BigInteger[2]; rs[0] = new BigInteger(1, sig[..32]); rs[1] = new BigInteger(1, sig[32..]); return rs; } public static bool VerifyRFC6979(this byte[] publicKey, byte[] data, byte[] sig) { if (publicKey is null || data is null || sig is null) return false; var rs = DecodeSignature(sig); var digest = new Sha256Digest(); var signer = new ECDsaSigner(new HMacDsaKCalculator(digest)); var secp256R1 = SecNamedCurves.GetByName("secp256r1"); var ecParameters = new ECDomainParameters(secp256R1.Curve, secp256R1.G, secp256R1.N); var bcPublicKey = new ECPublicKeyParameters(secp256R1.Curve.DecodePoint(publicKey), ecParameters); var hash = new byte[digest.GetDigestSize()]; digest.BlockUpdate(data, 0, data.Length); digest.DoFinal(hash, 0); signer.Init(false, bcPublicKey); return signer.VerifySignature(hash, rs[0], rs[1]); } public static bool VerifyRFC6979(this SignatureRFC6979 signature, IMessage message) { if (signature is null) { throw new ArgumentNullException(nameof(signature)); } return signature.Key.ToByteArray().VerifyRFC6979(message.ToByteArray(), signature.Sign.ToByteArray()); } public static bool VerifyData(this ECDsa key, byte[] data, byte[] sig) { if (key is null) throw new ArgumentNullException(nameof(key)); if (data is null) throw new ArgumentNullException(nameof(data)); if (sig is null) throw new ArgumentNullException(nameof(sig)); return key.VerifyHash(data.Sha512(), sig[1..]); } public static bool VerifyMessagePart(this Signature sig, IMessage data) { if (sig is null || sig.Key is null || sig.Sign is null) return false; using var key = sig.Key.ToByteArray().LoadPublicKey(); var data2Verify = data is null ? [] : data.ToByteArray(); return key.VerifyData(data2Verify, sig.Sign.ToByteArray()); } internal static bool VerifyMatryoskaLevel(IMessage body, IMetaHeader meta, IVerificationHeader verification) { if (!verification.MetaSignature.VerifyMessagePart(meta)) return false; var origin = verification.GetOrigin(); if (!verification.OriginSignature.VerifyMessagePart(origin)) return false; if (origin is null) return verification.BodySignature.VerifyMessagePart(body); return verification.BodySignature is null && VerifyMatryoskaLevel(body, meta.GetOrigin(), origin); } public static bool Verify(this IVerifiableMessage message) { if (message is null) { throw new ArgumentNullException(nameof(message)); } return VerifyMatryoskaLevel(message.GetBody(), message.GetMetaHeader(), message.GetVerificationHeader()); } internal static void CheckResponse(IResponse resp) { if (!resp.Verify()) throw new FormatException($"invalid response, type={resp.GetType()}"); var status = resp.MetaHeader.Status.ToModel(); if (status != null && !status.IsSuccess) throw new ResponseException(status); } /// /// This method is intended for unit tests for request verification. /// /// Created by SDK request to gRpc proxy public static void CheckRequest(IRequest request) { if (request is null) { throw new ArgumentNullException(nameof(request)); } if (!request.Verify()) throw new FormatException($"invalid response, type={request.GetType()}"); } }