From 871702ff0ca505d9ae4973884710f442cd7d4a4e Mon Sep 17 00:00:00 2001 From: Osiris Team <59899645+Osiris-Team@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:22:56 +0100 Subject: [PATCH] 1.5 --- pom.xml | 2 +- src/main/java/com/osiris/payhook/PayHook.java | 15 ++++++------ .../osiris/payhook/WebhookEventHeader.java | 4 ++-- .../com/osiris/payhook/paypal/SSLUtil.java | 23 ++++++++++--------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index a3192f1..4985aac 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.osiris.payhook PayHook - 1.4 + 1.5 jar PayHook diff --git a/src/main/java/com/osiris/payhook/PayHook.java b/src/main/java/com/osiris/payhook/PayHook.java index e3e2042..0eafa04 100644 --- a/src/main/java/com/osiris/payhook/PayHook.java +++ b/src/main/java/com/osiris/payhook/PayHook.java @@ -80,14 +80,14 @@ public String validateAndGet(Map map, String key) throws ParseHe } /** - * See {@link #validateWebHookEvent(WebhookEvent)} (WebHookEvent)} for details. + * See {@link #validateWebhookEvent(WebhookEvent)} (WebHookEvent)} for details. * @param validId your webhooks valid id. Get it from here: https://developer.paypal.com/developer/applications/ * @param validTypes your webhooks valid types/names. Here is a full list: https://developer.paypal.com/docs/api-basics/notifications/webhooks/event-names/ * @param header the http messages header as string. * @param body the http messages body as string. */ - public void validateWebHookEvent(String validId, List validTypes, WebhookEventHeader header, String body) throws ParseBodyException, WebHookValidationException, CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, SignatureException, InvalidKeyException { - validateWebHookEvent(new WebhookEvent(validId, validTypes, header, body)); + public void validateWebhookEvent(String validId, List validTypes, WebhookEventHeader header, String body) throws ParseBodyException, WebHookValidationException, CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, SignatureException, InvalidKeyException { + validateWebhookEvent(new WebhookEvent(validId, validTypes, header, body)); } /** @@ -102,7 +102,7 @@ public void validateWebHookEvent(String validId, List validTypes, Webhoo * @throws WebHookValidationException if validation failed. IMPORTANT: MESSAGE MAY CONTAIN SENSITIVE INFORMATION! * @throws ParseBodyException */ - public void validateWebHookEvent(WebhookEvent event) throws WebHookValidationException, ParseBodyException, IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { + public void validateWebhookEvent(WebhookEvent event) throws WebHookValidationException, ParseBodyException, IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, SignatureException, InvalidKeyException { WebhookEventHeader header = event.getHeader(); // Check if the webhook types match @@ -149,8 +149,9 @@ public void validateWebHookEvent(WebhookEvent event) throws WebHookValidationExc String expectedSignature = String.format("%s|%s|%s|%s", transmissionId, transmissionTime, validWebhookId, SSLUtil.crc32(bodyString)); // Compare the encoded signature with the expected signature - String decodedSignature = SSLUtil.validateAndGetSignatureAsString(clientCerts, authAlgo, actualSignatureEncoded, expectedSignature); - if (decodedSignature!=null){ + String decodedSignature = SSLUtil.decodeTransmissionSignature(actualSignatureEncoded); + boolean isSigValid = SSLUtil.validateTransmissionSignature(clientCerts, authAlgo, actualSignatureEncoded, expectedSignature); + if (isSigValid){ String[] arrayDecodedSignature = decodedSignature.split("\\|"); // Split by | char header.setWebhookId(arrayDecodedSignature[2]); header.setCrc32(arrayDecodedSignature[3]); @@ -160,7 +161,7 @@ public void validateWebHookEvent(WebhookEvent event) throws WebHookValidationExc throw new WebHookValidationException("Provided webhook id("+header.getWebhookId()+") does not match the valid id("+event.getValidWebHookId()+")!"); } else - throw new WebHookValidationException("Transmission signature is not valid!"); + throw new WebHookValidationException("Transmission signature is not valid! Expected: '"+expectedSignature+"' Provided: '"+decodedSignature+"'"); } /** diff --git a/src/main/java/com/osiris/payhook/WebhookEventHeader.java b/src/main/java/com/osiris/payhook/WebhookEventHeader.java index f098242..2c79f93 100644 --- a/src/main/java/com/osiris/payhook/WebhookEventHeader.java +++ b/src/main/java/com/osiris/payhook/WebhookEventHeader.java @@ -37,7 +37,7 @@ public String getTimestamp() { /** * IMPORTANT: SINCE THE WEBHOOK ID IS INSIDE THE ENCRYPTED TRANSMISSION SIGNATURE, THIS RETURNS NULL - * UNLESS YOU SUCCESSFULLY EXECUTED {@link PayHook#validateWebHookEvent(WebhookEvent)} ONCE BEFORE! + * UNLESS YOU SUCCESSFULLY EXECUTED {@link PayHook#validateWebhookEvent(WebhookEvent)} ONCE BEFORE! * The ID of the webhook resource for the destination URL to which PayPal delivers the event notification. */ public String getWebhookId() { @@ -50,7 +50,7 @@ public void setWebhookId(String webhookId) { /** * IMPORTANT: SINCE THE CRC32 IS INSIDE THE ENCRYPTED TRANSMISSION SIGNATURE, THIS RETURNS NULL - * UNLESS YOU SUCCESSFULLY EXECUTED {@link PayHook#validateWebHookEvent(WebhookEvent)} ONCE BEFORE! + * UNLESS YOU SUCCESSFULLY EXECUTED {@link PayHook#validateWebhookEvent(WebhookEvent)} ONCE BEFORE! * The Cyclic Redundancy Check (CRC32) checksum for the body of the HTTP payload. */ public String getCrc32() { diff --git a/src/main/java/com/osiris/payhook/paypal/SSLUtil.java b/src/main/java/com/osiris/payhook/paypal/SSLUtil.java index 28d542d..1ebecda 100644 --- a/src/main/java/com/osiris/payhook/paypal/SSLUtil.java +++ b/src/main/java/com/osiris/payhook/paypal/SSLUtil.java @@ -149,27 +149,28 @@ public static long crc32(String data) { * * @param clientCerts Client Certificates * @param algo Algorithm used for signature creation by server - * @param actualSignatureEncoded Paypal-Transmission-Sig header value passed by server - * @param expectedSignature Signature generated by formatting data with CRC32 value of request body - * @return the decoded signature as string if it is valid, null otherwise + * @param actualEncodedSignature Paypal-Transmission-Sig header value passed by server + * @param expectedDecodedSignature Signature generated by formatting data with CRC32 value of request body + * @return Returns true if signature is valid * * @throws NoSuchAlgorithmException * @throws SignatureException * @throws InvalidKeyException */ - public static String validateAndGetSignatureAsString(Collection clientCerts, String algo, - String actualSignatureEncoded, String expectedSignature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { + public static boolean validateTransmissionSignature(Collection clientCerts, String algo, + String actualEncodedSignature, String expectedDecodedSignature) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException { // Get the signatureAlgorithm from the PAYPAL-AUTH-ALGO HTTP header Signature signatureAlgorithm = Signature.getInstance(algo); // Get the certData from the URL provided in the HTTP headers and cache it X509Certificate[] clientChain = clientCerts.toArray(new X509Certificate[0]); signatureAlgorithm.initVerify(clientChain[0].getPublicKey()); - signatureAlgorithm.update(expectedSignature.getBytes()); + signatureAlgorithm.update(expectedDecodedSignature.getBytes()); // Actual signature is base 64 encoded and available in the HTTP headers - byte[] actualSignature = Base64.decodeBase64(actualSignatureEncoded.getBytes()); - if (signatureAlgorithm.verify(actualSignature)) - return new String(actualSignature); - else - return null; + byte[] actualSignature = Base64.decodeBase64(actualEncodedSignature.getBytes()); + return signatureAlgorithm.verify(actualSignature); + } + + public static String decodeTransmissionSignature(String encodedSignature){ + return new String(Base64.decodeBase64(encodedSignature.getBytes())); } }