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.Locale;
031import java.util.UUID;
032
033public class RecurlyJs {
034
035    private static final Logger log = LoggerFactory.getLogger(RecurlyJs.class);
036
037    // Specific to signature generation
038    public static final String PARAMETER_FORMAT = "%s=%s";
039    public static final String PARAMETER_SEPARATOR = "&";
040    public static final String NONCE_PARAMETER = "nonce";
041    public static final String TIMESTAMP_PARAMETER = "timestamp";
042
043    /**
044     * Get Recurly.js Signature
045     * See spec here: https://docs.recurly.com/deprecated-api-docs/recurlyjs/signatures
046     * <p>
047     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
048     *
049     * @param privateJsKey recurly.js private key
050     * @return signature string on success, null otherwise
051     */
052    public static String getRecurlySignature(String privateJsKey) {
053        return getRecurlySignature(privateJsKey, new ArrayList<String>());
054    }
055
056    /**
057     * Get Recurly.js Signature
058     * See spec here: https://docs.recurly.com/deprecated-api-docs/recurlyjs/signatures
059     * <p>
060     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
061     *
062     * @param privateJsKey recurly.js private key
063     * @param extraParams extra parameters to include in the signature
064     * @return signature string on success, null otherwise
065     */
066    public static String getRecurlySignature(String privateJsKey, List<String> extraParams) {
067        final long unixTime = System.currentTimeMillis() / 1000L;
068        final String uuid = UUID.randomUUID().toString().replaceAll("-", "");
069        return getRecurlySignature(privateJsKey, unixTime, uuid, extraParams);
070    }
071
072    /**
073     * Get Recurly.js Signature with extra parameter strings in the format "[param]=[value]"
074     * See spec here: https://docs.recurly.com/deprecated-api-docs/recurlyjs/signatures
075     * <p>
076     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
077     *
078     * @param privateJsKey recurly.js private key
079     * @param unixTime Unix timestamp, i.e. elapsed seconds since Midnight, Jan 1st 1970, UTC
080     * @param nonce A randomly generated string (number used only once)
081     * @param extraParams extra parameters to include in the signature
082     * @return signature string on success, null otherwise
083     */
084    public static String getRecurlySignature(String privateJsKey, Long unixTime, String nonce, List<String> extraParams) {
085        // Mandatory parameters shared by all signatures (as per spec)
086        extraParams = (extraParams == null) ? new ArrayList<String>() : extraParams;
087        extraParams.add(String.format(Locale.ROOT, PARAMETER_FORMAT, TIMESTAMP_PARAMETER, unixTime));
088        extraParams.add(String.format(Locale.ROOT, PARAMETER_FORMAT, NONCE_PARAMETER, nonce));
089        String protectedParams = Joiner.on(PARAMETER_SEPARATOR).join(extraParams);
090
091        return generateRecurlyHMAC(privateJsKey, protectedParams) + "|" + protectedParams;
092    }
093
094    /**
095     * HMAC-SHA1 Hash Generator - Helper method
096     * <p>
097     * Returns a signature key for use with recurly.js BuildSubscriptionForm.
098     *
099     * @param privateJsKey recurly.js private key
100     * @param protectedParams protected parameter string in the format: &lt;secure_hash&gt;|&lt;protected_string&gt;
101     * @return subscription object on success, null otherwise
102     */
103    private static String generateRecurlyHMAC(String privateJsKey, String protectedParams) {
104        try {
105            SecretKey sk = new SecretKeySpec(privateJsKey.getBytes(), "HmacSHA1");
106            Mac mac = Mac.getInstance("HmacSHA1");
107            mac.init(sk);
108            byte[] result = mac.doFinal(protectedParams.getBytes("UTF-8"));
109            return BaseEncoding.base16().encode(result).toLowerCase();
110        } catch (Exception e) {
111            log.error("Error while trying to generate Recurly HMAC signature", e);
112            return null;
113        }
114    }
115
116}