Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Example code:

Code Block
/*

...


* Copyright (c) 2017 LINK Mobility Oy. All rights reserved.

...


*/

...



package com.labyrintti.silverbullet.payment;

...



import java.io.UnsupportedEncodingException;

...


import java.net.URLEncoder;

...


import java.security.InvalidKeyException;

...


import java.security.MessageDigest;

...


import java.security.NoSuchAlgorithmException;

...


import java.util.Date;

...


import java.util.UUID;

...



import javax.crypto.Mac;

...


import javax.crypto.spec.SecretKeySpec;

...



import org.apache.commons.codec.binary.Base64;

...


import org.apache.commons.lang3.StringUtils;

...



/**

...


* <p>

...


* Authorization header for the HTTP requests to the LINK Payment API. Such header contains:

...


* <ul>

...


* <li>payment credentials identifying a Payment servie's partner</li>

...


* <li>information about the request: HTTP method, URL and payload(body)</li>,

...


* <li>unique identifiers for the header: timestamp and UUID</li>

...


* </ul>

...


* .

...


* </p>

...


*

...


*/

...


public class AuthorizationHeader {

...



private static final String HEADER_NAME = "Authorization";

...


/**

...


* The encoding used for the header

...


*/

...


private static final String ENCODING = "UTF-8";

...



/**

...


* Identifier for the MD5 algorithm as expected from {@link MessageDigest java.security.MessageDigest}

...


*/

...


private static final String MD5_ALGORITHM_NAME = "MD5";

...



/**

...


* Identifier for the HmacSHA256 algorithm as expected from {@link javax.crypto.Mac}

...


*/

...


private static final String HMAC_SHA256_ALGORITHM_NAME = "HmacSHA256";

...



/**

...


* The number of characters from the signature used in the header

...


*/

...


private static final int HEADER_SIGNATURE_LENGTH = 10;

...



/**

...


* The symbol used to separate the parts of the header's value

...


*/

...


private static final String HEADER_PARTS_SEPARATOR = ":";

...



/**

...


* A prefix used for the header's value

...


*/

...


private static final String HEADER_PREFIX = "hmac ";

...



/**

...


* A partner ID used to identify the partner

...


*/

...


private Integer partnerId;

...



/**

...


* An authentication secret key for the partner

...


*/

...


public String secretKey;

...



/**

...


* UNIX time (the number of seconds since Epoch UTC (January 01, 1970)) used to identify a request

...


*/

...


private long timestampInSeconds;

...



/**

...


* A UUID used to identify a request

...


*/

...


private UUID uuid;

...



/**

...


* The HTTP method of the request

...


*/

...


private String httpMethod;

...



/**

...


* The absolute URL of the request including any query parameters

...


*/

...


private String url;

...



/**

...


* The payload(body) of the request

...


*/

...


private String requestPayload;

...



/**

...


* Constructs AuthorizationHeader with the provided data and internally generated identifiers - timestamp (the

...


* current date) and UUID

...


*

...

 
* @param partnerId the id of the Payment service's partner

...


* @param secretKey the secret key used for authentication

...


* @param httpMethod the HTTP method of the request

...


* @param url the absolute URL of the request including any query parameters

...


* @param requestPayload the payload(body) of the request; the payload is not a required value and both null and

...


* empty string are accepted

...


*/

...


public AuthorizationHeader(Integer partnerId, String secretKey, String httpMethod, String url,

...

 String requestPayload) {

...


super();

...


this.partnerId = partnerId;

...


this.secretKey = secretKey;

...


this.httpMethod = httpMethod;

...


this.url = url;

...


this.requestPayload = requestPayload;

...



initHeaderIdentifiers();

...


}

...



/**

...


* Returns the authorization header value for the Payment API and returns it. The header value should be in follow

...


* the pattern: "hmac {partnerId}:{signature}:{nonce}:{timestamp}" where:

...


* <ul>

...


* <li>{partner} the partner id</li>

...


* <li>{signature} the first 10 characters of a Hmac Sha256 hash used as signature</li>

...


* <li>{nonce} a UUID that is unique for each header</li>

...


* <li>{timestamp} the partner id</li>

...


*

...

 
* @return the value of the authorization header

...


* @throws AuthorizationHeaderValueConstructionException in case the header value cannot be constructed

...


*/

...


public String getHeaderValue() throws AuthorizationHeaderValueConstructionException {

...


String headerValue = null;

...


try {

...


headerValue = constructHeaderValueFromSourceData();

...


} catch (InvalidKeyException | UnsupportedEncodingException | NoSuchAlgorithmException e) {

...


throw new AuthorizationHeaderValueConstructionException(e);

...


}

...


return headerValue;

...


}

...



/**

...


* Returns the name of the authorization header's name

...


*

...

 
* @return the name of the authorization header

...


*/

...


public String getHeaderName() {

...


return HEADER_NAME;

...


}

...



/**

...


* Constructs the authorization header value for the Payment API from the header's source data

...


*

...

 
* @return the value of the authorization header

...


* @throws UnsupportedEncodingException in case the {@link #ENCODING header's encoding} is not supported

...


* @throws NoSuchAlgorithmException in case any of the hashing algorithms used is not supported

...


* @throws InvalidKeyException in case the privateSecretKey is inappropriate

...


*/

...


private String constructHeaderValueFromSourceData() throws UnsupportedEncodingException, NoSuchAlgorithmException,

...

