/* * Copyright 2004 by Paulo Soares. * * The contents of this file are subject to the Mozilla Public License Version 1.1 * (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the License. * * The Original Code is 'iText, a free JAVA-PDF library'. * * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie. * All Rights Reserved. * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved. * * Contributor(s): all the names of the contributors are added in the source code * where applicable. * * Alternatively, the contents of this file may be used under the terms of the * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the * provisions of LGPL are applicable instead of those above. If you wish to * allow use of your version of this file only under the terms of the LGPL * License and not to allow others to use your version of this file under * the MPL, indicate your decision by deleting the provisions above and * replace them with the notice and other provisions required by the LGPL. * If you do not delete the provisions above, a recipient may use your version * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE. * * This library is free software; you can redistribute it and/or modify it * under the terms of the MPL as stated above or under the terms of the GNU * Library General Public License as published by the Free Software Foundation; * either version 2 of the License, or any later version. * * This library 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 Library general Public License for more * details. * * If you didn't download this code from the following link, you should check if * you aren't using an obsolete version: * http://www.lowagie.com/iText/ */ package com.opentrust.spi.pdf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Signature; import java.security.SignatureException; import java.security.cert.CRL; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.GregorianCalendar; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import javax.crypto.Cipher; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Object; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OutputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DEREncodable; import org.bouncycastle.asn1.DERObject; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.DERString; import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.CMSAttributes; import org.bouncycastle.asn1.esf.CommitmentTypeIndication; import org.bouncycastle.asn1.esf.SignaturePolicyIdentifier; import org.bouncycastle.asn1.esf.SignerAttribute; import org.bouncycastle.asn1.esf.SignerLocation; import org.bouncycastle.asn1.ess.ContentHints; import org.bouncycastle.asn1.ess.ContentIdentifier; import org.bouncycastle.asn1.ess.ESSCertID; import org.bouncycastle.asn1.ess.ESSCertIDv2; import org.bouncycastle.asn1.x509.DigestInfo; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedDataStreamGenerator; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.provider.X509CertParser; import org.bouncycastle.tsp.TSPException; import org.bouncycastle.tsp.TimeStampResponse; import org.bouncycastle.tsp.TimeStampToken; import com.keynectis.sequoia.security.clients.interfaces.ITspClient; import com.opentrust.spi.cms.CMSForPAdESBasicGenerator; import com.opentrust.spi.cms.CMSSignedDataWrapper; import com.opentrust.spi.cms.CMSVerifier; import com.opentrust.spi.cms.ExceptionHandlerTyped; import com.opentrust.spi.cms.helpers.ContentSignerWithProvidedSignatureValue; import com.opentrust.spi.cms.helpers.OCSPResponse; import com.opentrust.spi.cms.helpers.SignatureHelper; import com.opentrust.spi.crypto.CryptoConstants; import com.opentrust.spi.crypto.DigestHelper; import com.opentrust.spi.crypto.CryptoConstants.AlgorithmID; import com.opentrust.spi.crypto.CryptoConstants.AlgorithmType; import com.opentrust.spi.crypto.ExceptionHandler; import com.opentrust.spi.logger.Channel; import com.opentrust.spi.logger.SPILogger; //import com.opentrust.spi.ocsp.OCSPResponse; import com.opentrust.spi.tsp.TimeStampProcessor; import com.opentrust.spi.tsp.TimeStampProcessorFactory; import com.opentrust.spi.tsp.TimestampToken; import com.opentrust.spi.tsp.impl.BCTimeStampToken; import com.spilowagie.text.ExceptionConverter; import com.spilowagie.text.pdf.AcroFields; import com.spilowagie.text.pdf.PdfName; /** * This class does all the processing related to signing and verifying a PKCS#7 or PKCS#1 PDF-embedded * signature. * <p> * It's based in code found at org.bouncycastle. */ public class PDFEnvelopedSignature { private static SPILogger log = SPILogger.getLogger("PDFSIGN"); private Collection<OCSPResponse> ocspResponses; private String timeStampDigestAlgo; private String timeStampPolicyId; private boolean timeStampUseNonce; private int version, signerversion; private ASN1ObjectIdentifier cmsContentType; private Collection certs, crls; private X509Certificate signCert; private byte[] pkcs1SigValue; private String dataDigestAlgorithm, keyAndParameterAlgorithm; private Signature sig; private byte adbePkcs7Sha1Data[]; private TimestampToken timestampToken; private static final String ID_RSA = AlgorithmID.KEY_RSA.getOID(); private static final String ID_DSA = "1.2.840.10040.4.1"; private static final int GRANTED = 0; /** * properties from PDF signature dictionary (M, Location, Reason, ContactInfo) */ private String reason; private String location; private String contactInfo; private byte[] dictionaryCert; private int[] byteRange; private byte[] contentsKey; // the original <CONTENTS> content // Used to retrieve info for signature (revision, coversWholeDocument, fieldPositions...) private AcroFields acroFields; private String signatureFieldName; private Calendar dicoSignDate; private Calendar cmsSignDate; /** * Holds value of property signName. */ private String signName; private CMSSignedDataWrapper cmsSignature; private MessageDigest verifyDigest; private MessageDigest contentTimestampVerifyDigest; private OutputStream sigOut; private ByteArrayOutputStream bOut; private CMSSignedDataStreamGenerator cmsGenerator; TimestampToken docTimestampTStoken;//For "Document TimeStamp" signatures (PAdES LTV) ITspClient tspClient; public ITspClient getTspClient() { return tspClient; } public void setTspClient(ITspClient tspClient) { this.tspClient = tspClient; } private String subFilter; public static String SF_ADBE_X509_RSA_SHA1 = "adbe.x509.rsa_sha1"; public static String SF_ADBE_PKCS7_DETACHED = "adbe.pkcs7.detached"; public static String SF_ADBE_PKCS7_SHA1 = "adbe.pkcs7.sha1"; public static String SF_ETSI_CADES_DETACHED = "ETSI.CAdES.detached"; public static String SF_ETSI_RFC3161 = "ETSI.RFC3161"; // PKCS#1 signature verification /** * Verifies a signature using the sub-filter adbe.x509.rsa_sha1. * @param contentsKey the /Contents key * @param certsKey the /Cert key * @param provider the provider or <code>null</code> for the default provider */ public PDFEnvelopedSignature(byte[] contentsKey, byte[] certsKey, String provider, AcroFields acroFields, String signatureFieldName) { try { log.debug(Channel.TECH, "Verifying a adbe.x509.rsa_sha1 signature"); this.acroFields = acroFields; this.signatureFieldName = signatureFieldName; this.subFilter = SF_ADBE_X509_RSA_SHA1; this.dictionaryCert = certsKey; X509CertParser cr = new X509CertParser(); cr.engineInit(new ByteArrayInputStream(certsKey)); certs = cr.engineReadAll(); signCert = (X509Certificate)certs.iterator().next(); crls = new ArrayList(); ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(contentsKey)); pkcs1SigValue = ((DEROctetString)in.readObject()).getOctets(); Cipher c = Cipher.getInstance("RSA/NONE/PKCS1Padding", BouncyCastleProvider.PROVIDER_NAME); c.init(Cipher.DECRYPT_MODE, signCert); byte[] raw = c.doFinal(pkcs1SigValue); ASN1Sequence in3 = (ASN1Sequence)ASN1Object.fromByteArray(raw); DigestInfo di = DigestInfo.getInstance(in3); dataDigestAlgorithm = di.getAlgorithmId().getAlgorithm().getId(); keyAndParameterAlgorithm = ID_RSA; if (provider == null) sig = Signature.getInstance(getSignatureAlgorithm()); else sig = Signature.getInstance(getSignatureAlgorithm(), provider); sig.initVerify(signCert.getPublicKey()); } catch (Exception e) { throw new ExceptionConverter(e); } } /** * Verifies a signature using the sub-filter adbe.pkcs7.detached or * adbe.pkcs7.sha1 or ETSI.CAdES.detached or ETSI.RFC3161 * @param contentsKey the /Contents key * @param provider the provider or <code>null</code> for the default provider * @param acroFields */ protected PDFEnvelopedSignature(byte[] contentsKey, String provider, PdfName subFilter, AcroFields acroFields, String signatureFieldName) { try { log.debug(Channel.TECH, "Verifying an adbe.pkcs7.detached, adbe.pkcs7.sha1 or ETSI.CAdES.detached signature"); this.acroFields = acroFields; this.signatureFieldName = signatureFieldName; this.contentsKey = contentsKey; if(subFilter==PdfName.ADBE_PKCS7_DETACHED) this.subFilter = SF_ADBE_PKCS7_DETACHED; else if(subFilter==PdfName.ADBE_PKCS7_SHA1) this.subFilter = SF_ADBE_PKCS7_SHA1; else if(subFilter==PdfName.ETSI_CADES_DETACHED) this.subFilter = SF_ETSI_CADES_DETACHED; else if(subFilter==PdfName.ETSI_RFC3161) this.subFilter = SF_ETSI_RFC3161; else throw new IllegalArgumentException("Unknown subFilter found in signature dictionary : " + (subFilter==null?null:new String(subFilter.getBytes()))); log.debug(Channel.TECH, "Signature subFilter is %1$s", this.subFilter); if(this.subFilter == SF_ETSI_RFC3161) { // Then contentsKey contains a TimeStamptoken //docTimestampTStoken = TimestampTokenManagerFactory.getInstance().getTimeStampToken(contentsKey); docTimestampTStoken = new BCTimeStampToken(contentsKey); dataDigestAlgorithm = docTimestampTStoken.getMessageImprintAlgName(); verifyDigest = MessageDigest.getInstance(dataDigestAlgorithm, BouncyCastleProvider.PROVIDER_NAME); } else { cmsSignature = new CMSSignedDataWrapper(contentsKey); // uses provider BC, not provider given as parameter. TODO ? // the version version = cmsSignature.getVersion(); log.debug(Channel.TECH, "Parsing CMS Data, version=%1$s", version); cmsContentType = cmsSignature.getContentType(); log.debug(Channel.TECH, "Parsing CMS Data, cmsContentType=%1$s", cmsContentType); if (cmsSignature.hasMultipleSignerInfos()) throw new IllegalArgumentException("This PKCS#7 object has multiple SignerInfos - only one is supported at this time"); // and even forbidden for PAdES ? // the digestAlgorithms property is not fetched. We have no use for it, it is used internally by CMSSignature certs = cmsSignature.getSignatureCertificateInfo(); crls = cmsSignature.getCRLs(); ocspResponses = cmsSignature.getOCSPResponses(); log.debug(Channel.TECH, "Parsing CMS Data, certs=%1$s", certs); log.debug(Channel.TECH, "Parsing CMS Data, crls=%1$s", crls); log.debug(Channel.TECH, "Parsing CMS Data, ocspResponses=%1$s", ocspResponses); signerversion = cmsSignature.getSignerVersion(); log.debug(Channel.TECH, "Parsing CMS Data, signerversion=%1$s", signerversion); signCert = (X509Certificate)cmsSignature.getSignerCertificate(); if (signCert == null) { throw new IllegalArgumentException("Can't find signing certificate"); } log.debug(Channel.TECH, "Parsing CMS Data, signCert=%1$s", signCert); dataDigestAlgorithm = cmsSignature.getDataDigestAlgorithm(); log.debug(Channel.TECH, "Parsing CMS Data, dataDigestAlgorithm=%1$s", dataDigestAlgorithm); keyAndParameterAlgorithm = cmsSignature.getEncryptionAlgorithm(); log.debug(Channel.TECH, "Parsing CMS Data, keyAndParameterAlgorithm=%1$s", keyAndParameterAlgorithm); List<TimestampToken> timestps = cmsSignature.getSignatureTimestamps(); if(!timestps.isEmpty()) { timestampToken = timestps.get(0); log.debug(Channel.TECH, "Parsing CMS Data, found timestamp token with date %1$s", timestampToken.getDateTime()); } else log.debug(Channel.TECH, "Parsing CMS Data, no timestamp token found"); // sigAttr and digestAttr properties are not fetched. We have no use for them, they are used internally by CMSSignature if(cmsSignature.getSigningTime()!=null) { cmsSignDate = Calendar.getInstance(); cmsSignDate.setTime(cmsSignature.getSigningTime()); } log.debug(Channel.TECH, "Parsing CMS Data, cmsSignDate=%1$s", cmsSignDate); adbePkcs7Sha1Data = cmsSignature.getEncodedEncapsulatedData(); if(adbePkcs7Sha1Data!=null) { if(this.subFilter != SF_ADBE_PKCS7_SHA1) throw new Exception("Invalid CMS : cannot have encapsulated data for " + this.subFilter + " subfilter"); verifyDigest = MessageDigest.getInstance("SHA1", BouncyCastleProvider.PROVIDER_NAME); } else { if(this.subFilter == SF_ADBE_PKCS7_SHA1) throw new Exception("Invalid CMS : must have encapsulated data for " + SF_ADBE_PKCS7_SHA1 + " subfilter"); verifyDigest = MessageDigest.getInstance(dataDigestAlgorithm, BouncyCastleProvider.PROVIDER_NAME); try { if(cmsSignature.getContentTimestamp()!=null && cmsSignature.getContentTimestamp().getMessageImprintAlgName()!=null) contentTimestampVerifyDigest = MessageDigest.getInstance(cmsSignature.getContentTimestamp().getMessageImprintAlgName(), BouncyCastleProvider.PROVIDER_NAME); //TODO : fo not compute digest for ContentTS when digestAlgo is the same as dataDigestAlgorithm } catch(Exception e) { log.error(Channel.TECH, "Error while parsing content timestamp : %1$s", e); } } } } catch (Exception e) { throw new ExceptionConverter(e); } } /** * Used to build a PKCS7 object given all its properties (digest, certs, crls, raw signature, adbePkcs7Sha1...). **/ //FIXME : move to other class !!! public PDFEnvelopedSignature(byte[] digest, Certificate[] certChain, CRL[] crlList, OCSPResponse[] ocspResponseEncoded, String dataHashAlgorithm, String provider, byte signature[], byte adbePkcs7Sha1Data[], String digestEncryptionAlgorithm, Date signingTime) { try { log.debug(Channel.TECH, "Building PDFEnvelopedSignature object"); Hashtable<DERObjectIdentifier, Attribute> signedAttributesHashtable = new Hashtable<DERObjectIdentifier, Attribute>(); List<OCSPResponse> ocspResponses = ocspResponseEncoded==null?null:Arrays.asList(ocspResponseEncoded); List<CRL> crls = null; if(crlList!=null) { crls = Arrays.asList(crlList); } AlgorithmID algorithmID = CryptoConstants.AlgorithmID.valueOfTag(dataHashAlgorithm); if (algorithmID == null || algorithmID.getType() != AlgorithmType.DIGEST) throw new NoSuchAlgorithmException("Unknown Hash Algorithm " + dataHashAlgorithm); Attribute messageDigestAttribute = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(digest))); signedAttributesHashtable.put(CMSAttributes.messageDigest, messageDigestAttribute); this.signCert = (X509Certificate)certChain[0]; dataDigestAlgorithm = algorithmID.getOID(); this.crls =crls; this.certs = Arrays.asList(certChain); this.ocspResponses = ocspResponses; keyAndParameterAlgorithm = digestEncryptionAlgorithm; this.adbePkcs7Sha1Data = adbePkcs7Sha1Data; cmsGenerator = (CMSSignedDataStreamGenerator)CMSForPAdESBasicGenerator.buildCMSSignedGenerator(new ContentSignerWithProvidedSignatureValue(signature, AlgorithmID.valueOfTag(getSignatureAlgorithm()).getOID()), true, BouncyCastleProvider.PROVIDER_NAME, signedAttributesHashtable, signCert, certs, signingTime, dataDigestAlgorithm, crls, ocspResponses); bOut = new ByteArrayOutputStream(); sigOut = cmsGenerator.open(bOut, adbePkcs7Sha1Data!=null); if(adbePkcs7Sha1Data!=null) sigOut.write(adbePkcs7Sha1Data); } catch (Exception e) { throw new ExceptionConverter(e); } } /** * Update the digest with the specified bytes. This method is used both for signing and verifying * @param buf the data buffer * @param off the offset in the data buffer * @param len the data length * @throws SignatureException on error */ public void update(byte[] buf, int off, int len) throws SignatureException { if(sigOut!=null) // adbe.pkcs7.detached or adbe.pkcs7.sha1 (PKCS#7) generation try { if(adbePkcs7Sha1Data!=null) //adbe.pkcs7.sha1 verifyDigest.update(buf, off, len); // verifyDigest is used to compute sha1 digest on document else // adbe.pkcs7.detached sigOut.write(buf, off, len); } catch (IOException e) { throw new ExceptionConverter(e); } else if(verifyDigest!=null) { // adbe.pkcs7.detached or adbe.pkcs7.sha1 (PKCS#7) verification verifyDigest.update(buf, off, len); if(contentTimestampVerifyDigest!=null) contentTimestampVerifyDigest.update(buf, off, len); //Also compute digest for contentTimestamp } else if(sig!=null) //adbe.x509.rsa_sha1 (PKCS#1) verification sig.update(buf, off, len); } private boolean verified = false; private boolean verificationResult = false; /** * Verify the signature and digest(s) conformity * @throws SignatureException on error * @return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE> otherwise */ public boolean verify() { if(!verified) { try { verificationResult = true; if(verifyDigest!=null) { if(adbePkcs7Sha1Data!=null) { // PKCS7 verification (with encapsulated data) verificationResult = CMSVerifier.verify(cmsSignature) && Arrays.equals(verifyDigest.digest(), cmsSignature.getEncodedEncapsulatedData()); } else if(docTimestampTStoken!=null) { verificationResult = docTimestampTStoken.verifySignature() && docTimestampTStoken.verifyImprint(verifyDigest.digest()); } else { verificationResult = CMSVerifier.verifyReference(verifyDigest.digest(), cmsSignature); // PKCS7 verification if(contentTimestampVerifyDigest!=null) contentTimestampDigest = contentTimestampVerifyDigest.digest(); } } else verificationResult = sig.verify(pkcs1SigValue); // PKCS1 verification } catch(Exception e) { ExceptionHandler.handleNoThrow(e, "error while verifying signature"); verificationResult = false; } verified = true; } return verificationResult; } private byte[] encodedPKCS7; /** * Gets the bytes for the PKCS7SignedData object (performs actual signing when sigOut not null) * @param secondDigest NOT USED * @param signingTime NOT USED * @return the bytes for the PKCS7SignedData object */ protected byte[] getEncodedPKCS7(byte secondDigest[], String signingTime) { if(encodedPKCS7==null) { try { if(cmsSignature==null) { if(adbePkcs7Sha1Data!=null) //adbe.pkcs7.sha1 sigOut.write(verifyDigest.digest()); sigOut.close(); byte[] bytes = bOut.toByteArray(); this.cmsSignature = new CMSSignedDataWrapper(bytes); if(getServerTimestamp()!=null) getUpdatedEncodedPKCS7WithAddedTS(); } encodedPKCS7 = cmsSignature.getEncoded(); } catch (Exception e) { ExceptionHandlerTyped.handle(SignatureException.class, e,"Could not retrieve bytes for signature"); } } return encodedPKCS7; } public byte[] getUpdatedEncodedPKCS7WithAddedTS() throws NoSuchAlgorithmException, IOException, NoSuchFieldException, TSPException, CMSException { timestampToken = addTSToCMS(cmsSignature,getTimeStampDigestAlgo(), tspClient); return cmsSignature.getEncoded(); } public static TimestampToken addTSToCMS(CMSSignedDataWrapper cmsSignature, String algoId, ITspClient tspClient) throws NoSuchAlgorithmException, IOException, NoSuchFieldException, TSPException, CMSException { //byte[] tsResponse = getTSResponse2(cmsSignature.getSignatureValue(), user, password, serverTimestamp, algoId, policyId, useNonce); byte[] tsResponse; try { byte [] digest = DigestHelper.getDigest(cmsSignature.getSignatureValue(), algoId); byte [] fullresponse = tspClient.getRawTsp(digest, algoId); TimeStampResponse response = new TimeStampResponse(fullresponse); int status = response.getStatus(); if (status == GRANTED) { TimeStampToken tspValue = response.getTimeStampToken(); tsResponse = tspValue.getEncoded(); } else throw new RuntimeException("Timestamping failure, status " + status + ", " + response.getStatusString()); } catch (Exception e) { throw new RuntimeException("Error getting timestamp from " + tspClient.getSource(), e); } TimestampToken timestampToken = new BCTimeStampToken(tsResponse); cmsSignature.appendSignatureTimeStamp(timestampToken.getEncoded()); return timestampToken; } /* public static byte[] getTSResponse2(byte[] hash, String user, String password, String serverTimestamp, String algoId, String policyId, boolean useNonce) throws NoSuchAlgorithmException, IOException, NoSuchFieldException, TSPException, CMSException, SPITimestampException { TimeStampProcessor timeStampProcessor = TimeStampProcessorFactory.getInstance(). getTimeStampProcessor(serverTimestamp, policyId, algoId, true); return timeStampProcessor.timestamp(hash); } public static byte[] getTSResponse2(InputStream stream, String user, String password, String serverTimestamp, String algoId, String policyId, boolean useNonce) throws NoSuchAlgorithmException, IOException, NoSuchFieldException, TSPException, CMSException, SPITimestampException { TimeStampProcessor timeStampProcessor = TimeStampProcessorFactory.getInstance(). getTimeStampProcessor(serverTimestamp, policyId, algoId, true); return timeStampProcessor.timestamp(stream); } protected byte [] getTsp(byte [] hash, String hashAlgorithm) throws Exception { return tspClient.getTsp(hash, hashAlgorithm); } */ /** * Get the X.509 certificates associated with this PKCS#7 object * @return the X.509 certificates associated with this PKCS#7 object */ public Certificate[] getCertificates() { return (X509Certificate[])certs.toArray(new X509Certificate[certs.size()]); } /** * Get the X.509 certificate revocation lists associated with this PKCS#7 object * @return the X.509 certificate revocation lists associated with this PKCS#7 object */ public Collection getCRLs() { return crls; } /** * Set the X.509 certificate revocation lists associated with this PKCS#7 object * @param crls */ public void setCRLs(Collection crls) { this.crls = crls; } /** * Get the X.509 certificate actually used to sign the digest. * @return the X.509 certificate actually used to sign the digest */ public X509Certificate getSigningCertificate() { return signCert; } /** * Get the version of the PKCS#7 object. Always 1 * @return the version of the PKCS#7 object. Always 1 */ public int getVersion() { return version; } /** * Get the version of the PKCS#7 "SignerInfo" object. Always 1 * @return the version of the PKCS#7 "SignerInfo" object. Always 1 */ public int getSigningInfoVersion() { return signerversion; } public ASN1ObjectIdentifier getCmsContentType() { return cmsContentType; } /** * Get the algorithm used to calculate the message digest * * @return the algorithm used to calculate the message digest */ public String getSignatureAlgorithm() { if(keyAndParameterAlgorithm==null) return null; String dea = keyAndParameterAlgorithm; if (keyAndParameterAlgorithm.equals(ID_RSA)) dea = "RSA"; else if (keyAndParameterAlgorithm.equals(ID_DSA)) dea = "DSA"; else { AlgorithmID algo = AlgorithmID.valueOfOID(keyAndParameterAlgorithm); if(algo==null) algo = AlgorithmID.valueOfTag(keyAndParameterAlgorithm); if (algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_RSA_MD5) || algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_RSA_RIPEMD160) || algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_RSA_SHA1) || algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_RSA_SHA256) || algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_RSA_SHA384) || algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_RSA_SHA512)) { dea = "RSA"; } else if (algo.equals(CryptoConstants.AlgorithmID.SIGNATURE_DSA_SHA1)) { dea = "DSA"; } } return SignatureHelper.getSignatureAlgoFromDigestAndKeyAlgo(getDataDigestAlgorithm(), dea); } /** * Returns the algorithm. * * @return the digest algorithm */ public String getDataDigestAlgorithm() { String da = dataDigestAlgorithm; AlgorithmID algorithmID = CryptoConstants.AlgorithmID.valueOfOID(dataDigestAlgorithm); if (algorithmID != null) da = algorithmID.getTag(); return da; } /** * Loads the default root certificates at <java.home>/lib/security/cacerts * with the default provider. * @return a <CODE>KeyStore</CODE> */ public static KeyStore loadCacertsKeyStore() { return loadCacertsKeyStore(null); } /** * Loads the default root certificates at <java.home>/lib/security/cacerts. * @param provider the provider or <code>null</code> for the default provider * @return a <CODE>KeyStore</CODE> */ public static KeyStore loadCacertsKeyStore(String provider) { File file = new File(System.getProperty("java.home"), "lib"); file = new File(file, "security"); file = new File(file, "cacerts"); FileInputStream fin = null; try { fin = new FileInputStream(file); KeyStore k; if (provider == null) k = KeyStore.getInstance("JKS"); else k = KeyStore.getInstance("JKS", provider); k.load(fin, null); return k; } catch (Exception e) { throw new ExceptionConverter(e); } finally { try{if (fin != null) {fin.close();}}catch(Exception ex){} } } /** * Verifies a single certificate. * @param cert the certificate to verify * @param crls the certificate revocation list or <CODE>null</CODE> * @param calendar the date or <CODE>null</CODE> for the current date * @return a <CODE>String</CODE> with the error description or <CODE>null</CODE> * if no error */ public static String verifyCertificate(X509Certificate cert, Collection crls, OCSPResponse ocspResponse, Calendar calendar) { if (calendar == null) calendar = new GregorianCalendar(); if (cert.hasUnsupportedCriticalExtension()) return "Has unsupported critical extension"; try { cert.checkValidity(calendar.getTime()); } catch (Exception e) { return e.getMessage(); } if (crls != null) { for (Iterator it = crls.iterator(); it.hasNext();) { if (((CRL)it.next()).isRevoked(cert)) return "Certificate revoked"; } } if (ocspResponse != null) { //TODO implement OCSP response validation log.debug(Channel.TECH, "Certificate validation against OCSP response not implemented, skipped"); } return null; } /** * Verifies a certificate chain against a KeyStore. * @param certs the certificate chain * @param keystore the <CODE>KeyStore</CODE> * @param crls the certificate revocation list or <CODE>null</CODE> * @param calendar the date or <CODE>null</CODE> for the current date * @return <CODE>null</CODE> if the certificate chain could be validated or a * <CODE>Object[]{cert,error}</CODE> where <CODE>cert</CODE> is the * failed certificate and <CODE>error</CODE> is the error message */ public static Object[] verifyCertificates(Certificate certs[], KeyStore keystore, Collection crls, OCSPResponse ocspResponse, Calendar calendar) { if (calendar == null) calendar = new GregorianCalendar(); for (int k = 0; k < certs.length; ++k) { X509Certificate cert = (X509Certificate)certs[k]; String err = verifyCertificate(cert, crls, ocspResponse, calendar); if (err != null) return new Object[]{cert, err}; try { for (Enumeration aliases = keystore.aliases(); aliases.hasMoreElements();) { try { String alias = (String)aliases.nextElement(); if (!keystore.isCertificateEntry(alias)) continue; X509Certificate certStoreX509 = (X509Certificate)keystore.getCertificate(alias); if (verifyCertificate(certStoreX509, crls, ocspResponse, calendar) != null) continue; try { cert.verify(certStoreX509.getPublicKey()); return null; } catch (Exception e) { continue; } } catch (Exception ex) { } } } catch (Exception e) { } int j; for (j = 0; j < certs.length; ++j) { if (j == k) continue; X509Certificate certNext = (X509Certificate)certs[j]; try { cert.verify(certNext.getPublicKey()); break; } catch (Exception e) { } } if (j == certs.length) return new Object[]{cert, "Cannot be verified against the KeyStore or the certificate chain"}; } return new Object[]{null, "Invalid state. Possible circular certificate chain"}; } /** * Get the "issuer" from the TBSCertificate bytes that are passed in * @param enc a TBSCertificate in a byte array * @return a DERObject */ private static DERObject getIssuer(byte[] enc) { try { ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(enc)); ASN1Sequence seq = (ASN1Sequence)in.readObject(); return (DERObject)seq.getObjectAt(seq.getObjectAt(0) instanceof DERTaggedObject ? 3 : 2); } catch (IOException e) { throw new ExceptionConverter(e); } } /** * Get the "subject" from the TBSCertificate bytes that are passed in * @param enc A TBSCertificate in a byte array * @return a DERObject */ private static DERObject getSubject(byte[] enc) { try { ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(enc)); ASN1Sequence seq = (ASN1Sequence)in.readObject(); return (DERObject)seq.getObjectAt(seq.getObjectAt(0) instanceof DERTaggedObject ? 5 : 4); } catch (IOException e) { throw new ExceptionConverter(e); } } /** * Get the issuer fields from an X509 Certificate * @param cert an X509Certificate * @return an X509Name */ public static X509Name getIssuerFields(X509Certificate cert) { try { return new X509Name((ASN1Sequence)getIssuer(cert.getTBSCertificate())); } catch (Exception e) { throw new ExceptionConverter(e); } } /** * Get the subject fields from an X509 Certificate * @param cert an X509Certificate * @return an X509Name */ public static X509Name getSubjectFields(X509Certificate cert) { try { return new X509Name((ASN1Sequence)getSubject(cert.getTBSCertificate())); } catch (Exception e) { throw new ExceptionConverter(e); } } /** * Gets the bytes for the PKCS#1 object. * @return a byte array */ public byte[] getEncodedPKCS1() { try { // if (externalDigest != null) // digest = externalDigest; // else pkcs1SigValue = sig.sign(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); ASN1OutputStream dout = new ASN1OutputStream(bOut); dout.writeObject(new DEROctetString(pkcs1SigValue)); dout.close(); return bOut.toByteArray(); } catch (Exception e) { throw new ExceptionConverter(e); } } /** * @deprecated not used anymore */ public void setExternalDigest(byte signature[], byte adbePkcs7Sha1Data[], String digestEncryptionAlgorithm) { } /** * Gets the bytes for the PKCS7SignedData object. * @return the bytes for the PKCS7SignedData object * @throws SPISignatureException */ public byte[] getEncodedPKCS7(){ return getEncodedPKCS7(null, null); } /** * Holds value of properties for timestamp server. */ private String usernameTimestamp = null; private String passwordTimestamp = null; private String serverTimestamp = null; public byte[] getSignedAttrs() throws IOException { return cmsSignature.getEncodedSignedAttrs(); } /** * @deprecated not used anymore */ public byte[] getAuthenticatedAttributeBytes(byte secondDigest[], Calendar signingTime) { return null; } public String getReason() { return this.reason; } public void setReason(String reason) { this.reason = reason; } public String getLocation() { return this.location; } public void setLocation(String location) { this.location = location; } public String getContactInfo() { return contactInfo; } public void setContactInfo(String contactInfo) { this.contactInfo = contactInfo; } public byte[] getDictionaryCert() { return dictionaryCert; } public void setDictionaryCert(byte[] dictionaryCert) { this.dictionaryCert = dictionaryCert; } public int[] getByteRange() { return byteRange; } public void setByteRange(int[] byteRange) { this.byteRange = byteRange; } public byte[] getCONTENTSContent() { return contentsKey; } private int xRefComputedRevision; public void setXRefComputedRevision(int xRefComputedRevision) { this.xRefComputedRevision = xRefComputedRevision; } public int getXRefComputedRevision() { return xRefComputedRevision; } //AcroFields properties public boolean signatureCoversWholeDocument() { return acroFields.signatureCoversWholeDocument(signatureFieldName); } public int getRevision() { return acroFields.getRevision(signatureFieldName); } public int getTotalRevisions() { return acroFields.getTotalRevisions(); } public int getRevisionLength() { return acroFields.getRevisionLength(signatureFieldName); } public float[] getFieldPositions() { return acroFields.getFieldPositions(signatureFieldName); } public List<String> getSignatureNames() { return acroFields.getSignatureNames(); } public List<String> getSignatureNames(boolean withDocTS) { return acroFields.getSignatureNames(withDocTS); } public String getSubFilter() { return subFilter; } public Calendar getSignDate() { return this.dicoSignDate!=null?this.dicoSignDate:this.cmsSignDate; } public Calendar getDicoSignDate() { return this.dicoSignDate; } public Calendar getCmsSignDate() { return this.cmsSignDate; } public void setDicoSignDate(Calendar signDate) { this.dicoSignDate = signDate; } public String getSignName() { return this.signName; } public void setSignName(String signName) { this.signName = signName; } /** * a class that holds an X509 name */ public static class X509Name { /** * country code - StringType(SIZE(2)) */ public static final DERObjectIdentifier C = new DERObjectIdentifier("2.5.4.6"); /** * organization - StringType(SIZE(1..64)) */ public static final DERObjectIdentifier O = new DERObjectIdentifier("2.5.4.10"); /** * organizational unit name - StringType(SIZE(1..64)) */ public static final DERObjectIdentifier OU = new DERObjectIdentifier("2.5.4.11"); /** * Title */ public static final DERObjectIdentifier T = new DERObjectIdentifier("2.5.4.12"); /** * common name - StringType(SIZE(1..64)) */ public static final DERObjectIdentifier CN = new DERObjectIdentifier("2.5.4.3"); /** * device serial number name - StringType(SIZE(1..64)) */ public static final DERObjectIdentifier SN = new DERObjectIdentifier("2.5.4.5"); /** * locality name - StringType(SIZE(1..64)) */ public static final DERObjectIdentifier L = new DERObjectIdentifier("2.5.4.7"); /** * state, or province name - StringType(SIZE(1..64)) */ public static final DERObjectIdentifier ST = new DERObjectIdentifier("2.5.4.8"); /** Naming attribute of type X520name */ public static final DERObjectIdentifier SURNAME = new DERObjectIdentifier("2.5.4.4"); /** Naming attribute of type X520name */ public static final DERObjectIdentifier GIVENNAME = new DERObjectIdentifier("2.5.4.42"); /** Naming attribute of type X520name */ public static final DERObjectIdentifier INITIALS = new DERObjectIdentifier("2.5.4.43"); /** Naming attribute of type X520name */ public static final DERObjectIdentifier GENERATION = new DERObjectIdentifier("2.5.4.44"); /** Naming attribute of type X520name */ public static final DERObjectIdentifier UNIQUE_IDENTIFIER = new DERObjectIdentifier("2.5.4.45"); /** * Email address (RSA PKCS#9 extension) - IA5String. * <p>Note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here. */ public static final DERObjectIdentifier EmailAddress = new DERObjectIdentifier("1.2.840.113549.1.9.1"); /** * email address in Verisign certificates */ public static final DERObjectIdentifier E = EmailAddress; /** object identifier */ public static final DERObjectIdentifier DC = new DERObjectIdentifier("0.9.2342.19200300.100.1.25"); /** LDAP User id. */ public static final DERObjectIdentifier UID = new DERObjectIdentifier("0.9.2342.19200300.100.1.1"); /** A LinkedHashMap with default symbols */ public static LinkedHashMap DefaultSymbols = new LinkedHashMap(); static { DefaultSymbols.put(C, "C"); DefaultSymbols.put(O, "O"); DefaultSymbols.put(T, "T"); DefaultSymbols.put(OU, "OU"); DefaultSymbols.put(CN, "CN"); DefaultSymbols.put(L, "L"); DefaultSymbols.put(ST, "ST"); DefaultSymbols.put(SN, "SN"); DefaultSymbols.put(EmailAddress, "E"); DefaultSymbols.put(DC, "DC"); DefaultSymbols.put(UID, "UID"); DefaultSymbols.put(SURNAME, "SURNAME"); DefaultSymbols.put(GIVENNAME, "GIVENNAME"); DefaultSymbols.put(INITIALS, "INITIALS"); DefaultSymbols.put(GENERATION, "GENERATION"); } /** A LinkedHashMap with values */ public LinkedHashMap values = new LinkedHashMap(); /** * Constructs an X509 name * @param seq an ASN1 Sequence */ public X509Name(ASN1Sequence seq) { Enumeration e = seq.getObjects(); while (e.hasMoreElements()) { ASN1Set set = (ASN1Set)e.nextElement(); for (int i = 0; i < set.size(); i++) { ASN1Sequence s = (ASN1Sequence)set.getObjectAt(i); String id = (String)DefaultSymbols.get(s.getObjectAt(0)); if (id == null) continue; ArrayList vs = (ArrayList)values.get(id); if (vs == null) { vs = new ArrayList(); values.put(id, vs); } vs.add(((DERString)s.getObjectAt(1)).getString()); } } } /** * Constructs an X509 name * @param dirName a directory name */ public X509Name(String dirName) { X509NameTokenizer nTok = new X509NameTokenizer(dirName); while (nTok.hasMoreTokens()) { String token = nTok.nextToken(); int index = token.indexOf('='); if (index == -1) { throw new IllegalArgumentException("badly formated directory string"); } String id = token.substring(0, index).toUpperCase(); String value = token.substring(index + 1); ArrayList vs = (ArrayList)values.get(id); if (vs == null) { vs = new ArrayList(); values.put(id, vs); } vs.add(value); } } public String getField(String name) { ArrayList vs = (ArrayList)values.get(name); return vs == null ? null : (String)vs.get(0); } /** * gets a field array from the values LinkedHashMap * @param name * @return an ArrayList */ public ArrayList getFieldArray(String name) { ArrayList vs = (ArrayList)values.get(name); return vs == null ? null : vs; } /** * getter for values * @return a LinkedHashMap with the fields of the X509 name */ public LinkedHashMap getFields() { return values; } /** * @see java.lang.Object#toString() */ public String toString() { return values.toString(); } } /** * class for breaking up an X500 Name into it's component tokens, ala * java.util.StringTokenizer. We need this class as some of the * lightweight Java environment don't support classes like * StringTokenizer. */ public static class X509NameTokenizer { private String oid; private int index; private StringBuffer buf = new StringBuffer(); public X509NameTokenizer( String oid) { this.oid = oid; this.index = -1; } public boolean hasMoreTokens() { return (index != oid.length()); } public String nextToken() { if (index == oid.length()) { return null; } int end = index + 1; boolean quoted = false; boolean escaped = false; buf.setLength(0); while (end != oid.length()) { char c = oid.charAt(end); if (c == '"') { if (!escaped) { quoted = !quoted; } else { buf.append(c); } escaped = false; } else { if (escaped || quoted) { buf.append(c); escaped = false; } else if (c == '\\') { escaped = true; } else if (c == ',') { break; } else { buf.append(c); } } end++; } index = end; return buf.toString().trim(); } } public String getPasswordTimestamp() { return passwordTimestamp; } public void setPasswordTimestamp(String passwordTimestamp) { this.passwordTimestamp = passwordTimestamp; } public String getServerTimestamp() { return serverTimestamp; } public void setServerTimestamp(String serverTimestamp) { this.serverTimestamp = serverTimestamp; } public String getUsernameTimestamp() { return usernameTimestamp; } public void setUsernameTimestamp(String usernameTimestamp) { this.usernameTimestamp = usernameTimestamp; } public byte[] getSignatureValue() { return cmsSignature.getSignatureValue(); } public byte[] getDigest() { return cmsSignature.getDigestAttribute(); } private byte[] contentTimestampDigest; public byte[] getContentTimestampDigest() { return contentTimestampDigest; } public ESSCertID getSigningCertificateAttribute() { return cmsSignature.getSigningCertificateAttribute(); } public ESSCertIDv2 getSigningCertificateV2Attribute() { return cmsSignature.getSigningCertificateV2Attribute(); } public SignaturePolicyIdentifier getSignaturePolicyIdentifierAttribute() { return cmsSignature.getSignaturePolicyIdentifierAttribute(); } public SignerInformationStore getCounterSignatures() { return cmsSignature.getCounterSignatures(); } public DEREncodable getContentTypeAttribute() { return cmsSignature.getContentTypeAttribute(); } public DEREncodable getContentReferenceAttribute() { return cmsSignature.getContentReferenceAttribute(); } public ContentIdentifier getContentIdentifierAttribute() { return cmsSignature.getContentIdentifierAttribute(); } public ContentHints getContentHintsAttribute() { return cmsSignature.getContentHintsAttribute(); } public CommitmentTypeIndication getCommitmentTypeIndicationAttribute() { return cmsSignature.getCommitmentTypeIndicationAttribute(); } public SignerLocation getSignerLocationAttribute() { return cmsSignature.getSignerLocationAttribute(); } public SignerAttribute getSignerAttributesAttribute() { return cmsSignature.getSignerAttributesAttribute(); } public TimestampToken getContentTimestamp() { try { return cmsSignature.getContentTimestamp(); } catch (Exception e) { throw new ExceptionConverter(e); } } public TimestampToken getDocTimeStampValue() { return this.docTimestampTStoken; } public Signature getSig() { return sig; } public void setSig(Signature sig) { this.sig = sig; } public TimestampToken getTimestampToken() { return timestampToken; } public String getTimeStampDigestAlgo() { return timeStampDigestAlgo; } public void setTimeStampDigestAlgo(String timeStampDigestAlgo) { this.timeStampDigestAlgo = timeStampDigestAlgo; } public String getTimeStampPolicyId() { return timeStampPolicyId; } public void setTimeStampPolicyId(String timeStampPolicyId) { this.timeStampPolicyId = timeStampPolicyId; } public boolean isTimeStampUseNonce() { return timeStampUseNonce; } public void setTimeStampUseNonce(boolean timeStampUseNonce) { this.timeStampUseNonce = timeStampUseNonce; } public void setOcspResponses(Collection<OCSPResponse> ocspResponses) { this.ocspResponses = ocspResponses; } public Collection<OCSPResponse> getOcspResponses() { return ocspResponses; } public String getSignatureFieldName() { return signatureFieldName; } }