package org.jboss.resteasy.security.doseta;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptorContext;
import org.jboss.resteasy.core.ResteasyContext;
import org.jboss.resteasy.security.doseta.i18n.LogMessages;
import org.jboss.resteasy.security.doseta.i18n.Messages;
import org.jboss.resteasy.spi.AsyncOutputStream;
import org.jboss.resteasy.spi.AsyncWriterInterceptor;
import org.jboss.resteasy.spi.AsyncWriterInterceptorContext;
import org.jboss.resteasy.spi.BlockingAsyncOutputStream;
@Provider
@Priority(Priorities.ENTITY_CODER)
public class DigitalSigningInterceptor implements AsyncWriterInterceptor, ContainerResponseFilter, ClientRequestFilter
{
protected List<DKIMSignature> (MultivaluedMap<String, Object> headers)
{
List<Object> signatures = headers.get(DKIMSignature.DKIM_SIGNATURE);
if (signatures == null || signatures.isEmpty())
{
return Collections.EMPTY_LIST;
}
List<DKIMSignature> list = new ArrayList<DKIMSignature>();
for (Object obj : signatures)
{
if (obj instanceof DKIMSignature)
{
list.add((DKIMSignature) obj);
}
}
return list;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException
{
if (requestContext.hasEntity())
{
return;
}
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
List<DKIMSignature> list = getHeaders(headers);
for (DKIMSignature dosetaSignature : list)
{
KeyRepository repository = (KeyRepository) requestContext.getProperty(KeyRepository.class.getName());
try
{
sign(repository, headers, null, dosetaSignature);
}
catch (Exception e)
{
throw new ProcessingException(e);
}
}
}
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException
{
if (responseContext.getEntity() != null)
{
return;
}
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
List<DKIMSignature> list = getHeaders(headers);
for (DKIMSignature dosetaSignature : list)
{
try
{
sign(null, headers, null, dosetaSignature);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}
@Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException
{
LogMessages.LOGGER.debugf("Interceptor : %s, Method : aroundWriteTo", getClass().getName());
MultivaluedMap<String, Object> headers = context.getHeaders();
List<DKIMSignature> list = getHeaders(headers);
if (list.isEmpty())
{
context.proceed();
return;
}
OutputStream old = context.getOutputStream();
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
context.setOutputStream(baos);
context.proceed();
byte[] body = baos.toByteArray();
for (DKIMSignature dosetaSignature : list)
{
KeyRepository repository = (KeyRepository) context.getProperty(KeyRepository.class.getName());
sign(repository, headers, body, dosetaSignature);
}
old.write(body);
}
catch (Exception e)
{
throw new RuntimeException(Messages.MESSAGES.failedToSign(), e);
}
finally
{
context.setOutputStream(old);
}
}
@Override
public CompletionStage<Void> asyncAroundWriteTo(AsyncWriterInterceptorContext context)
{
LogMessages.LOGGER.debugf("Interceptor : %s, Method : aroundWriteTo", getClass().getName());
MultivaluedMap<String, Object> headers = context.getHeaders();
List<DKIMSignature> list = getHeaders(headers);
if (list.isEmpty())
{
return context.asyncProceed();
}
AsyncOutputStream old = context.getAsyncOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
context.setAsyncOutputStream(new BlockingAsyncOutputStream(baos));
return context.asyncProceed()
.thenCompose(v -> {
byte[] body = baos.toByteArray();
try {
for (DKIMSignature dosetaSignature : list)
{
KeyRepository repository = (KeyRepository) context.getProperty(KeyRepository.class.getName());
sign(repository, headers, body, dosetaSignature);
}
} catch (InvalidKeyException | NoSuchAlgorithmException | SignatureException | UnsupportedEncodingException e) {
CompletableFuture<Void> ret = new CompletableFuture<>();
ret.completeExceptionally(e);
return ret;
}
return old.asyncWrite(body);
}).whenComplete((v, t) -> {
context.setAsyncOutputStream(old);
if(t != null)
throw new RuntimeException(Messages.MESSAGES.failedToSign(), t);
});
}
protected void sign(KeyRepository repository, MultivaluedMap<String, Object> headers, byte[] body, DKIMSignature dosetaSignature) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException
{
if (dosetaSignature.getBased64Signature() != null) return;
PrivateKey privateKey = dosetaSignature.getPrivateKey();
if (privateKey == null)
{
if (repository == null) repository = ResteasyContext.getContextData(KeyRepository.class);
if (repository == null)
{
throw new InvalidKeyException(Messages.MESSAGES.unableToLocatePrivateKey());
}
privateKey = repository.findPrivateKey(dosetaSignature);
if (privateKey == null)
{
throw new InvalidKeyException(Messages.MESSAGES.unableToFindKey());
}
}
dosetaSignature.sign(headers, body, privateKey);
}
}