/*
* Copyright (c) 2012. The Genome Analysis Centre, Norwich, UK
* MISO project contacts: Robert Davey, Mario Caccamo @ TGAC
* *********************************************************************
*
* This file is part of MISO.
*
* MISO is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MISO is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MISO. If not, see <http://www.gnu.org/licenses/>.
*
* *********************************************************************
*/
package uk.ac.bbsrc.tgac.miso.integration.util;
import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* uk.ac.bbsrc.tgac.miso.webapp.context
* <p/>
* Info
*
* @author Rob Davey
* @date 09/02/12
* @since 0.1.6
*/
public class SignatureHelper {
protected static final Logger log = LoggerFactory.getLogger(SignatureHelper.class);
public static final String PUBLIC_KEY = "MIIBtzCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZp;RV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoDgYQAAoGARBu0g4MdHVhU6NoSXMKDBFSX9KfkTwIOXM6GY3DhAWsQhejkAkxp8c0IpkKn+i+PQNM/2pntXLWxDGHQGhfJIwvP041SrRTCXtx8SJ59ima8Z6/my7N72pPvbeDcPjlshtp/oa6eHh9M4J18W5hI4HD6I6f4qnppP1rRYaZolhw=";
public static final String PRIVATE_KEY = "MIIBSwIBADCCASwGByqGSM44BAEwggEfAoGBAP1/U4EddRIpUt9KnC7s5Of2EbdSPO9EAMMeP4C2USZpRV1AIlH7WT2NWPq/xfW6MPbLm1Vs14E7gB00b/JmYLdrmVClpJ+f6AR7ECLCT7up1/63xhv4O1fnxqimFQ8E+4P208UewwI1VBNaFpEy9nXzrith1yrv8iIDGZ3RSAHHAhUAl2BQjxUjC8yykrmCouuEC/BYHPUCgYEA9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKLZl6Ae1UlZAFMO/7PSSoEFgIUOCrAiHXm+FJBM7QHMhBxanPAn3k=";
public static final String USER_HEADER = "x-user";
public static final String TIMESTAMP_HEADER = "x-timestamp";
public static final String SIGNATURE_HEADER = "x-signature";
public static final String URL_X_HEADER = "x-url";
public static final List<String> SIGNATURE_KEYWORDS = Arrays.asList(USER_HEADER,
TIMESTAMP_HEADER,
URL_X_HEADER);
private static final String DSA_ALGORITHM = "DSA";
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
public static String createSignature(Map<String, List<String>> headersAndParams, String url, String privateKey) throws Exception {
TreeMap<String, String> sortedHeaders = new TreeMap<String, String>();
for (String key : headersAndParams.keySet()) {
if (SIGNATURE_KEYWORDS.contains(key)) {
sortedHeaders.put(key, headersAndParams.get(key).get(0));
}
}
String sortedUrl = createSortedUrl(url, sortedHeaders);
log.debug("CREATING SIGNATURE FROM SUPPLIED HEADERS: " + sortedUrl + " :: " + privateKey);
return calculateHMAC(sortedUrl, privateKey);
}
public static String createSignatureFromRequest(HttpServletRequest request, String privateKey) throws Exception {
String sortedUrl = createSortedUrl(request);
log.debug("CREATING SIGNATURE FROM REQUEST: " + sortedUrl + " :: " + privateKey);
return calculateHMAC(sortedUrl, privateKey);
}
public static String createSortedUrl(String url, TreeMap<String, String> headersAndParams) {
// build the url with headers and params sorted
String params = "";
for (String key : headersAndParams.keySet()) {
if (params.length() > 0) {
params += "@";
}
params += key + "=" + headersAndParams.get(key).toString();
}
if (!url.endsWith("?") && !"".equals(params)) url += "?";
log.debug("COMPLETE URL: " + url + params);
return url + params;
}
public static String createSortedUrl(HttpServletRequest request) {
// use a TreeMap to sort the headers and parameters
TreeMap<String, String> headersAndParams = new TreeMap<String, String>();
// load header values we care about
Enumeration e = request.getHeaderNames();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
if (SIGNATURE_KEYWORDS.contains(key)) {
log.debug("FOUND HEADER: " + key);
headersAndParams.put(key, request.getHeader(key));
}
}
/*
TODO - put parameters back in - sign SI message payload?
// load parameters
for (Object key : request.getParameterMap().keySet()) {
log.debug("FOUND PARAMETER: " + (String)key);
String[] o = (String[]) request.getParameterMap().get(key);
headersAndParams.put((String) key, o[0]);
}
*/
return createSortedUrl(
request.getContextPath() + request.getServletPath() + request.getPathInfo(),
headersAndParams);
}
public static String calculateHMAC(String data, String key) throws java.security.SignatureException {
String result;
try {
//get an hmac_sha1 key from the raw key bytes
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), HMAC_SHA1_ALGORITHM);
// get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
// compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(data.getBytes());
// base64-encode the hmac
result = Base64.encodeBase64URLSafeString(rawHmac);
//result = Base64.encodeBase64String(rawHmac);
}
catch (Exception e) {
throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
}
return result;
}
@Deprecated
private static PublicKey decodePublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(DSA_ALGORITHM);
byte[] publicKeyBytes = Base64.decodeBase64(publicKey);
//EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
EncodedKeySpec publicKeySpec = new PKCS8EncodedKeySpec(publicKeyBytes);
return keyFactory.generatePublic(publicKeySpec);
}
@Deprecated
public static boolean validateSignature(String url, String signatureString, String apiKey) throws InvalidKeyException, Exception {
if (url == null || signatureString == null || apiKey == null) {
throw new InvalidKeyException("Cannot verify signature when url, signature or api key are null!");
}
log.debug("VALIDATING: " + url + " :: " + signatureString + " :: " + apiKey);
Signature signature = Signature.getInstance(HMAC_SHA1_ALGORITHM);
signature.initVerify(decodePublicKey(apiKey));
signature.update(url.getBytes());
try {
return signature.verify(Base64.decodeBase64(signatureString));
}
catch (SignatureException e) {
log.error("FAILED TO VERIFY SIGNATURE: " + signature.toString());
return false;
}
}
public static boolean validateSignature(HttpServletRequest request, String publicKey, String signature) throws InvalidKeyException, Exception {
if (request == null || signature == null || publicKey == null) {
throw new InvalidKeyException("Cannot verify signature when request or signature are null!");
}
log.debug("VERIFYING HMAC: " + signature + " vs " + createSignatureFromRequest(request, publicKey));
return signature.equals(createSignatureFromRequest(request, publicKey));
}
public static String generatePrivateUserKey(byte[] data) throws NoSuchAlgorithmException {
SecretKeySpec signingKey = new SecretKeySpec(data, DSA_ALGORITHM);
return Base64.encodeBase64URLSafeString(signingKey.getEncoded());
}
}