001/*
002 * Copyright 2010-2014 Ning, Inc.
003 * Copyright 2014-2015 The Billing Project, LLC
004 *
005 * The Billing Project licenses this file to you under the Apache License, version 2.0
006 * (the "License"); you may not use this file except in compliance with the
007 * License.  You may obtain a copy of the License at:
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
014 * License for the specific language governing permissions and limitations
015 * under the License.
016 */
017
018package com.ning.billing.recurly;
019
020import com.google.common.base.Joiner;
021import com.google.common.io.BaseEncoding;
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import javax.crypto.Mac;
026import javax.crypto.SecretKey;
027import javax.crypto.spec.SecretKeySpec;
028import java.util.ArrayList;
029import java.util.List;
030import java.util.UUID;
031
032public class RecurlyJs {
033
034    private static final Logger log = LoggerFactory.getLogger(RecurlyJs.class);
035
036    // Specific to signature generation
037    public static final String PARAMETER_FORMAT = "%s=%s";
038    public static final String PARAMETER_SEPARATOR = "&";
039    public static final String NONCE_PARAMETER = "nonce";
040    public static final String TIMESTAMP_PARAMETER = "timestamp";
041
042    /**
043     * Get Recurly.js Signature
044     * See spec here: https://docs.recurly.com/deprecated-api-docs/recurlyjs/signatures
045     * <p>
046     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
047     *
048     * @param privateJsKey recurly.js private key
049     * @return signature string on success, null otherwise
050     */
051    public static String getRecurlySignature(String privateJsKey) {
052        return getRecurlySignature(privateJsKey, new ArrayList<String>());
053    }
054
055    /**
056     * Get Recurly.js Signature
057     * See spec here: https://docs.recurly.com/deprecated-api-docs/recurlyjs/signatures
058     * <p>
059     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
060     *
061     * @param privateJsKey recurly.js private key
062     * @param extraParams extra parameters to include in the signature
063     * @return signature string on success, null otherwise
064     */
065    public static String getRecurlySignature(String privateJsKey, List<String> extraParams) {
066        final long unixTime = System.currentTimeMillis() / 1000L;
067        final String uuid = UUID.randomUUID().toString().replaceAll("-", "");
068        return getRecurlySignature(privateJsKey, unixTime, uuid, extraParams);
069    }
070
071    /**
072     * Get Recurly.js Signature with extra parameter strings in the format "[param]=[value]"
073     * See spec here: https://docs.recurly.com/deprecated-api-docs/recurlyjs/signatures
074     * <p>
075     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
076     *
077     * @param privateJsKey recurly.js private key
078     * @param unixTime Unix timestamp, i.e. elapsed seconds since Midnight, Jan 1st 1970, UTC
079     * @param nonce A randomly generated string (number used only once)
080     * @param extraParams extra parameters to include in the signature
081     * @return signature string on success, null otherwise
082     */
083    public static String getRecurlySignature(String privateJsKey, Long unixTime, String nonce, List<String> extraParams) {
084        // Mandatory parameters shared by all signatures (as per spec)
085        extraParams = (extraParams == null) ? new ArrayList<String>() : extraParams;
086        extraParams.add(String.format(PARAMETER_FORMAT, TIMESTAMP_PARAMETER, unixTime));
087        extraParams.add(String.format(PARAMETER_FORMAT, NONCE_PARAMETER, nonce));
088        String protectedParams = Joiner.on(PARAMETER_SEPARATOR).join(extraParams);
089
090        return generateRecurlyHMAC(privateJsKey, protectedParams) + "|" + protectedParams;
091    }
092
093    /**
094     * HMAC-SHA1 Hash Generator - Helper method
095     * <p>
096     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
097     *
098     * @param privateJsKey recurly.js private key
099     * @param protectedParams protected parameter string in the format: &lt;secure_hash&gt;|&lt;protected_string&gt;
100     * @return subscription object on success, null otherwise
101     */
102    private static String generateRecurlyHMAC(String privateJsKey, String protectedParams) {
103        try {
104            SecretKey sk = new SecretKeySpec(privateJsKey.getBytes(), "HmacSHA1");
105            Mac mac = Mac.getInstance("HmacSHA1");
106            mac.init(sk);
107            byte[] result = mac.doFinal(protectedParams.getBytes("UTF-8"));
108            return BaseEncoding.base16().encode(result).toLowerCase();
109        } catch (Exception e) {
110            log.error("Error while trying to generate Recurly HMAC signature", e);
111            return null;
112        }
113    }
114
115}