HttpSignature.java

package org.linkedopenactors.rdfpub.adapter.driven;

import java.io.IOException;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.tomitribe.auth.signatures.SigningAlgorithm;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class HttpSignature {

	private MessageDigest messageDigest;
	
	public HttpSignature() {
		try {
			messageDigest = MessageDigest.getInstance("SHA256");
		} catch (NoSuchAlgorithmException e) {
			throw new IllegalStateException("", e);
		}
	}
	
//    public String signature(String keyId, PrivateKey privateKey, String uriString, HttpMethod method) {
//    	URI uri = URI.create(uriString);
//    	final Map<String, String> headers = new HashMap<String, String>();
//    	headers.put("Host", uri.getAuthority());
//    	headers.put("Date", getCurrentDate());
//    	
//    	List<String> headerKeys = List.of("(request-target)", "host", "date");
//    	
//		final org.tomitribe.auth.signatures.Signature signature = new org.tomitribe.auth.signatures.Signature(keyId, SigningAlgorithm.HS2019.getAlgorithmName(), "RSA_SHA256", null, null, headerKeys);
//
//        final Key key = new SecretKeySpec(privateKey.getEncoded(), "HmacSHA256");
//    	
//    	final org.tomitribe.auth.signatures.Signer signer = new org.tomitribe.auth.signatures.Signer(key, signature);
//
//    	try {
//			final org.tomitribe.auth.signatures.Signature signed = signer.sign(method.toString(), uri.getPath(), headers);
//			return signed.toString();
//		} catch (IOException e) {
//			throw new IllegalStateException("unable to generate http signature.", e);
//		}    	
//    }

	
//	public SignatureResponse test(String keyId, PrivateKey privateKey, String uriString, HttpMethod method, String payload) {
//		try {
//			URI uri = URI.create(uriString);
//			
//	    	final Map<String, String> headers = new HashMap<>();
//	    	headers.put("host", uri.getAuthority());
//	    	headers.put("date", getCurrentDate());
//	    	headers.put("digest", getMessageDigest(payload));
//			
//			String toBeSigned = "(request-target): post " + uri.getPath();
//			toBeSigned += "\nhost: " + uri.getAuthority();
//			toBeSigned += "\ndate: " + getCurrentDate();
//			toBeSigned += "\ndigest: " + getMessageDigest(payload);
//			
//			log.debug("toBeSigned: " + toBeSigned);
//			
//			MessageDigest md = MessageDigest.getInstance("SHA256");
//			byte[] messageHash = md.digest(toBeSigned.getBytes());
//			
//			Signature sig = Signature.getInstance( "SHA256withRSA" );
//			sig.initSign(privateKey);
//			sig.update(messageHash);
//			byte[] encryptedMessageHash = sig.sign();
//			
//			String signature = Base64.getEncoder().encodeToString(encryptedMessageHash); // TODO encoder as instance var ?
//			String sigStr = "keyId=\"" + keyId + "\",headers=\"(request-target) host date digest\",signature=\"" + signature + "\"";
//			
//			headers.put("signature", sigStr);
//			SignatureResponse response = new SignatureResponse(headers/*, signature*/);
//			return response;
//		} catch (Exception e) {
//			throw new IllegalStateException("error signing", e);
//		}
//	}
	
	
    public SignatureResponse signature(String keyId, PrivateKey privateKey, String uriString, HttpMethod method, String payload) {
    	URI uri = URI.create(uriString);
    	final Map<String, String> headers = new HashMap<>();
    	headers.put("host", uri.getAuthority());
    	headers.put("date", getCurrentDate());
    	headers.put("digest", getMessageDigest(payload));
    	try {
        	List<String> headerKeys = List.of("(request-target)", "Host", "Date", "Digest");
        	final org.tomitribe.auth.signatures.Signature signatureTemplate = new org.tomitribe.auth.signatures.Signature(keyId, SigningAlgorithm.RSA_SHA256.getAlgorithmName(), "RSA_SHA256", null, null, headerKeys);
    		final org.tomitribe.auth.signatures.Signer signer = new org.tomitribe.auth.signatures.Signer(privateKey, signatureTemplate);
    		final org.tomitribe.auth.signatures.Signature signature = signer.sign(method.toString(), uri.getPath(), headers);
    		
			String signatureHeaderValue = signature.toString().substring("Signature ".length());
			headers.put("signature", signatureHeaderValue);
			SignatureResponse response = new SignatureResponse(headers/*, signature*/);
			return response;
		} catch (IOException e) {
			throw new IllegalStateException("unable to generate http signature.", e);
		}    	
    }

	private String getMessageDigest(String payload) {
		String digest;
    	try {
			byte[] messageHash = messageDigest.digest(payload.getBytes());
			digest = Base64.getEncoder().encodeToString(messageHash); // TODO encoder as instance var ?
			digest = "sha-256=" + digest;
			log.trace("payload: " + payload);
			log.debug("digest: " + digest);
		} catch (Exception e) {
			throw new IllegalStateException("unable to generate digest.", e);
		}
		return digest;
	}

	private String getCurrentDate() {
		Locale locale = Locale.forLanguageTag( "en-EN" );
		DateTimeFormatter df = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z")
				.withLocale(locale)
				.withZone(ZoneId.of("GMT"));
    	String date = LocalDateTime.now(ZoneId.of("GMT")).format(df);
		return date;
	}

	@Data
	public class SignatureResponse {
		
		private String host;
		private String date;
		private String digest;
		private String signatureString;
		
		public SignatureResponse(final Map<String, String> headers) {
			setHost(Optional.ofNullable(headers.get("host")).orElseThrow());
			setDate(Optional.ofNullable(headers.get("date")).orElseThrow());
			setDigest(Optional.ofNullable(headers.get("digest")).orElseThrow());
			setSignatureString(Optional.ofNullable(headers.get("signature")).orElseThrow());
		}
	}
	
	public String getMessageHash(String payloadDigest) {
		byte[] messageHash;
		try {
			messageHash = messageDigest.digest(payloadDigest.getBytes());
			return Base64.getEncoder().encodeToString(messageHash); // TODO encoder as instance var ?
		} catch (Exception e) {
			throw new IllegalStateException("unable to generate digest.", e);
		}
	}
}