Skip to main content

Validate the Digital Signature for Payment Pages 2.0

Zuora

Validate the Digital Signature for Payment Pages 2.0

When you implement a Payment Page with the Submit button outside, you need to validate the digital signature that Zuora returns to your callback page. Verify if the signature is a valid response from Zuora and if it is not expired. Zuora supports two types of digital signature for Payment Pages 2.0: basic and advanced. You use the client parameter, signatureType, to specify which type of signature to use. The Basic signature is used by default.

Validate the Basic Digital Signature

For the basic signature, when the callback occurs, Zuora signs the tenant ID, Payment Page ID, a generated Token, and a timestamp with the private key, and add the signature to the callback URL. On the client side, during the callback processing, the client uses the public key to verify the signature.

The basic signature is generated with the following parts, delimited by the “#” character.

  • Callback path
  • Tenant ID
  • Token
  • Timestamp when the signature is generated
  • Payment Page ID

To validate the Basic digital signature response for a Payment Page:

  1. Include the Zuora security library, SignatureDecrypter.
  2. Parse the callback parameters returned from Zuora and get the signature from the parameters.
  3. Decrypt the signature with the SignatureDescrypter method. See Obtain the Public Key for Payment Pages 2.0 for getting the public key.
    Alternatively, you can use a Zuora REST call to decrypt the signature. See Decrypt RSA signature for detailed information.
  4. Parse the decrypted signature.
  5. Validate if the signature is trusted.
  6. Check if the signature timestamp is expired, i.e. the difference between the current time and the signature timestamp is more than 5 minutes.

The following code shows a sample implementation of the verification steps.

//Step 1: Include the Zuora library
com.zuora.rsa.security.decrypt.SignatureDecrypter;

//Step 2: Get the signature from the callback parameter.
String signature = request.getParameter("signature");

// Step 3: Decrypt the signature.
String decryptedSignature = SignatureDecrypter.decryptAsString(signature, publicKeyString);

// Step 4: Parse the decrypted signature and get the timestamp when this signature was generated.    
StringTokenizer st = new StringTokenizer(decryptedSignature,"#");
String url = st.nextToken();
String tenanId = st.nextToken();
String token = st.nextToken();
String timestamp = st.nextToken();
String pageId = st.nextToken();

// Step 5: Validate signature for the page ID and tenant ID.
if(pageId != myPaymentPageID || tenantId != myTenantID) {
   // the signature is invalid.
   throw new Exception("Page Id in signature is invalid.");  
}

// Step 6: Validate that the signature is not expired.
long expiredTime = 5 * 60 * 1000; 
if((new Date()).getTime() > (Long.parseLong(timestamp) + expiredTime)) {
    // signature is expired.
    throw new Exception("Signature is expired.");
}

See the validSignature function in the Payment Pages 2.0 sample code suite on Zuora GitHub site for the complete example of signature verification.

Validate the Advanced Digital Signature

For added security, you can use the Advanced digital signature for Payment Pages 2.0. For the Advanced signature, Zuora creates the signature with Tenant ID, Page ID, Token, Timestamp, as well as all pass-through URL parameters, encrypted Payment Method ID, and Error Code. Upon receiving the signature, the client verifies the signature using the public key, and comparing the values with the parameter values on the URL. Furthermore, the client will decrypt the Payment Method ID as well.

This approach will ensure the following:

  • The Payment Method ID will not be passed over HTTPS in plain text
  • Since pass-through parameters, Payment Method ID and Error Code are signed with the signature, once the signature is verified, the client can trust the values on the URL to be originated from Zuora. 

The advanced signature will be generated using algorithm SHA512withRSA over a string that is concatenated with the following parts, delimited by the “#” character.

  • CallBack Path
  • Tenant ID
  • Host Page ID
  • Token (32 chars)
  • Timestamp (The time when we generate the signature)
  • Error Code
  • Passthrough parameters (field_passthrough[1,2,3,4,5])

    Note: Though up to 15 passthrough parameters can be supported when passing in your client parameters, only the first 5 parameters are used for signature generation and validation.

  • Payment Method ID signed with Private Key