 InvalidKeyException {

...


String message = generateMessageToBeSigned();

...


String signature = sign(message);

...


String header = buildAuthorizationHeaderString(signature);

...



return header;

...


}

...



/**

...


* Initiates the values of the values that identify a concrete header: it's timestamp and UUID

...


*/

...


private void initHeaderIdentifiers() {

...


this.timestampInSeconds = new Date().getTime() / 1000;

...


this.uuid = UUID.randomUUID();

...


}

...



/**

...


* Generates a string (called "message") used to create signature for the header. This message is constructed from

...


* the request data, the {@link #partnerId partner ID}, the {@link #timestampInSeconds} and the {@link #uuid}

...


*

...

 
* @return a message used to create a header's signature

...


* @throws UnsupportedEncodingException in case the {@link #ENCODING} is not supported by the URL encoder or

...


* string-to-byte encoder

...


* @throws NoSuchAlgorithmException in case the hashing algorithm is not supported

...


*/

...


private String generateMessageToBeSigned() throws UnsupportedEncodingException, NoSuchAlgorithmException {

...


String upperCaseHttpMethodName = httpMethod.toUpperCase();

...


String timestampAsString = Long.toString(timestampInSeconds);

...


String lowerCaseUrl = url.toLowerCase();

...


String urlEncodedLowerCaseUrl = URLEncoder.encode(lowerCaseUrl, ENCODING);

...


String requestPayloadEncodedHash = StringUtils.EMPTY;

...


if (requestPayload != null && !StringUtils.isEmpty(requestPayload)) {

...


requestPayloadEncodedHash = getHashedAndEncodedRequestPayload();

...


}

...



String messageToBeSigned = partnerId + upperCaseHttpMethodName + urlEncodedLowerCaseUrl

...

 + timestampAsString + uuid + requestPayloadEncodedHash;

...



return messageToBeSigned;

...


}

...



/**

...


* Creates a HMAC signature string for the header

...


*

...

 
* @param message an input message to be signed

...


* @return the provided message hashed with HMAC-SHA256 using the {@link #privateSecretKey}

...


* @throws UnsupportedEncodingException in case the charset used to encode the message into a byte array

...


* @throws InvalidKeyException in case the {@link #privateSecretKey} is inappropriate for MAC

...


* @throws NoSuchAlgorithmException in case the selected MAC algorithm (HMAC-SHA256) is not supported

...


*/

...


private String sign(String message) throws UnsupportedEncodingException, InvalidKeyException,

...

