/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2017 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jpos.rest;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Validate REST API requests
*
* jPOS uses a combination of a consumer's secret key, received timestamp, optional nonce and payLoad hash
* to validate requests.
*/
public class APIAuthentication {
public static final String VERSION = "1.0";
public static final String HASH_ALGORITHM = "HmacSHA256";
private static final long FIVE_MINUTES = 5*60*1000L;
private APIAuthentication() { }
public static byte[] computeHash(APICredential cred, SecretKey secretKey, byte[]... payLoad) throws NoSuchAlgorithmException, InvalidKeyException {
return computeHash(secretKey, cred.getTimestamp(), cred.getNonce(), payLoad);
}
public static void validate (APICredential cred, SecretKey secretKey, byte[]... payLoad) throws NoSuchAlgorithmException, InvalidKeyException {
if (!VERSION.equals(cred.getVersion()))
throw new IllegalArgumentException ("unsupported.version");
assertNotNull(payLoad, "invalid.payLoad");
assertNotNull(secretKey, "invalid.secret");
assertNotNull(cred.getHash(), "invalid.null.hash");
assertTimestamp(cred.getTimestamp());
assertEquals(computeHash(secretKey, cred.getTimestamp(), cred.getNonce(), payLoad), cred.getHash(), "invalid.hash");
}
private static void assertTimestamp(long ts) {
long offset = Math.abs(System.currentTimeMillis() - ts);
if (offset > FIVE_MINUTES) {
throw new IllegalArgumentException ("invalid.timestamp");
}
}
private static void assertNotNull(Object obj, String message)
throws IllegalArgumentException {
if (obj == null) {
throw new IllegalArgumentException(message);
}
}
private static void assertEquals (byte[] a, byte[] b, String message) {
if (!Arrays.equals(a, b))
throw new IllegalArgumentException(message);
}
private static byte[] computeHash (SecretKey secretKey, long timestamp, String nonce, byte[]... payLoad)
throws InvalidKeyException, NoSuchAlgorithmException
{
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
mac.update(Long.toString(timestamp).getBytes());
if (nonce != null)
mac.update (nonce.getBytes());
for (byte[] p : payLoad) mac.update(p);
return mac.doFinal();
}
}