To validate the Advanced digital signature response for a Payment Page:

  1. Include the Zuora Security library 1.1 (zuora-rsa-security-1.1.jar).
    • com.zuora.rsa.security.decrypt.SignatureDecrypter
    • com.zuora.rsa.security.decrypt.FieldDecrypter
  2. Parse the callback parameters returned from Zuora and get the signature.
  3. Decrypt the Payment Page ID and the Payment Method using the public key. See Obtain the Public Key for Payment Pages 2.0 for getting the public key.
  4. Construct a string with the following values returned in the callback URL:
    • Callback URL
    • Tenant ID
    • Token
    • Timestamp
    • Decrypted pageId
    • Error code
    • Passthrough parameters (field_passthrough[1,2,3,4,5])

      Note: Though up to 15 passthrough parameters can be supported when passing in your client parameters, only the first 5 parameters are used for signature generation and validation.

    • Decrypted payment method id
  5. Use the SignatureDecrypter.verifyAdvancedSignature method to validate the signature and compare the signature against the parameter values on the URL.
  6. Check if the signature timestamp is expired, i.e. the difference between the current time and the signature timestamp is more than 5 minutes.

The following code shows a sample implementation of the verification steps. This sample code to validate the advanced signature can be found in the latest version of the Payment Pages 2.0 sample code suite on Zuora GitHub site.

//Step 1: Include the Zuora Security library 1.1 (zuora-rsa-security-1.1.jar)
com.zuora.rsa.security.decrypt.SignatureDecrypter;
com.zuora.rsa.security.decrypt.FieldDecrypter;
 
//Step 2: Get the signature from the callback parameter.
String signature = new String(request.getParameter("signature").getBytes("iso-8859-1"), "UTF-8"); //Make sure using correct character encoding standard.
 
//Step 3: Construct the string to be encrypted.
String DELIM="#";
String timestamp = request.getParameter("timestamp");
String pageId = FieldDecrypter.decrypt(request.getParameter("pageId"), publicKeyString );
String paymentMethodId = FieldDecrypter.decrypt(request.getParameter("refId"), publicKeyString);
StringBuilder encryptedString = new StringBuilder();
encryptedString.append( "/hpm2samplecodejsp/webcontent/callback.jsp"); //Use your callback url instead in your page configration
encryptedString.append( DELIM + request.getParameter("tenantId") );
encryptedString.append( DELIM + request.getParameter("token"));
encryptedString.append( DELIM + timestamp);
encryptedString.append( DELIM + pageId);
encryptedString.append( DELIM + (request.getParameter("errorCode") == null?"":request.getParameter("errorCode") ));
encryptedString.append( DELIM + (request.getParameter("field_passthrough1") == null? "":request.getParameter("field_passthrough1")));
encryptedString.append( DELIM + (request.getParameter("field_passthrough2") == null? "":request.getParameter("field_passthrough2")));
encryptedString.append( DELIM + (request.getParameter("field_passthrough3") == null? "":request.getParameter("field_passthrough3")));
encryptedString.append( DELIM + (request.getParameter("field_passthrough4") == null? "":request.getParameter("field_passthrough4")));
encryptedString.append( DELIM + (request.getParameter("field_passthrough5") == null? "":request.getParameter("field_passthrough5")));
encryptedString.append( DELIM + paymentMethodId );

//Step 4: Verify the signature. (Need zuora-rsa-security-1.1.jar )
boolean isSignatureValid = false;
isSignatureValid = SignatureDecrypter.verifyAdvancedSignature(signature, encryptedString.toString(), publicKeyString);
if( !isSignatureValid ){
    // signature is invalid.
    throw new Exception("Signature is invalid."); 
}
 
//Step 5: Validate that the signature is not expired.
long expiredTime = 5 * 60 * 1000;
if((new Date()).getTime() > (Long.parseLong(timestamp) + expiredTime)) {
    // signature is expired.
    throw new Exception("Signature is expired.");
}