Java tutorial
/* * Copyright 2015 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ackpdfbox.app; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.DERSet; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.Attributes; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import org.bouncycastle.tsp.TSPException; import org.bouncycastle.util.Store; public abstract class CreateSignatureBase implements SignatureInterface { private PrivateKey privateKey; private Certificate certificate; private TSAClient tsaClient; private boolean externalSigning; /** * Initialize the signature creator with a keystore (pkcs12) and pin that should be used for the * signature. * * @param keystore is a pkcs12 keystore. * @param pin is the pin for the keystore / private key * @throws KeyStoreException if the keystore has not been initialized (loaded) * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found * @throws UnrecoverableKeyException if the given password is wrong * @throws CertificateException if the certificate is not valid as signing time * @throws IOException if no certificate could be found */ public CreateSignatureBase(KeyStore keystore, char[] pin) throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, IOException, CertificateException { // grabs the first alias from the keystore and get the private key. An // alternative method or constructor could be used for setting a specific // alias that should be used. Enumeration<String> aliases = keystore.aliases(); String alias; Certificate cert = null; while (aliases.hasMoreElements()) { alias = aliases.nextElement(); setPrivateKey((PrivateKey) keystore.getKey(alias, pin)); Certificate[] certChain = keystore.getCertificateChain(alias); if (certChain == null) { continue; } cert = certChain[0]; setCertificate(cert); if (cert instanceof X509Certificate) { // avoid expired certificate ((X509Certificate) cert).checkValidity(); } break; } if (cert == null) { throw new IOException("Could not find certificate"); } } public final void setPrivateKey(PrivateKey privateKey) { this.privateKey = privateKey; } public final void setCertificate(Certificate certificate) { this.certificate = certificate; } public void setTsaClient(TSAClient tsaClient) { this.tsaClient = tsaClient; } public TSAClient getTsaClient() { return tsaClient; } /** * We just extend CMS signed Data * * @param signedData Generated CMS signed data * @return CMSSignedData Extended CMS signed data * @throws IOException * @throws org.bouncycastle.tsp.TSPException */ private CMSSignedData signTimeStamps(CMSSignedData signedData) throws IOException, TSPException { SignerInformationStore signerStore = signedData.getSignerInfos(); List<SignerInformation> newSigners = new ArrayList<SignerInformation>(); for (SignerInformation signer : signerStore.getSigners()) { newSigners.add(signTimeStamp(signer)); } // TODO do we have to return a new store? return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners)); } /** * We are extending CMS Signature * * @param signer information about signer * @return information about SignerInformation */ private SignerInformation signTimeStamp(SignerInformation signer) throws IOException, TSPException { AttributeTable unsignedAttributes = signer.getUnsignedAttributes(); ASN1EncodableVector vector = new ASN1EncodableVector(); if (unsignedAttributes != null) { vector = unsignedAttributes.toASN1EncodableVector(); } byte[] token = getTsaClient().getTimeStampToken(signer.getSignature()); ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken; ASN1Encodable signatureTimeStamp = new Attribute(oid, new DERSet(ASN1Primitive.fromByteArray(token))); vector.add(signatureTimeStamp); Attributes signedAttributes = new Attributes(vector); SignerInformation newSigner = SignerInformation.replaceUnsignedAttributes(signer, new AttributeTable(signedAttributes)); // TODO can this actually happen? if (newSigner == null) { return signer; } return newSigner; } /** * SignatureInterface implementation. * * This method will be called from inside of the pdfbox and create the PKCS #7 signature. * The given InputStream contains the bytes that are given by the byte range. * * This method is for internal use only. * * Use your favorite cryptographic library to implement PKCS #7 signature creation. */ @Override public byte[] sign(InputStream content) throws IOException { //TODO this method should be private try { List<Certificate> certList = new ArrayList<Certificate>(); certList.add(certificate); Store certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate .getInstance(ASN1Primitive.fromByteArray(certificate.getEncoded())); ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey); gen.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()) .build(sha1Signer, new X509CertificateHolder(cert))); gen.addCertificates(certs); CMSProcessableInputStream msg = new CMSProcessableInputStream(content); CMSSignedData signedData = gen.generate(msg, false); if (tsaClient != null) { signedData = signTimeStamps(signedData); } return signedData.getEncoded(); } catch (GeneralSecurityException e) { throw new IOException(e); } catch (CMSException e) { throw new IOException(e); } catch (TSPException e) { throw new IOException(e); } catch (OperatorCreationException e) { throw new IOException(e); } } /** * Set if external signing scenario should be used. * If {@code false}, SignatureInterface would be used for signing. * <p> * Default: {@code false} * </p> * @param externalSigning {@code true} if external signing should be performed */ public void setExternalSigning(boolean externalSigning) { this.externalSigning = externalSigning; } public boolean isExternalSigning() { return externalSigning; } }