Java tutorial
/* * eID Applet Project. * Copyright (C) 2010 FedICT. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * This software 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 this software; if not, see * http://www.gnu.org/licenses/. */ package be.fedict.eid.applet.service.signer.time; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.security.SecureRandom; import java.security.Security; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.security.auth.x500.X500Principal; import org.apache.commons.codec.binary.Hex; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.ByteArrayRequestEntity; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.cmp.PKIFailureInfo; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.cms.SignerId; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.tsp.TSPAlgorithms; import org.bouncycastle.tsp.TimeStampRequest; import org.bouncycastle.tsp.TimeStampRequestGenerator; import org.bouncycastle.tsp.TimeStampResponse; import org.bouncycastle.tsp.TimeStampToken; import be.fedict.eid.applet.service.signer.facets.RevocationData; /** * A TSP time-stamp service implementation. * * @author Frank Cornelis * */ public class TSPTimeStampService implements TimeStampService { private static final Log LOG = LogFactory.getLog(TSPTimeStampService.class); static { if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) { Security.addProvider(new BouncyCastleProvider()); } } public static final String DEFAULT_USER_AGENT = "eID Applet Service TSP Client"; private final String tspServiceUrl; private String requestPolicy; private final String userAgent; private final TimeStampServiceValidator validator; private String username; private String password; private String proxyHost; private int proxyPort; private String digestAlgo; private String digestAlgoOid; public TSPTimeStampService(String tspServiceUrl, TimeStampServiceValidator validator) { this(tspServiceUrl, validator, null, null); } @Override public String getTimeStampServiceURL() { return tspServiceUrl; } /** * Main constructor. * * @param tspServiceUrl * the URL of the TSP service. * @param validator * the trust validator used to validate incoming TSP response * signatures. * @param requestPolicy * the optional TSP request policy. * @param userAgent * the optional User-Agent TSP request header value. */ public TSPTimeStampService(String tspServiceUrl, TimeStampServiceValidator validator, String requestPolicy, String userAgent) { if (null == tspServiceUrl) { throw new IllegalArgumentException("TSP service URL required"); } this.tspServiceUrl = tspServiceUrl; if (null == validator) { throw new IllegalArgumentException("TSP validator required"); } this.validator = validator; this.requestPolicy = requestPolicy; if (null != userAgent) { this.userAgent = userAgent; } else { this.userAgent = DEFAULT_USER_AGENT; } this.digestAlgo = "SHA-1"; this.digestAlgoOid = "1.3.14.3.2.26";//org.bouncycastle.tsp.TSPAlgorithms.SHA1.toString(); } /** * Sets the request policy OID. * * @param policyOid */ public void setRequestPolicy(String policyOid) { this.requestPolicy = policyOid; } /** * Sets the credentials used in case the TSP service requires * authentication. * * @param username * @param password */ public void setAuthenticationCredentials(String username, String password) { this.username = username; this.password = password; } /** * Resets the authentication credentials. */ public void resetAuthenticationCredentials() { this.username = null; this.password = null; } /** * Sets the digest algorithm used for time-stamping data. Example value: * "SHA-1". * SHA-256: 2.16.840.1.101.3.4.2.1 * SHA-384: 2.16.840.1.101.3.4.2.2 * SHA-512: 2.16.840.1.101.3.4.2.3 * @param digestAlgo */ public void setDigestAlgo(String digestAlgo) { if ("SHA-1".equals(digestAlgo)) { this.digestAlgoOid = "1.3.14.3.2.26";//org.bouncycastle.tsp.TSPAlgorithms.SHA1.toString(); } else if ("SHA-256".equals(digestAlgo)) { this.digestAlgoOid = "2.16.840.1.101.3.4.2.1";//org.bouncycastle.tsp.TSPAlgorithms.SHA256.toString(); } else if ("SHA-384".equals(digestAlgo)) { this.digestAlgoOid = "2.16.840.1.101.3.4.2.2";//org.bouncycastle.tsp.TSPAlgorithms.SHA384.toString(); } else if ("SHA-512".equals(digestAlgo)) { this.digestAlgoOid = "2.16.840.1.101.3.4.2.3";//org.bouncycastle.tsp.TSPAlgorithms.SHA512.toString(); } else { throw new IllegalArgumentException("unsupported digest algo: " + digestAlgo); } this.digestAlgo = digestAlgo; } /** * Configures the HTTP proxy settings to be used to connect to the TSP * service. * * @param proxyHost * @param proxyPort */ public void setProxy(String proxyHost, int proxyPort) { this.proxyHost = proxyHost; this.proxyPort = proxyPort; } /** * Resets the HTTP proxy settings. */ public void resetProxy() { this.proxyHost = null; this.proxyPort = 0; } public byte[] timeStamp(byte[] data, RevocationData revocationData) throws Exception { // digest the message MessageDigest messageDigest = MessageDigest.getInstance(this.digestAlgo); byte[] digest = messageDigest.digest(data); // generate the TSP request BigInteger nonce = new BigInteger(128, new SecureRandom()); TimeStampRequestGenerator requestGenerator = new TimeStampRequestGenerator(); requestGenerator.setCertReq(true); if (null != this.requestPolicy) { requestGenerator.setReqPolicy(this.requestPolicy); } TimeStampRequest request = requestGenerator.generate(this.digestAlgoOid, digest, nonce); byte[] encodedRequest = request.getEncoded(); // create the HTTP client HttpClient httpClient = new HttpClient(); if (null != this.username) { Credentials credentials = new UsernamePasswordCredentials(this.username, this.password); httpClient.getState().setCredentials(AuthScope.ANY, credentials); } if (null != this.proxyHost) { httpClient.getHostConfiguration().setProxy(this.proxyHost, this.proxyPort); } // create the HTTP POST request PostMethod postMethod = new PostMethod(this.tspServiceUrl); RequestEntity requestEntity = new ByteArrayRequestEntity(encodedRequest, "application/timestamp-query"); postMethod.addRequestHeader("User-Agent", this.userAgent); postMethod.setRequestEntity(requestEntity); // invoke TSP service int statusCode = httpClient.executeMethod(postMethod); if (HttpStatus.SC_OK != statusCode) { LOG.error("Error contacting TSP server " + this.tspServiceUrl); throw new Exception("Error contacting TSP server " + this.tspServiceUrl); } // HTTP input validation Header responseContentTypeHeader = postMethod.getResponseHeader("Content-Type"); if (null == responseContentTypeHeader) { throw new RuntimeException("missing Content-Type header"); } String contentType = responseContentTypeHeader.getValue(); if (!contentType.startsWith("application/timestamp-reply")) { LOG.debug("response content: " + postMethod.getResponseBodyAsString()); throw new RuntimeException("invalid Content-Type: " + contentType); } if (0 == postMethod.getResponseContentLength()) { throw new RuntimeException("Content-Length is zero"); } // TSP response parsing and validation InputStream inputStream = postMethod.getResponseBodyAsStream(); TimeStampResponse timeStampResponse = new TimeStampResponse(inputStream); timeStampResponse.validate(request); if (0 != timeStampResponse.getStatus()) { LOG.debug("status: " + timeStampResponse.getStatus()); LOG.debug("status string: " + timeStampResponse.getStatusString()); PKIFailureInfo failInfo = timeStampResponse.getFailInfo(); if (null != failInfo) { LOG.debug("fail info int value: " + failInfo.intValue()); if (PKIFailureInfo.unacceptedPolicy == failInfo.intValue()) { LOG.debug("unaccepted policy"); } } throw new RuntimeException("timestamp response status != 0: " + timeStampResponse.getStatus()); } TimeStampToken timeStampToken = timeStampResponse.getTimeStampToken(); SignerId signerId = timeStampToken.getSID(); BigInteger signerCertSerialNumber = signerId.getSerialNumber(); X500Principal signerCertIssuer = new X500Principal(signerId.getIssuer().getEncoded()); LOG.debug("signer cert serial number: " + signerCertSerialNumber); LOG.debug("signer cert issuer: " + signerCertIssuer); // TSP signer certificates retrieval CertStore certStore = timeStampToken.getCertificatesAndCRLs("Collection", BouncyCastleProvider.PROVIDER_NAME); Collection<? extends Certificate> certificates = certStore.getCertificates(null); X509Certificate signerCert = null; Map<String, X509Certificate> certificateMap = new HashMap<String, X509Certificate>(); for (Certificate certificate : certificates) { X509Certificate x509Certificate = (X509Certificate) certificate; if (signerCertIssuer.equals(x509Certificate.getIssuerX500Principal()) && signerCertSerialNumber.equals(x509Certificate.getSerialNumber())) { signerCert = x509Certificate; } String ski = Hex.encodeHexString(getSubjectKeyId(x509Certificate)); certificateMap.put(ski, x509Certificate); LOG.debug("embedded certificate: " + x509Certificate.getSubjectX500Principal() + "; SKI=" + ski); } // TSP signer cert path building if (null == signerCert) { throw new RuntimeException("TSP response token has no signer certificate"); } List<X509Certificate> tspCertificateChain = new LinkedList<X509Certificate>(); X509Certificate tsaIssuer = loadCertificate( "be/fedict/eid/applet/service/CA POLITICA SELLADO DE TIEMPO - COSTA RICA.crt"); X509Certificate rootCA = loadCertificate("be/fedict/eid/applet/service/CA RAIZ NACIONAL COSTA RICA.cer"); LOG.debug("adding to certificate chain: " + signerCert.getSubjectX500Principal()); tspCertificateChain.add(signerCert); LOG.debug("adding to certificate chain: " + tsaIssuer.getSubjectX500Principal()); tspCertificateChain.add(tsaIssuer); LOG.debug("adding to certificate chain: " + rootCA.getSubjectX500Principal()); tspCertificateChain.add(rootCA); // verify TSP signer signature timeStampToken.validate(tspCertificateChain.get(0), BouncyCastleProvider.PROVIDER_NAME); // verify TSP signer certificate this.validator.validate(tspCertificateChain, revocationData); LOG.debug("time-stamp token time: " + timeStampToken.getTimeStampInfo().getGenTime()); byte[] timestamp = timeStampToken.getEncoded(); return timestamp; } private static X509Certificate loadCertificate(String resourceName) { LOG.debug("loading certificate: " + resourceName); Thread currentThread = Thread.currentThread(); ClassLoader classLoader = currentThread.getContextClassLoader(); InputStream certificateInputStream = classLoader.getResourceAsStream(resourceName); if (null == certificateInputStream) { throw new IllegalArgumentException("resource not found: " + resourceName); } try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); return (X509Certificate) certificateFactory.generateCertificate(certificateInputStream); } catch (CertificateException e) { throw new RuntimeException("X509 error: " + e.getMessage(), e); } } private byte[] getSubjectKeyId(X509Certificate cert) throws IOException { byte[] extvalue = cert.getExtensionValue(X509Extensions.SubjectKeyIdentifier.getId()); if (extvalue == null) { return null; } ASN1OctetString str = ASN1OctetString .getInstance(new ASN1InputStream(new ByteArrayInputStream(extvalue)).readObject()); SubjectKeyIdentifier keyId = SubjectKeyIdentifier .getInstance(new ASN1InputStream(new ByteArrayInputStream(str.getOctets())).readObject()); return keyId.getKeyIdentifier(); } private byte[] getAuthorityKeyId(X509Certificate cert) throws IOException { byte[] extvalue = cert.getExtensionValue(X509Extensions.AuthorityKeyIdentifier.getId()); if (extvalue == null) { return null; } DEROctetString oct = (DEROctetString) (new ASN1InputStream(new ByteArrayInputStream(extvalue)) .readObject()); /*AuthorityKeyIdentifier keyId = new AuthorityKeyIdentifier( (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream( oct.getOctets())).readObject());*/ AuthorityKeyIdentifier keyId = new AuthorityKeyIdentifier(oct.getOctets()); return keyId.getKeyIdentifier(); } }