001/**
002 * Copyright (c) 2010-2019 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.restfb.util;
023
024import java.nio.charset.StandardCharsets;
025
026import javax.crypto.Mac;
027import javax.crypto.spec.SecretKeySpec;
028
029/**
030 * A collection of data-encoding utility methods.
031 * 
032 * @author Josef Gierbl
033 * @author Mikael Grev
034 * @author <a href="http://restfb.com">Mark Allen</a>
035 * @since 1.6.13
036 */
037public final class EncodingUtils {
038
039  /**
040   * Prevents instantiation.
041   */
042  private EncodingUtils() {}
043
044  /**
045   * Decodes a base64-encoded string, padding out if necessary.
046   * 
047   * @param base64
048   *          The base64-encoded string to decode.
049   * @return A decoded version of {@code base64}.
050   * @throws NullPointerException
051   *           If {@code base64} is {@code null}.
052   */
053  public static byte[] decodeBase64(String base64) {
054    if (base64 == null)
055      throw new NullPointerException("Parameter 'base64' cannot be null.");
056
057    String fixedBase64 = padBase64(base64);
058    return Base64.getDecoder().decode(fixedBase64);
059  }
060
061  private static String padBase64(String base64) {
062    String padding = "";
063    int remainder = base64.length() % 4;
064
065    if (remainder == 1)
066      padding = "===";
067    else if (remainder == 2)
068      padding = "==";
069    else if (remainder == 3)
070      padding = "=";
071
072    return base64 + padding;
073  }
074
075  /**
076   * Encodes a hex {@code byte[]} from given {@code byte[]}.
077   * 
078   * This function is equivalent to Apache commons-codec binary {@code new Hex().encode(byte[])}
079   * 
080   * @param data
081   *          The data to encode as hex.
082   * @return Hex-encoded {@code byte[]}
083   * @throws NullPointerException
084   *           If {@code data} is {@code null}.
085   */
086  public static byte[] encodeHex(final byte[] data) {
087    if (data == null)
088      throw new NullPointerException("Parameter 'data' cannot be null.");
089
090    final char[] toDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
091    final int l = data.length;
092    final char[] out = new char[l << 1];
093    for (int i = 0, j = 0; i < l; i++) {
094      out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
095      out[j++] = toDigits[0x0F & data[i]];
096    }
097
098    return new String(out).getBytes(StandardCharsets.UTF_8);
099  }
100
101  /**
102   * Generates an appsecret_proof for facebook.
103   * 
104   * See https://developers.facebook.com/docs/graph-api/securing-requests for more info
105   * 
106   * @param appSecret
107   *          The facebook application secret
108   * @param accessToken
109   *          The facebook access token
110   * @return A Hex encoded SHA256 Hash as a String
111   */
112  public static String encodeAppSecretProof(String appSecret, String accessToken) {
113    try {
114      byte[] key = appSecret.getBytes(StandardCharsets.UTF_8);
115      SecretKeySpec signingKey = new SecretKeySpec(key, "HmacSHA256");
116      Mac mac = Mac.getInstance("HmacSHA256");
117      mac.init(signingKey);
118      byte[] raw = mac.doFinal(accessToken.getBytes());
119      byte[] hex = encodeHex(raw);
120      return new String(hex, StandardCharsets.UTF_8);
121    } catch (Exception e) {
122      throw new IllegalStateException("Creation of appsecret_proof has failed", e);
123    }
124  }
125}