I have a webhook that sends a header, which needs to be verified. Below are some details:
Problem :
The Java method always returns false. The provided header and body are correct and should result as TRUE.
As per docs from the provider :
Signature = Base64(RSA512(WEBHOOK_PRIVATE_KEY, SHA512(eventBody)))
Public Key :
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----
Header :
YNYVgWsx3PdoEGq2nUFGLmE6tE2y0LCc/eWPSY+rAqK+8fcxrPN0SPGbTdAXQ9+v62T5akWaVRWKXc1YBWlZxhVTCa/Ou7FfjVPG6JIQNX3Lks3ZhW0k29bVKf7Qvjp7z8HM9s1D8ZC28HvpX15a4by7DpNKkQ6cLWMDtBvqY02FSO+L4Vq54GZoTrplYkqCYcI4/oWchYzMZMq4omIOuam2DXm5BLlZ7HCR/nhUyp5duJpYnWJCKEwOTh3zLm842r5Fa9humq9WKkkT+AgFxe95bG4F3p8XhsciXiaNgx8diKLF0aBklqJ6yA70vjIP92BHuEnvIl37RiSFiIvkYWvLpMc1LoPxWZvncaLUjlYSVT3zd/gCDPEn1Mu8wUogGt9npkc/eKMdrKefcjEIMrJoO0HMMREZcOpc72F0+RM4QCkMaQMmK4zq9cBF0E2bNaEabNDSWXIfx9fa2VuyGYa5GLmAPUQPYRv50n92IGFewxj9vFAWhca+uthvsqz3FekyHK+c9G1Wh9OScR2TQp9Lbe4LqlX4FGapQitmfDvKRJhjAVm0n5355+k1dRse4fGeXqd2EfledWUJ3egpmW1NlmWBr8d4PsruKYZnphEMn9F5F3Vyu2sCpBSvqmcMANXzyZP7u3lGsUpH4V2lM6nCeBiRcbfwyrFsJ6Q5dso=
RequestBody:
{"type":"TRANSACTION_STATUS_UPDATED","tenantId":"f4df1e73-ec68-53c5-aa92-1a2bc45900ef","timestamp":1671288284087,"data":{"id":"b96f37dd-0fe9-4aa0-853b-f7d39c2ddc52","createdAt":1671286112004,"lastUpdated":1671286112019,"assetId":"BTC_TEST","source":{"id":"","type":"UNKNOWN","name":"External","subType":""},"destination":{"id":"26","type":"VAULT_ACCOUNT","name":"55b49ae0-0f34-4b3c-8cf6-0094254261c2","subType":""},"amount":1.0E-5,"networkFee":1.41E-6,"netAmount":1.0E-5,"sourceAddress":"tb1qluc5wgms8kpu0tydu00590qryfan3969jvmc8e","destinationAddress":"tb1qyhfnsfe2dy8az3040yvx087qdfsw6yxk8pc7yj","destinationAddressDescription":"","destinationTag":"","status":"CONFIRMING","txHash":"15873dc631db22a1bd13c6adecf7fb63f8fbbecce36eb38d12df65573e83dfa9","subStatus":"PENDING_BLOCKCHAIN_CONFIRMATIONS","signedBy":[],"createdBy":"","rejectedBy":"","amountUSD":0.17,"addressType":"","note":"","exchangeTxId":"","requestedAmount":1.0E-5,"feeCurrency":"BTC_TEST","operation":"TRANSFER","customerRefId":null,"numOfConfirmations":2,"amountInfo":{"amount":"0.00001","requestedAmount":"0.00001","netAmount":"0.00001","amountUSD":"0.17"},"feeInfo":{"networkFee":"0.00000141"},"destinations":[],"externalTxId":null,"blockInfo":{"blockHeight":"2411637","blockHash":"00000000d45b7ebf40921cbc70fb6791985a0b256241757a28bf762be07478e8"},"signedMessages":[],"index":1}}
Java method that's called when webhook event is received :
public boolean matches(WebhookEvent body, String header){
try {
File publicKeyFile = new File("publicKey.pub");
byte[] bytes = PemUtils.parsePEMFile(publicKeyFile);
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
PublicKey publicKey = kf.generatePublic(spec);
ObjectMapper objectMapper = new ObjectMapper();
String message = objectMapper.writeValueAsString(body);
Signature verifier = Signature.getInstance("SHA512withRSA");
verifier.initVerify(publicKey);
verifier.update(message.getBytes());
boolean isVerified = verifier.verify(Base64.getDecoder().decode(header));
System.out.println("Verified: " + isVerified);
return isVerified;
} catch (Exception e){
log.error("Error while verifying signature : " + e.getMessage());
e.printStackTrace();
return false;
}
}
PemFile parser :
static byte[] parsePEMFile(File pemFile) throws IOException {
if (!pemFile.isFile() || !pemFile.exists()) {
throw new FileNotFoundException(String.format("The file '%s' doesn't exist.", pemFile.getAbsolutePath()));
}
PemReader reader = new PemReader(new FileReader(pemFile));
PemObject pemObject = reader.readPemObject();
byte[] content = pemObject.getContent();
reader.close();
return content;
}
Request POJO
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class WebhookEvent {
@JsonProperty(value = "type")
private FireblocksEventType eventType;
@JsonProperty(value = "tenantId")
private String tenantId;
@JsonProperty(value = "timestamp")
private long timestamp;
@JsonProperty(value = "data")
private TransactionDetailObject eventData;
}
TransactionDetailObject :
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class TransactionDetailObject {
private String id;
private String assetId;
private TransferPeerPathResponse source;
private TransferPeerPathResponse destination;
private BigDecimal requestedAmount;
private AmountInfo amountInfo;
private FeeInfo feeinfo;
private BigDecimal amount;
private BigDecimal netAmount;
private BigDecimal amountUSD;
private BigDecimal serviceFee;
private Boolean treatAsGrossAmount;
private BigDecimal networkFee;
private Long createdAt;
private Long lastUpdated;
private TransactionStatus status;
private String txHash;
private Long index;
private TransactionSubStatus subStatus;
private String sourceAddress;
private String destinationAddress;
private String destinationAddressDescription;
private String destinationTag;
private List<String> signedBy;
private String createdBy;
private String rejectedBy;
private String addressType;
private String note;
private String exchangeTxId;
private String feeCurrency;
private TransactionOperation operation;
private AmlScreeningResult amlScreeningResult;
private String customerRefId;
private Long numOfConfirmations;
private List<NetworkRecord>networkRecords;
private String replacedTxHash;
private String externalTxId;
private List<DestinationResponse>destinations;
private BlockInfo blockInfo;
private RewardsInfo rewardsInfo;
private AuthorizationInfo authorizationInfo;
private List<SignedMessage>signedMessages;
private Object extraParameters;
}