package org.jboss.resteasy.security.doseta;
import org.jboss.resteasy.security.SigningAlgorithm;
import org.jboss.resteasy.security.doseta.i18n.Messages;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.util.ParameterParser;
import javax.ws.rs.core.MultivaluedMap;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DKIMSignature
{
public static final String DKIM_SIGNATURE = "DKIM-Signature";
public static final String TIMESTAMP = "t";
public static final String DOMAIN = "d";
public static final String EXPIRATION = "x";
public static final String ALGORITHM = "a";
public static final String SIGNATURE = "b";
public static final String = "h";
public static final String IDENTITY = "i";
public static final String VERSION = "v";
public static final String BODY_HASH = "bh";
public static final String CANONICALIZATION = "c";
public static final String QUERY = "q";
public static final String SELECTOR = "s";
public static final String LENGTH = "l";
public static String DEFAULT_SIGNER = "DEFAULT_SIGNER";
public static final String SHA256WITH_RSA = "SHA256withRSA";
public static String DEFAULT_ALGORITHM = SHA256WITH_RSA;
protected PrivateKey privateKey;
protected Map<String, String> attributes = new LinkedHashMap<String, String>();
protected List<String> = new ArrayList<String>();
protected byte[] signature;
protected String ;
protected boolean bodyHashRequired = true;
public DKIMSignature()
{
}
public DKIMSignature(final Map<String, String> attrs)
{
attributes = attrs;
extractAttributes();
}
public DKIMSignature(final String headerValue)
{
this.headerValue = headerValue;
ParameterParser parser = new ParameterParser();
attributes = parser.parse(headerValue, ';');
extractAttributes();
}
protected void ()
{
String heads = attributes.get(HEADERS);
if (heads != null)
{
headers = Arrays.asList(heads.split(":"));
}
String sig = attributes.get(SIGNATURE);
if (sig != null) signature = Base64.getDecoder().decode(sig);
}
public List<String> ()
{
return headers;
}
public String toString()
{
return headerValue;
}
public boolean isBodyHashRequired()
{
return bodyHashRequired;
}
public void setBodyHashRequired(boolean bodyHashRequired)
{
this.bodyHashRequired = bodyHashRequired;
}
public void (String headerName)
{
headers.add(headerName);
}
public void setAttribute(String name, String value)
{
if (value == null)
{
attributes.remove(name);
}
attributes.put(name, value);
}
public void setAlgorithm(String value)
{
setAttribute(ALGORITHM, value);
}
public void setTimestamp(String value)
{
setAttribute(TIMESTAMP, value);
}
public void setTimestamp()
{
setAttribute(TIMESTAMP, ((new Date()).getTime() / 1000) + "");
}
public void setSelector(String selector)
{
setAttribute(SELECTOR, selector);
}
public String getSelector()
{
return attributes.get(SELECTOR);
}
public String getQuery()
{
return attributes.get(QUERY);
}
public void setQuery(String query)
{
setAttribute(QUERY, query);
}
public void setDomain(String domain)
{
setAttribute(DOMAIN, domain);
}
public String getDomain()
{
return attributes.get(DOMAIN);
}
public void setId(String id)
{
setAttribute(IDENTITY, id);
}
public void setExpiration(Date expire)
{
setAttribute(EXPIRATION, (expire.getTime() / 1000) + "");
}
public void setExpiration(int seconds, int minutes, int hours, int days, int months, int years)
{
Calendar now = Calendar.getInstance();
if (seconds > 0) now.add(Calendar.SECOND, seconds);
if (minutes > 0) now.add(Calendar.MINUTE, minutes);
if (hours > 0) now.add(Calendar.HOUR, hours);
if (days > 0) now.add(Calendar.DAY_OF_MONTH, days);
if (months > 0) now.add(Calendar.MONTH, months);
if (years > 0) now.add(Calendar.YEAR, years);
setExpiration(now.getTime());
}
public boolean isExpired()
{
String exp = attributes.get(EXPIRATION);
if (exp == null) return false;
long expL = Long.parseLong(exp);
return (expL * 1000) < (new Date()).getTime();
}
public boolean isStale(int seconds, int minutes, int hours, int days, int months, int years)
{
String time = attributes.get(TIMESTAMP);
if (time == null) return true;
long timeL = Long.parseLong(time);
Date timestamp = new Date(timeL * 1000L);
Calendar expires = Calendar.getInstance();
expires.setTime(timestamp);
if (seconds > 0) expires.add(Calendar.SECOND, seconds);
if (minutes > 0) expires.add(Calendar.MINUTE, minutes);
if (hours > 0) expires.add(Calendar.HOUR, hours);
if (days > 0) expires.add(Calendar.DAY_OF_MONTH, days);
if (months > 0) expires.add(Calendar.MONTH, months);
if (years > 0) expires.add(Calendar.YEAR, years);
return (new Date()).getTime() > expires.getTime().getTime();
}
public String getId()
{
return attributes.get(IDENTITY);
}
public String getAlgorithm()
{
return attributes.get(ALGORITHM);
}
public Map<String, String> getAttributes()
{
return attributes;
}
public String getBased64Signature()
{
return attributes.get(SIGNATURE);
}
public void setBase64Signature(String signature)
{
setAttribute(SIGNATURE, signature);
}
public byte[] getSignature()
{
return signature;
}
public void setSignature(byte[] signature)
{
this.signature = signature;
}
public PrivateKey getPrivateKey()
{
return privateKey;
}
public void setPrivateKey(PrivateKey privateKey)
{
this.privateKey = privateKey;
}
public void sign(Map headers, byte[] body, PrivateKey defaultKey) throws SignatureException
{
PrivateKey key = privateKey == null ? defaultKey : privateKey;
if (key == null)
{
throw new SignatureException(Messages.MESSAGES.privateKeyIsNull());
}
attributes.put(VERSION, "1");
attributes.put(ALGORITHM, SigningAlgorithm.SHA256withRSA.getRfcNotation());
attributes.put(CANONICALIZATION, "simple/simple");
String algorithm = SigningAlgorithm.SHA256withRSA.getJavaSecNotation();
String hashAlgorithm = SigningAlgorithm.SHA256withRSA.getJavaHashNotation();
Signature signature = null;
try
{
signature = Signature.getInstance(algorithm);
signature.initSign(key);
}
catch (Exception e)
{
throw new SignatureException(e);
}
if (this.headers.size() > 0)
{
StringBuffer headerCat = new StringBuffer();
int count = 0;
for (int i = 0; i < this.headers.size(); i++)
{
String name = this.headers.get(i);
if (i > 0) headerCat.append(":");
headerCat.append(name);
}
attributes.put(HEADERS, headerCat.toString());
updateSignatureWithHeader(headers, signature);
}
if (body != null && bodyHashRequired)
{
String encodedBodyHash = calculateEncodedHash(body, hashAlgorithm);
attributes.put(BODY_HASH, encodedBodyHash);
}
StringBuffer dosetaBuffer = new StringBuffer();
boolean first = true;
for (Map.Entry<String, String> entry : attributes.entrySet())
{
if (first) first = false;
else dosetaBuffer.append(";");
dosetaBuffer.append(entry.getKey()).append("=").append(entry.getValue());
}
if (!first) dosetaBuffer.append(";");
dosetaBuffer.append("b=");
String dosetaHeader = dosetaBuffer.toString();
signature.update(dosetaHeader.getBytes());
byte[] signed = signature.sign();
setSignature(signed);
String base64Signature = Base64.getEncoder().encodeToString(signed);
dosetaHeader += base64Signature;
this.headerValue = dosetaHeader;
}
private String calculateEncodedHash(byte[] body, String hashAlgorithm) throws SignatureException
{
byte[] bodyHash = hash(body, hashAlgorithm);
return Base64.getEncoder().encodeToString(bodyHash);
}
private byte[] hash(byte[] body, String hashAlgorithm) throws SignatureException
{
MessageDigest digest = null;
try
{
digest = MessageDigest.getInstance(hashAlgorithm);
}
catch (Exception e)
{
throw new SignatureException(e);
}
int length = body.length;
if (attributes.containsKey(LENGTH))
{
length = Integer.parseInt(attributes.get(LENGTH));
}
byte[] bodyHash = null;
digest.update(body, 0, length);
bodyHash = digest.digest();
return bodyHash;
}
private MultivaluedMap<String, String> (Map transmittedHeaders, Signature signature) throws SignatureException
{
MultivaluedMap<String, String> verifiedHeaders = new MultivaluedMapImpl<String, String>();
List<String> list = this.headers;
Map<String, Integer> count = new HashMap<String, Integer>();
for (String name : list)
{
int index = 0;
if (count.containsKey(name))
{
index = count.get(name);
index++;
}
count.put(name, index);
Object v = transmittedHeaders.get(name);
if (v == null)
{
throw new SignatureException(Messages.MESSAGES.unableToFindHeader(name, (index > 0 ? "[" + index + "]" : "")));
}
if (v instanceof List)
{
List l = (List) v;
int i = l.size() - 1 - index;
if (i < 0)
{
throw new SignatureException(Messages.MESSAGES.unableToFindHeader(name, (index > 0 ? "[" + index + "]" : "")));
}
v = l.get(i);
}
else if (index > 0)
{
throw new SignatureException(Messages.MESSAGES.unableToFindHeader(name, (index > 0 ? "[" + index + "]" : "")));
}
String entry = name + ":" + v.toString() + "\r\n";
signature.update(entry.getBytes());
verifiedHeaders.add(name, v.toString());
}
return verifiedHeaders;
}
public MultivaluedMap<String, String> verify(Map headers, byte[] body, PublicKey key) throws SignatureException
{
return verify(true, headers, body, key);
}
public MultivaluedMap<String, String> verify(boolean bodyHashRequired, Map headers, byte[] body, PublicKey key) throws SignatureException
{
if (key == null) throw new SignatureException(Messages.MESSAGES.noKeyToVerifyWith());
String algorithm = getAlgorithm();
if (algorithm == null || !SigningAlgorithm.SHA256withRSA.getRfcNotation().toLowerCase().equals(algorithm.toLowerCase()))
{
throw new SignatureException(Messages.MESSAGES.unsupportedAlgorithm(algorithm));
}
Signature verifier = null;
try
{
verifier = Signature.getInstance(SigningAlgorithm.SHA256withRSA.getJavaSecNotation());
verifier.initVerify(key);
}
catch (Exception e)
{
throw new SignatureException(e);
}
String encodedBh = attributes.get("bh");
if (encodedBh == null)
{
if (body != null && bodyHashRequired) throw new SignatureException(Messages.MESSAGES.thereWasNoBodyHash());
}
else
{
byte[] bh = hash(body, SigningAlgorithm.SHA256withRSA.getJavaHashNotation());
byte[] enclosedBh = null;
enclosedBh = Base64.getDecoder().decode(encodedBh);
if (Arrays.equals(bh, enclosedBh) == false)
{
throw new SignatureException(Messages.MESSAGES.bodyHashesDoNotMatch());
}
}
MultivaluedMap<String, String> verifiedHeaders = updateSignatureWithHeader(headers, verifier);
ParameterParser parser = new ParameterParser();
String strippedHeader = parser.setAttribute(headerValue.toCharArray(), 0, headerValue.length(), ';', "b", "");
verifier.update(strippedHeader.getBytes());
if (verifier.verify(getSignature()) == false)
{
throw new SignatureException(Messages.MESSAGES.failedToVerifySignature());
}
return verifiedHeaders;
}
}