 NoSuchAlgorithmException {

...


byte[] decodedSecretKeyByteArray = getDecodedSecretKeyAsByteArray();

...


byte[] messageByteArray = message.getBytes(ENCODING);

...



byte[] hash = createSha256Hash(decodedSecretKeyByteArray, messageByteArray);

...


String encodedHash = Base64.encodeBase64String(hash);

...



return encodedHash;

...


}

...



/**

...


* Builds a authorization header value using part of the provided signature by joining all the header parts (the

...


* {@link #partnerId partner ID}, the first characters of the signature, the {@link #uuid} and the

...


* {@link #timestampInSeconds}) separated by {@link AuthorizationHeader#HEADER_PARTS_SEPARATOR} and adding a

...


* {@link #HEADER_PREFIX prefix} to the string

...


*

...

 
* @param fullSignature the full signature for the header

...


* @return an authorization header that uses the provided signature

...


*/

...


private String buildAuthorizationHeaderString(String fullSignature) {

...


String timestampAsString = Long.toString(timestampInSeconds);

...


String headerSignature = fullSignature.substring(0, HEADER_SIGNATURE_LENGTH);

...


String partnerIdAsString = String.valueOf(partnerId);

...


String uuidString = uuid.toString();

...



String joinedHeaderParts = joinStringsWithSeparator(HEADER_PARTS_SEPARATOR, partnerIdAsString, headerSignature,

...

 uuidString, timestampAsString);

...


String headerValue = HEADER_PREFIX + joinedHeaderParts;

...


return headerValue;

...


}

...



/**

...


* Hashes and encodes the request payload (body) using MD5 and Base64 respectively

...


*

...

 
* @return the hashed and then encoded payload

...


* @throws NoSuchAlgorithmException in case the hash algorithm is not supported

...


* @throws UnsupportedEncodingException in case the encoding used for th header is not supported

...


*/

...


private String getHashedAndEncodedRequestPayload() throws NoSuchAlgorithmException, UnsupportedEncodingException {

...


byte[] requestPayloadHash = createMd5Hash(requestPayload);

...


String requestPayloadEncodedHash = Base64.encodeBase64String(requestPayloadHash);

...



return requestPayloadEncodedHash;

...


}

...



/**

...


* Returns the secret key decoded with Base64

...


*

...

 
* @return the decoded secret key

...


*/

...


private byte[] getDecodedSecretKeyAsByteArray() {

...


byte[] decodedSecretKey = Base64.decodeBase64(secretKey);

...



return decodedSecretKey;

...


}

...



/**

...


* Creates a MD5 hash from the provided input

...


*

...

 
* @param input the input string to hash

...


* @return the creates hash as binary data

...


* @throws NoSuchAlgorithmException in case the selected algorithm (MD4) is not supported

...


* @throws UnsupportedEncodingException in case the encoding of the input character set is not supported

...


*/

...


private static byte[] createMd5Hash(String input) throws NoSuchAlgorithmException, UnsupportedEncodingException {

...


MessageDigest md5Digest = MessageDigest.getInstance(MD5_ALGORITHM_NAME);

...


md5Digest.update(input.getBytes(ENCODING));

...


byte[] hash = md5Digest.digest();

...



return hash;

...


}

...



/**

...


* Creates HmacSHA256 hash of a message using the provided secret key

...


*

...

 
* @param secretKey a secret key used to create the hash

...


* @param message the message to be hashed and authenticated/validated later

...


* @return the created hash as binary data

...


* @throws NoSuchAlgorithmException in case the selected algorithm (HmacSHA256) is not supported

...


* @throws InvalidKeyException in case the provided key is inappropriate for the MAC

...


*/

...


private static byte[] createSha256Hash(byte[] secretKey, byte[] message) throws NoSuchAlgorithmException,

...

 InvalidKeyException {

...


Mac algorithm = Mac.getInstance(HMAC_SHA256_ALGORITHM_NAME);

...


SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, HMAC_SHA256_ALGORITHM_NAME);

...


algorithm.init(secretKeySpec);

...


byte[] hash = algorithm.doFinal(message);

...



return hash;

...


}

...



/**

...


* <p>

...


* Joins the elements of the provided array into a single String containing the provided list of elements.

...


* </p>

...


*

...


* @see StringUtils#join(Object[], String)

...


* @param separator the separator to use, null treated as ""

...


* @param stringsToJoin the varargs strings providing the values to join together. null elements are treated as ""

...


* @return the joined String, {@code null} if no strings to join are provided

...


*/

...


private static String joinStringsWithSeparator(String separator, String... stringsToJoin) {

...


String joined = StringUtils.join(stringsToJoin, separator);

...


return joined;

...


}

...



}