package info.frostfs.sdk.tools; import com.google.protobuf.Message; import frostfs.session.Types; import info.frostfs.sdk.constants.CryptoConst; import info.frostfs.sdk.exceptions.ProcessFrostFSException; import info.frostfs.sdk.exceptions.ResponseFrostFSException; import info.frostfs.sdk.exceptions.ValidationFrostFSException; import info.frostfs.sdk.mappers.response.ResponseStatusMapper; import info.frostfs.sdk.utils.MessageHelper; import org.apache.commons.codec.digest.DigestUtils; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.sec.SECObjectIdentifiers; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.HMacDSAKCalculator; import java.math.BigInteger; import java.security.PublicKey; import java.security.Signature; import java.util.Arrays; import static info.frostfs.sdk.KeyExtension.getPublicKeyFromBytes; import static info.frostfs.sdk.constants.CryptoConst.RFC6979_SIGNATURE_SIZE; import static info.frostfs.sdk.constants.ErrorConst.INVALID_RESPONSE; import static info.frostfs.sdk.constants.ErrorConst.WRONG_SIGNATURE_SIZE_TEMPLATE; import static info.frostfs.sdk.constants.FieldConst.*; import static java.util.Objects.isNull; import static org.bouncycastle.crypto.util.DigestFactory.createSHA256; import static org.bouncycastle.util.BigIntegers.fromUnsignedByteArray; public class Verifier { private Verifier() { } public static boolean verifyRFC6979(frostfs.refs.Types.SignatureRFC6979 signature, Message data) { return verifyRFC6979(signature.getKey().toByteArray(), data.toByteArray(), signature.getSign().toByteArray()); } public static boolean verifyRFC6979(byte[] publicKey, byte[] data, byte[] sig) { if (isNull(publicKey) || isNull(data) || isNull(sig)) { return false; } var rs = decodeSignature(sig); var digest = createSHA256(); var signer = new ECDSASigner(new HMacDSAKCalculator(digest)); var secp256R1 = SECNamedCurves.getByOID(SECObjectIdentifiers.secp256r1); var ecParameters = new ECDomainParameters(secp256R1.getCurve(), secp256R1.getG(), secp256R1.getN()); var ecPublicKey = new ECPublicKeyParameters(secp256R1.getCurve().decodePoint(publicKey), ecParameters); var hash = new byte[digest.getDigestSize()]; digest.update(data, 0, data.length); digest.doFinal(hash, 0); signer.init(false, ecPublicKey); return signer.verifySignature(hash, rs[0], rs[1]); } private static BigInteger[] decodeSignature(byte[] sig) { if (sig.length != RFC6979_SIGNATURE_SIZE) { throw new ValidationFrostFSException( String.format(WRONG_SIGNATURE_SIZE_TEMPLATE, RFC6979_SIGNATURE_SIZE, sig.length) ); } var rs = new BigInteger[2]; rs[0] = fromUnsignedByteArray(Arrays.copyOfRange(sig, 0, (RFC6979_SIGNATURE_SIZE / 2) - 1)); rs[1] = fromUnsignedByteArray( Arrays.copyOfRange(sig, RFC6979_SIGNATURE_SIZE / 2, RFC6979_SIGNATURE_SIZE - 1) ); return rs; } public static void checkResponse(Message response) { if (!verify(response)) { throw new ResponseFrostFSException(INVALID_RESPONSE); } var metaHeader = (Types.ResponseMetaHeader) MessageHelper.getField(response, META_HEADER_FIELD_NAME); if (isNull(metaHeader) || metaHeader.getSerializedSize() == 0) { return; } var status = ResponseStatusMapper.toModel(metaHeader.getStatus()); if (!status.isSuccess()) { throw new ResponseFrostFSException(status); } } public static boolean verify(Message response) { var body = (Message) MessageHelper.getField(response, BODY_FIELD_NAME); var metaHeader = (Types.ResponseMetaHeader) MessageHelper.getField(response, META_HEADER_FIELD_NAME); var verifyHeader = (Types.ResponseVerificationHeader) MessageHelper.getField(response, VERIFY_HEADER_FIELD_NAME); return verifyMatryoshkaLevel(body, metaHeader, verifyHeader); } public static boolean verifyMatryoshkaLevel(Message data, frostfs.session.Types.ResponseMetaHeader meta, frostfs.session.Types.ResponseVerificationHeader verification) { if (!verifyMessagePart(verification.getMetaSignature(), meta)) { return false; } var origin = verification.getOrigin(); if (!verifyMessagePart(verification.getOriginSignature(), origin)) { return false; } if (origin.getSerializedSize() == 0) { return verifyMessagePart(verification.getBodySignature(), data); } return verification.getBodySignature().getSerializedSize() == 0 && verifyMatryoshkaLevel(data, meta.getOrigin(), origin); } public static boolean verifyMessagePart(frostfs.refs.Types.Signature sig, Message data) { if (sig.getSerializedSize() == 0 || sig.getKey().isEmpty() || sig.getSign().isEmpty()) { return false; } var publicKey = getPublicKeyFromBytes(sig.getKey().toByteArray()); var data2Verify = data.getSerializedSize() == 0 ? new byte[]{} : data.toByteArray(); return verifyData(publicKey, data2Verify, sig.getSign().toByteArray()); } public static boolean verifyData(PublicKey publicKey, byte[] data, byte[] sig) { try { Signature signature = Signature.getInstance(CryptoConst.SIGNATURE_ALGORITHM); signature.initVerify(publicKey); signature.update(DigestUtils.sha512(data)); return signature.verify(Arrays.copyOfRange(sig, 1, sig.length)); } catch (Exception exp) { throw new ProcessFrostFSException(exp); } } }