Using the AWS KMS service to sign Ethereum, cannot get the correct R and S values from the returned signature

50 views Asked by At
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.kms.AWSKMS;
import com.amazonaws.services.kms.AWSKMSClientBuilder;
import com.amazonaws.services.kms.model.GetPublicKeyRequest;
import com.amazonaws.services.kms.model.SignRequest;
import com.amazonaws.util.BinaryUtils;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.junit.Test;
import org.web3j.crypto.ECDSASignature;
import org.web3j.crypto.Hash;
import org.web3j.crypto.Keys;
import org.web3j.crypto.Sign;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.\*;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

@Slf4j
public class AwsKmsTest {

    private static final String keyId = "****";
    
    AWSKMS kmsClient = AWSKMSClientBuilder.standard()
            .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("****", "*****")))
            .withRegion(Regions.AP_NORTHEAST_1)
            .build();
    
    
    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }
    
    @Test
    public void publicKey() throws Exception {
        BCECPublicKey publicKey = (BCECPublicKey) getPublicKey();
        System.out.println(Keys.getAddress(pubKeyToBigInter(publicKey)));
    }
    
    @Test
    public void sign() throws Exception {
        String msg = "hi";
        byte[] Bytes = msg.getBytes(StandardCharsets.UTF_8);
        byte[] digestBytes = MessageDigest.getInstance("SHA-256").digest(Bytes);
    
        SignRequest signRequest = new SignRequest();
        signRequest.setKeyId(keyId);
        signRequest.setMessage(ByteBuffer.wrap(digestBytes));
        signRequest.setMessageType("DIGEST");
        signRequest.setSigningAlgorithm("ECDSA_SHA_256");
        ByteBuffer signatureB = kmsClient.sign(signRequest).getSignature();
    
        BCECPublicKey publicKey = (BCECPublicKey) getPublicKey();
    
        Signature signature = Signature.getInstance("SHA256withECDSA");
        signature.initVerify(publicKey);
        signature.update(Bytes);
        if (signature.verify(signatureB.array())) {
            ECDSASignature ecdsaSignature = parseDERSequence(signatureB.array());
            //throw exception:Could not construct a recoverable key. Are your credentials valid?
            Sign.SignatureData signMessage = Sign.createSignatureData(ecdsaSignature,
                    pubKeyToBigInter(publicKey),
                    Hash.sha3(Bytes));
            System.out.println("0x" + Hex.toHexString(signMessage.getR()) + Hex.toHexString(signMessage.getS()) + Hex.toHexString(signMessage.getV()));
        }
    
    }
    
    public static BigInteger pubKeyToBigInter(BCECPublicKey publicKey) {
        byte[] publicKeyBytes = publicKey.getQ().getEncoded(false);
        return new BigInteger(1, Arrays.copyOfRange(publicKeyBytes, 1, publicKeyBytes.length));
    }
    
    public PublicKey getPublicKey() throws Exception {
        GetPublicKeyRequest publicKeyRequest = new GetPublicKeyRequest();
        publicKeyRequest.setKeyId(keyId);
        ByteBuffer publicKeyByteBuffer = kmsClient.getPublicKey(publicKeyRequest).getPublicKey();
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(BinaryUtils.copyBytesFrom(publicKeyByteBuffer));
        KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
        return keyFactory.generatePublic(keySpec);
    }
    
    
    private ECDSASignature parseDERSequence(byte[] derEncoded) throws IOException {
        ASN1InputStream asn1InputStream = new ASN1InputStream(derEncoded);
        ASN1Sequence asn1Sequence = (ASN1Sequence) asn1InputStream.readObject();
        ASN1Integer r = (ASN1Integer) asn1Sequence.getObjectAt(0);
        ASN1Integer s = (ASN1Integer) asn1Sequence.getObjectAt(1);
        BigInteger rValue = r.getPositiveValue();
        BigInteger sValue = s.getPositiveValue();
        return new ECDSASignature(rValue, sValue);
    }

}

Sign.createSignatureData(ecdsaSignature,pubKeyToBigInter(publicKey),Hash.sha3(Bytes)); throw a exception:Could not construct a recoverable key. Are your credentials valid?

It can be roughly confirmed that the method parseDERSequence returns incorrect f and s values, resulting in failure. Does anyone know how to solve it

1

There are 1 answers

0
carlos On

Make sure to specify MessageType: 'DIGEST' in the KMS.SignRequest, otherwise, AWS will try to hash your payload again which will generate an invalid signature. The payload needs to be a keccak256 hash of your transaction object.

byte[] Bytes = msg.getBytes(StandardCharsets.UTF_8); byte[] digestBytes = MessageDigest.getInstance("SHA-256").digest(Bytes);

Replace with

byte[] Bytes = Hash.sha3(msg.getBytes(StandardCharsets.UTF_8));