/*
* XAdES4j - A Java library for generation and verification of XAdES signatures.
* Copyright (C) 2017 Luis Goncalves.
*
* XAdES4j is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or any later version.
*
* XAdES4j 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with XAdES4j. If not, see <http://www.gnu.org/licenses/>.
*/
package xades4j.providers.impl;
import com.google.inject.Inject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;
import org.apache.xml.security.algorithms.MessageDigestAlgorithm;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cmp.PKIStatus;
import org.bouncycastle.tsp.TSPAlgorithms;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampRequest;
import org.bouncycastle.tsp.TimeStampRequestGenerator;
import org.bouncycastle.tsp.TimeStampResponse;
import org.bouncycastle.tsp.TimeStampToken;
import xades4j.UnsupportedAlgorithmException;
import xades4j.providers.MessageDigestEngineProvider;
import xades4j.providers.TimeStampTokenGenerationException;
import xades4j.providers.TimeStampTokenProvider;
import xades4j.utils.Base64;
/**
* Implementation of {@code TimeStampTokenProvider} that gets time-stamp tokens
* from a HTTP TSA. Requests are issued with {@code certReq} set to
* {@code true}. If username and password are set supplied, HTTP basic
* authenticated will be used.
*
* @author luis
*/
public class HttpTimeStampTokenProvider implements TimeStampTokenProvider
{
private static final Map<String, ASN1ObjectIdentifier> digestUriToOidMappings;
static
{
digestUriToOidMappings = new HashMap<String, ASN1ObjectIdentifier>(6);
digestUriToOidMappings.put(MessageDigestAlgorithm.ALGO_ID_DIGEST_NOT_RECOMMENDED_MD5, TSPAlgorithms.MD5);
digestUriToOidMappings.put(MessageDigestAlgorithm.ALGO_ID_DIGEST_RIPEMD160, TSPAlgorithms.RIPEMD160);
digestUriToOidMappings.put(MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA1, TSPAlgorithms.SHA1);
digestUriToOidMappings.put(MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256, TSPAlgorithms.SHA256);
digestUriToOidMappings.put(MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA384, TSPAlgorithms.SHA384);
digestUriToOidMappings.put(MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA512, TSPAlgorithms.SHA512);
}
// TODO this probably should be a provider to avoid being dependent on a fixed set of algorithms
private static ASN1ObjectIdentifier identifierForDigest(String digestAlgUri)
{
return digestUriToOidMappings.get(digestAlgUri);
}
private final MessageDigestEngineProvider messageDigestProvider;
private final TSAHttpData tsaHttpData;
private final TimeStampRequestGenerator tsRequestGenerator;
private final String base64TsaUsrAndPwd;
@Inject
public HttpTimeStampTokenProvider(MessageDigestEngineProvider messageDigestProvider, TSAHttpData tsaHttpData)
{
this.messageDigestProvider = messageDigestProvider;
this.tsaHttpData = tsaHttpData;
this.tsRequestGenerator = new TimeStampRequestGenerator();
this.tsRequestGenerator.setCertReq(true);
if (this.tsaHttpData.getUsername() != null)
{
String usrAndPwd = this.tsaHttpData.getUsername() + ":" + this.tsaHttpData.getPassword();
this.base64TsaUsrAndPwd = Base64.encodeBytes(usrAndPwd.getBytes());
}
else
{
this.base64TsaUsrAndPwd = null;
}
}
@Override
public final TimeStampTokenRes getTimeStampToken(
byte[] tsDigestInput,
String digestAlgUri) throws TimeStampTokenGenerationException
{
try
{
MessageDigest md = messageDigestProvider.getEngine(digestAlgUri);
byte[] digest = md.digest(tsDigestInput);
TimeStampRequest tsRequest = this.tsRequestGenerator.generate(
identifierForDigest(digestAlgUri),
digest,
BigInteger.valueOf(System.currentTimeMillis()));
InputStream responseStream = getResponse(tsRequest.getEncoded());
TimeStampResponse tsResponse = new TimeStampResponse(responseStream);
if (tsResponse.getStatus() != PKIStatus.GRANTED
&& tsResponse.getStatus() != PKIStatus.GRANTED_WITH_MODS)
{
throw new TimeStampTokenGenerationException("Time stamp token not granted. " + tsResponse.getStatusString());
}
tsResponse.validate(tsRequest);
TimeStampToken tsToken = tsResponse.getTimeStampToken();
return new TimeStampTokenRes(tsToken.getEncoded(), tsToken.getTimeStampInfo().getGenTime());
} catch (UnsupportedAlgorithmException ex)
{
throw new TimeStampTokenGenerationException("Digest algorithm not supported", ex);
} catch (TSPException ex)
{
throw new TimeStampTokenGenerationException("Invalid time stamp response", ex);
} catch (IOException ex)
{
throw new TimeStampTokenGenerationException("Encoding error", ex);
}
}
private InputStream getResponse(byte[] encodedRequest) throws TimeStampTokenGenerationException
{
try
{
HttpURLConnection connection = createHttpConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-type", "application/timestamp-query");
connection.setRequestProperty("Content-length", String.valueOf(encodedRequest.length));
OutputStream out = connection.getOutputStream();
out.write(encodedRequest);
out.flush();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
{
throw new TimeStampTokenGenerationException(String.format("TSA returned HTTP %d %s", connection.getResponseCode(), connection.getResponseMessage()));
}
// TODO do we need to invoke connection.disconnect()?
return new BufferedInputStream(connection.getInputStream());
} catch (IOException ex)
{
throw new TimeStampTokenGenerationException("Error when connecting to the TSA", ex);
}
}
private HttpURLConnection createHttpConnection() throws IOException
{
HttpURLConnection connection = (HttpURLConnection) new URL(this.temporaryGetTSAUrl()).openConnection();
String username = this.tsaHttpData.getUsername(), password = this.tsaHttpData.getPassword();
if (this.base64TsaUsrAndPwd != null)
{
connection.setRequestProperty("Authorization", "Basic " + this.base64TsaUsrAndPwd);
}
return connection;
}
/**
* This package-private method is TEMPORARY until the deprecated
* {@link DefaultTimeStampTokenProvider} is removed.
*/
String temporaryGetTSAUrl()
{
return this.tsaHttpData.getUrl();
}
}