aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/lowagie/text/pdf/PdfPKCS7.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/lowagie/text/pdf/PdfPKCS7.java')
-rw-r--r--src/main/java/com/lowagie/text/pdf/PdfPKCS7.java1276
1 files changed, 1276 insertions, 0 deletions
diff --git a/src/main/java/com/lowagie/text/pdf/PdfPKCS7.java b/src/main/java/com/lowagie/text/pdf/PdfPKCS7.java
new file mode 100644
index 0000000..d9f3473
--- /dev/null
+++ b/src/main/java/com/lowagie/text/pdf/PdfPKCS7.java
@@ -0,0 +1,1276 @@
+/*
+ * 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.lowagie.text.pdf;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.MessageDigest;
+import java.security.SignatureException;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.KeyStore;
+import java.io.File;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import com.lowagie.text.ExceptionConverter;
+
+import com.lowagie.bc.asn1.ASN1InputStream;
+import com.lowagie.bc.asn1.DERObject;
+import com.lowagie.bc.asn1.ASN1Sequence;
+import com.lowagie.bc.asn1.ASN1Set;
+import com.lowagie.bc.asn1.DERObjectIdentifier;
+import com.lowagie.bc.asn1.DEROctetString;
+import com.lowagie.bc.asn1.DERTaggedObject;
+import com.lowagie.bc.asn1.DERInteger;
+import com.lowagie.bc.asn1.ASN1TaggedObject;
+import com.lowagie.bc.asn1.DERConstructedSet;
+import com.lowagie.bc.asn1.DERSequence;
+import com.lowagie.bc.asn1.DERNull;
+import com.lowagie.bc.asn1.ASN1EncodableVector;
+import com.lowagie.bc.asn1.DERSet;
+import com.lowagie.bc.asn1.DERString;
+import com.lowagie.bc.asn1.DERUTCTime;
+import com.lowagie.bc.asn1.ASN1OutputStream;
+
+/**
+ * This class does all the processing related to signing and verifying a PKCS#7
+ * signature.
+ * <p>
+ * It's based in code found at org.bouncycastle.
+ */
+public class PdfPKCS7 {
+
+ private byte sigAttr[];
+ private byte digestAttr[];
+ private int version, signerversion;
+ private Set digestalgos;
+ private Collection certs, crls;
+ private X509Certificate signCert;
+ private byte[] digest;
+ private MessageDigest messageDigest;
+ private String digestAlgorithm, digestEncryptionAlgorithm;
+ private Signature sig;
+ private transient PrivateKey privKey;
+ private byte RSAdata[];
+ private boolean verified;
+ private boolean verifyResult;
+ private byte externalDigest[];
+ private byte externalRSAdata[];
+
+ private static final String ID_PKCS7_DATA = "1.2.840.113549.1.7.1";
+ private static final String ID_PKCS7_SIGNED_DATA = "1.2.840.113549.1.7.2";
+ private static final String ID_MD5 = "1.2.840.113549.2.5";
+ private static final String ID_MD2 = "1.2.840.113549.2.2";
+ private static final String ID_SHA1 = "1.3.14.3.2.26";
+ private static final String ID_RSA = "1.2.840.113549.1.1.1";
+ private static final String ID_DSA = "1.2.840.10040.4.1";
+ private static final String ID_CONTENT_TYPE = "1.2.840.113549.1.9.3";
+ private static final String ID_MESSAGE_DIGEST = "1.2.840.113549.1.9.4";
+ private static final String ID_SIGNING_TIME = "1.2.840.113549.1.9.5";
+ private static final String ID_MD2RSA = "1.2.840.113549.1.1.2";
+ private static final String ID_MD5RSA = "1.2.840.113549.1.1.4";
+ private static final String ID_SHA1RSA = "1.2.840.113549.1.1.5";
+ /**
+ * Holds value of property reason.
+ */
+ private String reason;
+
+ /**
+ * Holds value of property location.
+ */
+ private String location;
+
+ /**
+ * Holds value of property signDate.
+ */
+ private Calendar signDate;
+
+ /**
+ * Holds value of property signName.
+ */
+ private String signName;
+
+ /**
+ * 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
+ * @throws SecurityException on error
+ * @throws CRLException on error
+ * @throws InvalidKeyException on error
+ * @throws CertificateException on error
+ * @throws NoSuchProviderException on error
+ * @throws NoSuchAlgorithmException on error
+ * @throws IOException on error
+ */
+ public PdfPKCS7(byte[] contentsKey, byte[] certsKey, String provider) throws SecurityException, CRLException, InvalidKeyException, CertificateException, NoSuchProviderException, NoSuchAlgorithmException, IOException {
+ CertificateFactory cf;
+ if (provider == null)
+ cf = CertificateFactory.getInstance("X.509");
+ else
+ cf = CertificateFactory.getInstance("X.509", provider);
+ if (provider == null)
+ certs = cf.generateCertificates(new ByteArrayInputStream(certsKey));
+ signCert = (X509Certificate)certs.iterator().next();
+ crls = new ArrayList();
+ ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(contentsKey));
+ digest = ((DEROctetString)in.readObject()).getOctets();
+ if (provider == null)
+ sig = Signature.getInstance("SHA1withRSA");
+ else
+ sig = Signature.getInstance("SHA1withRSA", provider);
+ sig.initVerify(signCert.getPublicKey());
+ }
+
+ /**
+ * Verifies a signature using the sub-filter adbe.pkcs7.detached or
+ * adbe.pkcs7.sha1.
+ * @param contentsKey the /Contents key
+ * @param provider the provider or <code>null</code> for the default provider
+ * @throws SecurityException on error
+ * @throws CRLException on error
+ * @throws InvalidKeyException on error
+ * @throws CertificateException on error
+ * @throws NoSuchProviderException on error
+ * @throws NoSuchAlgorithmException on error
+ */
+ public PdfPKCS7(byte[] contentsKey, String provider) throws SecurityException, CRLException, InvalidKeyException, CertificateException, NoSuchProviderException, NoSuchAlgorithmException {
+ ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(contentsKey));
+
+ //
+ // Basic checks to make sure it's a PKCS#7 SignedData Object
+ //
+ DERObject pkcs;
+
+ try {
+ pkcs = din.readObject();
+ }
+ catch (IOException e) {
+ throw new SecurityException("can't decode PKCS7SignedData object");
+ }
+ if (!(pkcs instanceof ASN1Sequence)) {
+ throw new SecurityException("Not a valid PKCS#7 object - not a sequence");
+ }
+ ASN1Sequence signedData = (ASN1Sequence)pkcs;
+ DERObjectIdentifier objId = (DERObjectIdentifier)signedData.getObjectAt(0);
+ if (!objId.getId().equals(ID_PKCS7_SIGNED_DATA))
+ throw new SecurityException("Not a valid PKCS#7 object - not signed data");
+ ASN1Sequence content = (ASN1Sequence)((DERTaggedObject)signedData.getObjectAt(1)).getObject();
+ // the positions that we care are:
+ // 0 - version
+ // 1 - digestAlgorithms
+ // 2 - possible ID_PKCS7_DATA
+ // (the certificates and crls are taken out by other means)
+ // last - signerInfos
+
+ // the version
+ version = ((DERInteger)content.getObjectAt(0)).getValue().intValue();
+
+ // the digestAlgorithms
+ digestalgos = new HashSet();
+ Enumeration e = ((ASN1Set)content.getObjectAt(1)).getObjects();
+ while (e.hasMoreElements())
+ {
+ ASN1Sequence s = (ASN1Sequence)e.nextElement();
+ DERObjectIdentifier o = (DERObjectIdentifier)s.getObjectAt(0);
+ digestalgos.add(o.getId());
+ }
+
+ // the certificates and crls
+ CertificateFactory cf;
+ if (provider == null)
+ cf = CertificateFactory.getInstance("X.509");
+ else
+ cf = CertificateFactory.getInstance("X.509", provider);
+ certs = cf.generateCertificates(new ByteArrayInputStream(contentsKey));
+ crls = cf.generateCRLs(new ByteArrayInputStream(contentsKey));
+
+ // the possible ID_PKCS7_DATA
+ ASN1Sequence rsaData = (ASN1Sequence)content.getObjectAt(2);
+ if (rsaData.size() > 1) {
+ DEROctetString rsaDataContent = (DEROctetString)((DERTaggedObject)rsaData.getObjectAt(1)).getObject();
+ RSAdata = rsaDataContent.getOctets();
+ }
+
+ // the signerInfos
+ int next = 3;
+ while (content.getObjectAt(next) instanceof DERTaggedObject)
+ ++next;
+ ASN1Set signerInfos = (ASN1Set)content.getObjectAt(next);
+ if (signerInfos.size() != 1)
+ throw new SecurityException("This PKCS#7 object has multiple SignerInfos - only one is supported at this time");
+ ASN1Sequence signerInfo = (ASN1Sequence)signerInfos.getObjectAt(0);
+ // the positions that we care are
+ // 0 - version
+ // 1 - the signing certificate serial number
+ // 2 - the digest algorithm
+ // 3 or 4 - digestEncryptionAlgorithm
+ // 4 or 5 - encryptedDigest
+ signerversion = ((DERInteger)signerInfo.getObjectAt(0)).getValue().intValue();
+ // Get the signing certificate
+ ASN1Sequence issuerAndSerialNumber = (ASN1Sequence)signerInfo.getObjectAt(1);
+ BigInteger serialNumber = ((DERInteger)issuerAndSerialNumber.getObjectAt(1)).getValue();
+ for (Iterator i = certs.iterator(); i.hasNext();) {
+ X509Certificate cert = (X509Certificate)i.next();
+ if (serialNumber.equals(cert.getSerialNumber())) {
+ signCert = cert;
+ break;
+ }
+ }
+ if (signCert == null) {
+ throw new SecurityException("Can't find signing certificate with serial " + serialNumber.toString(16));
+ }
+ digestAlgorithm = ((DERObjectIdentifier)((ASN1Sequence)signerInfo.getObjectAt(2)).getObjectAt(0)).getId();
+ next = 3;
+ if (signerInfo.getObjectAt(next) instanceof ASN1TaggedObject) {
+ ASN1TaggedObject tagsig = (ASN1TaggedObject)signerInfo.getObjectAt(next);
+ ASN1Sequence sseq = (ASN1Sequence)tagsig.getObject();
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ ASN1OutputStream dout = new ASN1OutputStream(bOut);
+ try {
+ ASN1EncodableVector attribute = new ASN1EncodableVector();
+ for (int k = 0; k < sseq.size(); ++k) {
+ attribute.add(sseq.getObjectAt(k));
+ }
+ dout.writeObject(new DERSet(attribute));
+ dout.close();
+ }
+ catch (IOException ioe){}
+ sigAttr = bOut.toByteArray();
+
+ for (int k = 0; k < sseq.size(); ++k) {
+ ASN1Sequence seq2 = (ASN1Sequence)sseq.getObjectAt(k);
+ if (((DERObjectIdentifier)seq2.getObjectAt(0)).getId().equals(ID_MESSAGE_DIGEST)) {
+ ASN1Set set = (ASN1Set)seq2.getObjectAt(1);
+ digestAttr = ((DEROctetString)set.getObjectAt(0)).getOctets();
+ break;
+ }
+ }
+ if (digestAttr == null)
+ throw new SecurityException("Authenticated attribute is missing the digest.");
+ ++next;
+ }
+ digestEncryptionAlgorithm = ((DERObjectIdentifier)((ASN1Sequence)signerInfo.getObjectAt(next++)).getObjectAt(0)).getId();
+ digest = ((DEROctetString)signerInfo.getObjectAt(next)).getOctets();
+ if (RSAdata != null || digestAttr != null) {
+ if (provider == null || provider.startsWith("SunPKCS11"))
+ messageDigest = MessageDigest.getInstance(getHashAlgorithm());
+ else
+ messageDigest = MessageDigest.getInstance(getHashAlgorithm(), provider);
+ }
+ if (provider == null)
+ sig = Signature.getInstance(getDigestAlgorithm());
+ else
+ sig = Signature.getInstance(getDigestAlgorithm(), provider);
+ sig.initVerify(signCert.getPublicKey());
+ }
+
+ /**
+ * Generates a signature.
+ * @param privKey the private key
+ * @param certChain the certificate chain
+ * @param crlList the certificate revocation list
+ * @param hashAlgorithm the hash algorithm
+ * @param provider the provider or <code>null</code> for the default provider
+ * @param hasRSAdata <CODE>true</CODE> if the sub-filter is adbe.pkcs7.sha1
+ * @throws SecurityException on error
+ * @throws InvalidKeyException on error
+ * @throws NoSuchProviderException on error
+ * @throws NoSuchAlgorithmException on error
+ */
+ public PdfPKCS7(PrivateKey privKey, Certificate[] certChain, CRL[] crlList,
+ String hashAlgorithm, String provider, boolean hasRSAdata)
+ throws SecurityException, InvalidKeyException, NoSuchProviderException,
+ NoSuchAlgorithmException
+ {
+ this.privKey = privKey;
+
+ if (hashAlgorithm.equals("MD5")) {
+ digestAlgorithm = ID_MD5;
+ }
+ else if (hashAlgorithm.equals("MD2")) {
+ digestAlgorithm = ID_MD2;
+ }
+ else if (hashAlgorithm.equals("SHA")) {
+ digestAlgorithm = ID_SHA1;
+ }
+ else if (hashAlgorithm.equals("SHA1")) {
+ digestAlgorithm = ID_SHA1;
+ }
+ else {
+ throw new NoSuchAlgorithmException("Unknown Hash Algorithm "+hashAlgorithm);
+ }
+
+ version = signerversion = 1;
+ certs = new ArrayList();
+ crls = new ArrayList();
+ digestalgos = new HashSet();
+ digestalgos.add(digestAlgorithm);
+
+ //
+ // Copy in the certificates and crls used to sign the private key.
+ //
+ signCert = (X509Certificate)certChain[0];
+ for (int i = 0;i < certChain.length;i++) {
+ certs.add(certChain[i]);
+ }
+
+ if (crlList != null) {
+ for (int i = 0;i < crlList.length;i++) {
+ crls.add(crlList[i]);
+ }
+ }
+
+ if (privKey != null) {
+ //
+ // Now we have private key, find out what the digestEncryptionAlgorithm is.
+ //
+ digestEncryptionAlgorithm = privKey.getAlgorithm();
+ if (digestEncryptionAlgorithm.equals("RSA")) {
+ digestEncryptionAlgorithm = ID_RSA;
+ }
+ else if (digestEncryptionAlgorithm.equals("DSA")) {
+ digestEncryptionAlgorithm = ID_DSA;
+ }
+ else {
+ throw new NoSuchAlgorithmException("Unknown Key Algorithm "+digestEncryptionAlgorithm);
+ }
+ }
+ if (hasRSAdata) {
+ RSAdata = new byte[0];
+ if (provider == null || provider.startsWith("SunPKCS11"))
+ messageDigest = MessageDigest.getInstance(getHashAlgorithm());
+ else
+ messageDigest = MessageDigest.getInstance(getHashAlgorithm(), provider);
+ }
+
+ if (privKey != null) {
+ if (provider == null)
+ sig = Signature.getInstance(getDigestAlgorithm());
+ else
+ sig = Signature.getInstance(getDigestAlgorithm(), provider);
+
+ sig.initSign(privKey);
+ }
+ }
+
+ /**
+ * 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 (RSAdata != null || digestAttr != null)
+ messageDigest.update(buf, off, len);
+ else
+ sig.update(buf, off, len);
+ }
+
+ /**
+ * Verify the digest.
+ * @throws SignatureException on error
+ * @return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE> otherwise
+ */
+ public boolean verify() throws SignatureException {
+ if (verified)
+ return verifyResult;
+ if (sigAttr != null) {
+ sig.update(sigAttr);
+ if (RSAdata != null) {
+ byte msd[] = messageDigest.digest();
+ messageDigest.update(msd);
+ }
+ verifyResult = (Arrays.equals(messageDigest.digest(), digestAttr) && sig.verify(digest));
+ }
+ else {
+ if (RSAdata != null)
+ sig.update(messageDigest.digest());
+ verifyResult = sig.verify(digest);
+ }
+ verified = true;
+ return verifyResult;
+ }
+
+ /**
+ * 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[0]);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Get the algorithm used to calculate the message digest
+ * @return the algorithm used to calculate the message digest
+ */
+ public String getDigestAlgorithm() {
+ String dea = digestEncryptionAlgorithm;
+
+ if (digestEncryptionAlgorithm.equals(ID_RSA)) {
+ dea = "RSA";
+ }
+ else if (digestEncryptionAlgorithm.equals(ID_DSA)) {
+ dea = "DSA";
+ }
+
+ return getHashAlgorithm() + "with" + dea;
+ }
+
+ /**
+ * Returns the algorithm.
+ * @return the digest algorithm
+ */
+ public String getHashAlgorithm() {
+ String da = digestAlgorithm;
+
+ if (digestAlgorithm.equals(ID_MD5) || digestAlgorithm.equals(ID_MD5RSA)) {
+ da = "MD5";
+ }
+ else if (digestAlgorithm.equals(ID_MD2) || digestAlgorithm.equals(ID_MD2RSA)) {
+ da = "MD2";
+ }
+ else if (digestAlgorithm.equals(ID_SHA1) || digestAlgorithm.equals(ID_SHA1RSA)) {
+ da = "SHA1";
+ }
+ return da;
+ }
+
+ /**
+ * Loads the default root certificates at &lt;java.home&gt;/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 &lt;java.home&gt;/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{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, 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";
+ }
+ }
+ 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 validade 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, 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, 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, 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
+ digest = sig.sign();
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ ASN1OutputStream dout = new ASN1OutputStream(bOut);
+ dout.writeObject(new DEROctetString(digest));
+ dout.close();
+
+ return bOut.toByteArray();
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+ /**
+ * Sets the digest/signature to an external calculated value.
+ * @param digest the digest. This is the actual signature
+ * @param RSAdata the extra data that goes into the data tag in PKCS#7
+ * @param digestEncryptionAlgorithm the encryption algorithm. It may must be <CODE>null</CODE> if the <CODE>digest</CODE>
+ * is also <CODE>null</CODE>. If the <CODE>digest</CODE> is not <CODE>null</CODE>
+ * then it may be "RSA" or "DSA"
+ */
+ public void setExternalDigest(byte digest[], byte RSAdata[], String digestEncryptionAlgorithm) {
+ externalDigest = digest;
+ externalRSAdata = RSAdata;
+ if (digestEncryptionAlgorithm != null) {
+ if (digestEncryptionAlgorithm.equals("RSA")) {
+ this.digestEncryptionAlgorithm = ID_RSA;
+ }
+ else if (digestEncryptionAlgorithm.equals("DSA")) {
+ this.digestEncryptionAlgorithm = ID_DSA;
+ }
+ else
+ throw new ExceptionConverter(new NoSuchAlgorithmException("Unknown Key Algorithm "+digestEncryptionAlgorithm));
+ }
+ }
+
+ /**
+ * Gets the bytes for the PKCS7SignedData object.
+ * @return the bytes for the PKCS7SignedData object
+ */
+ public byte[] getEncodedPKCS7() {
+ return getEncodedPKCS7(null, null);
+ }
+
+ /**
+ * Gets the bytes for the PKCS7SignedData object. Optionally the authenticatedAttributes
+ * in the signerInfo can also be set. If either of the parameters is <CODE>null</CODE>, none will be used.
+ * @param secondDigest the digest in the authenticatedAttributes
+ * @param signingTime the signing time in the authenticatedAttributes
+ * @return the bytes for the PKCS7SignedData object
+ */
+ public byte[] getEncodedPKCS7(byte secondDigest[], Calendar signingTime) {
+ try {
+ if (externalDigest != null) {
+ digest = externalDigest;
+ if (RSAdata != null)
+ RSAdata = externalRSAdata;
+ }
+ else if (externalRSAdata != null && RSAdata != null) {
+ RSAdata = externalRSAdata;
+ sig.update(RSAdata);
+ digest = sig.sign();
+ }
+ else {
+ if (RSAdata != null) {
+ RSAdata = messageDigest.digest();
+ sig.update(RSAdata);
+ }
+ digest = sig.sign();
+ }
+
+ // Create the set of Hash algorithms
+ DERConstructedSet digestAlgorithms = new DERConstructedSet();
+ for(Iterator it = digestalgos.iterator(); it.hasNext();) {
+ ASN1EncodableVector algos = new ASN1EncodableVector();
+ algos.add(new DERObjectIdentifier((String)it.next()));
+ algos.add(new DERNull());
+ digestAlgorithms.addObject(new DERSequence(algos));
+ }
+
+ // Create the contentInfo.
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_PKCS7_DATA));
+ if (RSAdata != null)
+ v.add(new DERTaggedObject(0, new DEROctetString(RSAdata)));
+ DERSequence contentinfo = new DERSequence(v);
+
+ // Get all the certificates
+ //
+ v = new ASN1EncodableVector();
+ for (Iterator i = certs.iterator(); i.hasNext();) {
+ ASN1InputStream tempstream = new ASN1InputStream(new ByteArrayInputStream(((X509Certificate)i.next()).getEncoded()));
+ v.add(tempstream.readObject());
+ }
+
+ DERSet dercertificates = new DERSet(v);
+
+ // Create signerinfo structure.
+ //
+ ASN1EncodableVector signerinfo = new ASN1EncodableVector();
+
+ // Add the signerInfo version
+ //
+ signerinfo.add(new DERInteger(signerversion));
+
+ v = new ASN1EncodableVector();
+ v.add(getIssuer(signCert.getTBSCertificate()));
+ v.add(new DERInteger(signCert.getSerialNumber()));
+ signerinfo.add(new DERSequence(v));
+
+ // Add the digestAlgorithm
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(digestAlgorithm));
+ v.add(new DERNull());
+ signerinfo.add(new DERSequence(v));
+
+ // add the authenticated attribute if present
+ if (secondDigest != null && signingTime != null) {
+ ASN1EncodableVector attribute = new ASN1EncodableVector();
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_CONTENT_TYPE));
+ v.add(new DERSet(new DERObjectIdentifier(ID_PKCS7_DATA)));
+ attribute.add(new DERSequence(v));
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_SIGNING_TIME));
+ v.add(new DERSet(new DERUTCTime(signingTime.getTime())));
+ attribute.add(new DERSequence(v));
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_MESSAGE_DIGEST));
+ v.add(new DERSet(new DEROctetString(secondDigest)));
+ attribute.add(new DERSequence(v));
+ signerinfo.add(new DERTaggedObject(false, 0, new DERSet(attribute)));
+ }
+ // Add the digestEncryptionAlgorithm
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(digestEncryptionAlgorithm));
+ v.add(new DERNull());
+ signerinfo.add(new DERSequence(v));
+
+ // Add the digest
+ signerinfo.add(new DEROctetString(digest));
+
+
+ // Finally build the body out of all the components above
+ ASN1EncodableVector body = new ASN1EncodableVector();
+ body.add(new DERInteger(version));
+ body.add(digestAlgorithms);
+ body.add(contentinfo);
+ body.add(new DERTaggedObject(false, 0, dercertificates));
+
+ if (crls.size() > 0) {
+ v = new ASN1EncodableVector();
+ for (Iterator i = crls.iterator();i.hasNext();) {
+ ASN1InputStream t = new ASN1InputStream(new ByteArrayInputStream((((X509CRL)i.next()).getEncoded())));
+ v.add(t.readObject());
+ }
+ DERSet dercrls = new DERSet(v);
+ body.add(new DERTaggedObject(false, 1, dercrls));
+ }
+
+ // Only allow one signerInfo
+ body.add(new DERSet(new DERSequence(signerinfo)));
+
+ // Now we have the body, wrap it in it's PKCS7Signed shell
+ // and return it
+ //
+ ASN1EncodableVector whole = new ASN1EncodableVector();
+ whole.add(new DERObjectIdentifier(ID_PKCS7_SIGNED_DATA));
+ whole.add(new DERTaggedObject(0, new DERSequence(body)));
+
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ ASN1OutputStream dout = new ASN1OutputStream(bOut);
+ dout.writeObject(new DERSequence(whole));
+ dout.close();
+
+ return bOut.toByteArray();
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+
+ /**
+ * When using authenticatedAttributes the authentication process is different.
+ * The document digest is generated and put inside the attribute. The signing is done over the DER encoded
+ * authenticatedAttributes. This method provides that encoding and the parameters must be
+ * exactly the same as in {@link #getEncodedPKCS7(byte[],Calendar)}.
+ * <p>
+ * A simple example:
+ * <p>
+ * <pre>
+ * Calendar cal = Calendar.getInstance();
+ * PdfPKCS7 pk7 = new PdfPKCS7(key, chain, null, "SHA1", null, false);
+ * MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
+ * byte buf[] = new byte[8192];
+ * int n;
+ * InputStream inp = sap.getRangeStream();
+ * while ((n = inp.read(buf)) &gt; 0) {
+ * messageDigest.update(buf, 0, n);
+ * }
+ * byte hash[] = messageDigest.digest();
+ * byte sh[] = pk7.getAuthenticatedAttributeBytes(hash, cal);
+ * pk7.update(sh, 0, sh.length);
+ * byte sg[] = pk7.getEncodedPKCS7(hash, cal);
+ * </pre>
+ * @param secondDigest the content digest
+ * @param signingTime the signing time
+ * @return the byte array representation of the authenticatedAttributes ready to be signed
+ */
+ public byte[] getAuthenticatedAttributeBytes(byte secondDigest[], Calendar signingTime) {
+ try {
+ ASN1EncodableVector attribute = new ASN1EncodableVector();
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_CONTENT_TYPE));
+ v.add(new DERSet(new DERObjectIdentifier(ID_PKCS7_DATA)));
+ attribute.add(new DERSequence(v));
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_SIGNING_TIME));
+ v.add(new DERSet(new DERUTCTime(signingTime.getTime())));
+ attribute.add(new DERSequence(v));
+ v = new ASN1EncodableVector();
+ v.add(new DERObjectIdentifier(ID_MESSAGE_DIGEST));
+ v.add(new DERSet(new DEROctetString(secondDigest)));
+ attribute.add(new DERSequence(v));
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+
+ ASN1OutputStream dout = new ASN1OutputStream(bOut);
+ dout.writeObject(new DERSet(attribute));
+ dout.close();
+
+ return bOut.toByteArray();
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+ /**
+ * Getter for property reason.
+ * @return Value of property reason.
+ */
+ public String getReason() {
+ return this.reason;
+ }
+
+ /**
+ * Setter for property reason.
+ * @param reason New value of property reason.
+ */
+ public void setReason(String reason) {
+ this.reason = reason;
+ }
+
+ /**
+ * Getter for property location.
+ * @return Value of property location.
+ */
+ public String getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Setter for property location.
+ * @param location New value of property location.
+ */
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ /**
+ * Getter for property signDate.
+ * @return Value of property signDate.
+ */
+ public Calendar getSignDate() {
+ return this.signDate;
+ }
+
+ /**
+ * Setter for property signDate.
+ * @param signDate New value of property signDate.
+ */
+ public void setSignDate(Calendar signDate) {
+ this.signDate = signDate;
+ }
+
+ /**
+ * Getter for property sigName.
+ * @return Value of property sigName.
+ */
+ public String getSignName() {
+ return this.signName;
+ }
+
+ /**
+ * Setter for property sigName.
+ * @param signName New value of property sigName.
+ */
+ 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 HashMap with default symbols */
+ public static HashMap DefaultSymbols = new HashMap();
+
+ 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 HashMap with values */
+ public HashMap values = new HashMap();
+
+ /**
+ * 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 Hashmap
+ * @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 HashMap with the fields of the X509 name
+ */
+ public HashMap 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();
+ }
+ }
+} \ No newline at end of file