package de.otto.hmac.authentication; import static org.slf4j.LoggerFactory.getLogger; import static com.google.common.base.Strings.isNullOrEmpty; import static de.otto.hmac.HmacAttributes.X_HMAC_AUTH_DATE; import static de.otto.hmac.HmacAttributes.X_HMAC_AUTH_SIGNATURE; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAmount; 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 com.google.common.hash.HashCode; import com.google.common.hash.Hashing; import com.google.common.io.ByteSource; public class RequestSigningUtil { private static final Logger LOG = getLogger(RequestSigningUtil.class); public static boolean checkRequest(final WrappedRequest request, final String secretKey, final Clock clock) { if (!hasValidRequestTimeStamp(request, clock)) { return false; } final String requestSignature = getSignature(request); final String[] split = requestSignature.split(":"); final String sentSignature = split[1]; final String generatedSignature = createRequestSignature(request, secretKey); return generatedSignature.equals(sentSignature); } public static boolean hasValidRequestTimeStamp(final WrappedRequest request, final Clock clock) { final String requestTimeString = getDateFromHeader(request); if (requestTimeString == null || requestTimeString.isEmpty()) { LOG.error("Signierter Request enthält kein Datum."); return false; } final Instant serverTime = Instant.now(clock); final Instant requestTime = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(requestTimeString, Instant::from); final TemporalAmount fiveMinutes = Duration.ofMinutes(5); final boolean inRange = requestTime.isAfter(serverTime.minus(fiveMinutes)) && requestTime.isBefore(serverTime.plus(fiveMinutes)); if (!inRange) { LOG.warn("Zeitstempel ausserhalb Serverzeit. Server: " + serverTime + ". Request: " + requestTimeString + "."); } return inRange; } public static String createSignatureBase(final WrappedRequest request) { return createSignatureBase(request.getMethod(), getDateFromHeader(request), request.getRequestURI(), request.getBody()); } public static String createSignatureBase(final String method, final String dateHeader, final String requestUri, ByteSource body) { final StringBuilder builder = new StringBuilder(); builder.append(method).append("\n"); builder.append(dateHeader).append("\n"); builder.append(requestUri).append("\n"); builder.append(toMd5Hex(body)); return builder.toString(); } public static String createRequestSignature(final String method, final String dateHeader, final String requestUri, ByteSource body, final String secretKey) { final String signatureBase = createSignatureBase(method, dateHeader, requestUri, body); return createRequestSignature(signatureBase, secretKey); } public static String createRequestSignature(String signatureBase, String secretKey) { if (isNullOrEmpty(secretKey)) { throw new IllegalArgumentException("Secret Key provided to HMAC SigningUtils was null or empty."); } try { final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"); final Mac mac = Mac.getInstance("HmacSHA256"); mac.init(keySpec); final byte[] result = mac.doFinal(signatureBase.getBytes()); return encodeBase64WithoutLinefeed(result); } catch (final NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException("should never happen", e); } } public static String createRequestSignature(final WrappedRequest request, final String secretKey) { final String signatureBase = createSignatureBase(request); return createRequestSignature(signatureBase, secretKey); } protected static String encodeBase64WithoutLinefeed(byte[] result) { return Base64.encodeBase64String(result).trim(); } public static boolean hasSignature(final HttpServletRequest request) { return request.getHeader(X_HMAC_AUTH_SIGNATURE) != null; } public static String getSignature(final HttpServletRequest request) { return request.getHeader(X_HMAC_AUTH_SIGNATURE); } public static String getDateFromHeader(final HttpServletRequest request) { final String header = request.getHeader(X_HMAC_AUTH_DATE); if (header == null) { return ""; } return header; } public static String toMd5Hex(ByteSource byteSource) { try { HashCode md5 = byteSource.hash(Hashing.md5()); return md5.toString(); } catch (IOException e) { throw new RuntimeException("error evaluating md5 sum", e); } } }