package org.spongycastle.cms; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.Provider; import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.spongycastle.asn1.ASN1EncodableVector; import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.ASN1OctetString; import org.spongycastle.asn1.ASN1Set; import org.spongycastle.asn1.BEROctetString; import org.spongycastle.asn1.DERSet; import org.spongycastle.asn1.cms.AttributeTable; import org.spongycastle.asn1.cms.CMSObjectIdentifiers; import org.spongycastle.asn1.cms.ContentInfo; import org.spongycastle.asn1.cms.SignedData; import org.spongycastle.asn1.cms.SignerInfo; import org.spongycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.spongycastle.operator.ContentSigner; import org.spongycastle.operator.OperatorCreationException; import org.spongycastle.operator.bc.BcDigestCalculatorProvider; import org.spongycastle.operator.jcajce.JcaContentSignerBuilder; /** * general class for generating a pkcs7-signature message. * <p> * A simple example of usage, generating a detached signature. * * <pre> * List certList = new ArrayList(); * CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes()); * * certList.add(signCert); * * Store certs = new JcaCertStore(certList); * * CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); * ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("SC").build(signKP.getPrivate()); * * gen.addSignerInfoGenerator( * new JcaSignerInfoGeneratorBuilder( * new JcaDigestCalculatorProviderBuilder().setProvider("SC").build()) * .build(sha1Signer, signCert)); * * gen.addCertificates(certs); * * CMSSignedData sigData = gen.generate(msg, false); * </pre> */ public class CMSSignedDataGenerator extends CMSSignedGenerator { private List signerInfs = new ArrayList(); private class SignerInf { final PrivateKey key; final Object signerIdentifier; final String digestOID; final String encOID; final CMSAttributeTableGenerator sAttr; final CMSAttributeTableGenerator unsAttr; final AttributeTable baseSignedTable; SignerInf( PrivateKey key, Object signerIdentifier, String digestOID, String encOID, CMSAttributeTableGenerator sAttr, CMSAttributeTableGenerator unsAttr, AttributeTable baseSignedTable) { this.key = key; this.signerIdentifier = signerIdentifier; this.digestOID = digestOID; this.encOID = encOID; this.sAttr = sAttr; this.unsAttr = unsAttr; this.baseSignedTable = baseSignedTable; } SignerInfoGenerator toSignerInfoGenerator( SecureRandom random, Provider sigProvider, boolean addDefaultAttributes) throws IOException, CertificateEncodingException, CMSException, OperatorCreationException, NoSuchAlgorithmException { String digestName = CMSSignedHelper.INSTANCE.getDigestAlgName(digestOID); String signatureName = digestName + "with" + CMSSignedHelper.INSTANCE.getEncryptionAlgName(encOID); JcaSignerInfoGeneratorBuilder builder = new JcaSignerInfoGeneratorBuilder(new BcDigestCalculatorProvider()); if (addDefaultAttributes) { builder.setSignedAttributeGenerator(sAttr); } builder.setDirectSignature(!addDefaultAttributes); builder.setUnsignedAttributeGenerator(unsAttr); JcaContentSignerBuilder signerBuilder; try { signerBuilder = new JcaContentSignerBuilder(signatureName).setSecureRandom(random); } catch (IllegalArgumentException e) { throw new NoSuchAlgorithmException(e.getMessage()); } if (sigProvider != null) { signerBuilder.setProvider(sigProvider); } ContentSigner contentSigner = signerBuilder.build(key); if (signerIdentifier instanceof X509Certificate) { return builder.build(contentSigner, (X509Certificate)signerIdentifier); } else { return builder.build(contentSigner, (byte[])signerIdentifier); } } } /** * base constructor */ public CMSSignedDataGenerator() { } /** * constructor allowing specific source of randomness * @param rand instance of SecureRandom to use */ public CMSSignedDataGenerator( SecureRandom rand) { super(rand); } /** * add a signer - no attributes other than the default ones will be * provided here. * * @param key signing key to use * @param cert certificate containing corresponding public key * @param digestOID digest algorithm OID * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, X509Certificate cert, String digestOID) throws IllegalArgumentException { addSigner(key, cert, getEncOID(key, digestOID), digestOID); } /** * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be * provided here. * * @param key signing key to use * @param cert certificate containing corresponding public key * @param encryptionOID digest encryption algorithm OID * @param digestOID digest algorithm OID * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, X509Certificate cert, String encryptionOID, String digestOID) throws IllegalArgumentException { doAddSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), null, null); } /** * add a signer - no attributes other than the default ones will be * provided here. * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, byte[] subjectKeyID, String digestOID) throws IllegalArgumentException { addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID); } /** * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be * provided here. * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, byte[] subjectKeyID, String encryptionOID, String digestOID) throws IllegalArgumentException { doAddSigner(key, subjectKeyID, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(), null, null); } /** * add a signer with extra signed/unsigned attributes. * * @param key signing key to use * @param cert certificate containing corresponding public key * @param digestOID digest algorithm OID * @param signedAttr table of attributes to be included in signature * @param unsignedAttr table of attributes to be included as unsigned * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, X509Certificate cert, String digestOID, AttributeTable signedAttr, AttributeTable unsignedAttr) throws IllegalArgumentException { addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttr, unsignedAttr); } /** * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes. * * @param key signing key to use * @param cert certificate containing corresponding public key * @param encryptionOID digest encryption algorithm OID * @param digestOID digest algorithm OID * @param signedAttr table of attributes to be included in signature * @param unsignedAttr table of attributes to be included as unsigned * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, X509Certificate cert, String encryptionOID, String digestOID, AttributeTable signedAttr, AttributeTable unsignedAttr) throws IllegalArgumentException { doAddSigner(key, cert, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), signedAttr); } /** * add a signer with extra signed/unsigned attributes. * * @param key signing key to use * @param subjectKeyID subjectKeyID of corresponding public key * @param digestOID digest algorithm OID * @param signedAttr table of attributes to be included in signature * @param unsignedAttr table of attributes to be included as unsigned * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, byte[] subjectKeyID, String digestOID, AttributeTable signedAttr, AttributeTable unsignedAttr) throws IllegalArgumentException { addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttr, unsignedAttr); } /** * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes. * * @param key signing key to use * @param subjectKeyID subjectKeyID of corresponding public key * @param encryptionOID digest encryption algorithm OID * @param digestOID digest algorithm OID * @param signedAttr table of attributes to be included in signature * @param unsignedAttr table of attributes to be included as unsigned * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, byte[] subjectKeyID, String encryptionOID, String digestOID, AttributeTable signedAttr, AttributeTable unsignedAttr) throws IllegalArgumentException { doAddSigner(key, subjectKeyID, encryptionOID, digestOID, new DefaultSignedAttributeTableGenerator(signedAttr), new SimpleAttributeTableGenerator(unsignedAttr), signedAttr); } /** * add a signer with extra signed/unsigned attributes based on generators. * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, X509Certificate cert, String digestOID, CMSAttributeTableGenerator signedAttrGen, CMSAttributeTableGenerator unsignedAttrGen) throws IllegalArgumentException { addSigner(key, cert, getEncOID(key, digestOID), digestOID, signedAttrGen, unsignedAttrGen); } /** * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators. * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, X509Certificate cert, String encryptionOID, String digestOID, CMSAttributeTableGenerator signedAttrGen, CMSAttributeTableGenerator unsignedAttrGen) throws IllegalArgumentException { doAddSigner(key, cert, encryptionOID, digestOID, signedAttrGen, unsignedAttrGen, null); } /** * add a signer with extra signed/unsigned attributes based on generators. * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, byte[] subjectKeyID, String digestOID, CMSAttributeTableGenerator signedAttrGen, CMSAttributeTableGenerator unsignedAttrGen) throws IllegalArgumentException { addSigner(key, subjectKeyID, getEncOID(key, digestOID), digestOID, signedAttrGen, unsignedAttrGen); } /** * add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators. * @deprecated use addSignerInfoGenerator */ public void addSigner( PrivateKey key, byte[] subjectKeyID, String encryptionOID, String digestOID, CMSAttributeTableGenerator signedAttrGen, CMSAttributeTableGenerator unsignedAttrGen) throws IllegalArgumentException { doAddSigner(key, subjectKeyID, encryptionOID, digestOID, signedAttrGen, unsignedAttrGen, null); } private void doAddSigner( PrivateKey key, Object signerIdentifier, String encryptionOID, String digestOID, CMSAttributeTableGenerator signedAttrGen, CMSAttributeTableGenerator unsignedAttrGen, AttributeTable baseSignedTable) throws IllegalArgumentException { signerInfs.add(new SignerInf(key, signerIdentifier, digestOID, encryptionOID, signedAttrGen, unsignedAttrGen, baseSignedTable)); } /** * generate a signed object that for a CMS Signed Data * object using the given provider. * @deprecated use generate() method not taking provider. */ public CMSSignedData generate( CMSProcessable content, String sigProvider) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException { return generate(content, CMSUtils.getProvider(sigProvider)); } /** * generate a signed object that for a CMS Signed Data * object using the given provider. * @deprecated use generate() method not taking provider. */ public CMSSignedData generate( CMSProcessable content, Provider sigProvider) throws NoSuchAlgorithmException, CMSException { return generate(content, false, sigProvider); } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature. The content type * is set according to the OID represented by the string signedContentType. * @deprecated use generate(CMSTypedData, boolean) */ public CMSSignedData generate( String eContentType, CMSProcessable content, boolean encapsulate, String sigProvider) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException { return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider), true); } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature. The content type * is set according to the OID represented by the string signedContentType. * @deprecated use generate(CMSTypedData, boolean) */ public CMSSignedData generate( String eContentType, CMSProcessable content, boolean encapsulate, Provider sigProvider) throws NoSuchAlgorithmException, CMSException { return generate(eContentType, content, encapsulate, sigProvider, true); } /** * Similar method to the other generate methods. The additional argument * addDefaultAttributes indicates whether or not a default set of signed attributes * need to be added automatically. If the argument is set to false, no * attributes will get added at all. * @deprecated use generate(CMSTypedData, boolean) */ public CMSSignedData generate( String eContentType, CMSProcessable content, boolean encapsulate, String sigProvider, boolean addDefaultAttributes) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException { return generate(eContentType, content, encapsulate, CMSUtils.getProvider(sigProvider), addDefaultAttributes); } /** * Similar method to the other generate methods. The additional argument * addDefaultAttributes indicates whether or not a default set of signed attributes * need to be added automatically. If the argument is set to false, no * attributes will get added at all. */ public CMSSignedData generate( String eContentType, final CMSProcessable content, boolean encapsulate, Provider sigProvider, boolean addDefaultAttributes) throws NoSuchAlgorithmException, CMSException { boolean isCounterSignature = (eContentType == null); final ASN1ObjectIdentifier contentTypeOID = isCounterSignature ? null : new ASN1ObjectIdentifier(eContentType); for (Iterator it = signerInfs.iterator(); it.hasNext();) { SignerInf signer = (SignerInf)it.next(); try { signerGens.add(signer.toSignerInfoGenerator(rand, sigProvider, addDefaultAttributes)); } catch (OperatorCreationException e) { throw new CMSException("exception creating signerInf", e); } catch (IOException e) { throw new CMSException("exception encoding attributes", e); } catch (CertificateEncodingException e) { throw new CMSException("error creating sid.", e); } } signerInfs.clear(); if (content != null) { return generate(new CMSTypedData() { public ASN1ObjectIdentifier getContentType() { return contentTypeOID; } public void write(OutputStream out) throws IOException, CMSException { content.write(out); } public Object getContent() { return content.getContent(); } }, encapsulate); } else { return generate(new CMSAbsentContent(contentTypeOID), encapsulate); } } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature with the * default content type "data". * @deprecated use generate(CMSTypedData, boolean) */ public CMSSignedData generate( CMSProcessable content, boolean encapsulate, String sigProvider) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException { if (content instanceof CMSTypedData) { return this.generate(((CMSTypedData)content).getContentType().getId(), content, encapsulate, sigProvider); } else { return this.generate(DATA, content, encapsulate, sigProvider); } } /** * generate a signed object that for a CMS Signed Data * object using the given provider - if encapsulate is true a copy * of the message will be included in the signature with the * default content type "data". * @deprecated use generate(CMSTypedData, boolean) */ public CMSSignedData generate( CMSProcessable content, boolean encapsulate, Provider sigProvider) throws NoSuchAlgorithmException, CMSException { if (content instanceof CMSTypedData) { return this.generate(((CMSTypedData)content).getContentType().getId(), content, encapsulate, sigProvider); } else { return this.generate(DATA, content, encapsulate, sigProvider); } } public CMSSignedData generate( CMSTypedData content) throws CMSException { return generate(content, false); } public CMSSignedData generate( // FIXME Avoid accessing more than once to support CMSProcessableInputStream CMSTypedData content, boolean encapsulate) throws CMSException { if (!signerInfs.isEmpty()) { throw new IllegalStateException("this method can only be used with SignerInfoGenerator"); } // TODO // if (signerInfs.isEmpty()) // { // /* RFC 3852 5.2 // * "In the degenerate case where there are no signers, the // * EncapsulatedContentInfo value being "signed" is irrelevant. In this // * case, the content type within the EncapsulatedContentInfo value being // * "signed" MUST be id-data (as defined in section 4), and the content // * field of the EncapsulatedContentInfo value MUST be omitted." // */ // if (encapsulate) // { // throw new IllegalArgumentException("no signers, encapsulate must be false"); // } // if (!DATA.equals(eContentType)) // { // throw new IllegalArgumentException("no signers, eContentType must be id-data"); // } // } // // if (!DATA.equals(eContentType)) // { // /* RFC 3852 5.3 // * [The 'signedAttrs']... // * field is optional, but it MUST be present if the content type of // * the EncapsulatedContentInfo value being signed is not id-data. // */ // // TODO signedAttrs must be present for all signers // } ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); ASN1EncodableVector signerInfos = new ASN1EncodableVector(); digests.clear(); // clear the current preserved digest state // // add the precalculated SignerInfo objects. // for (Iterator it = _signers.iterator(); it.hasNext();) { SignerInformation signer = (SignerInformation)it.next(); digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); // TODO Verify the content type and calculated digest match the precalculated SignerInfo signerInfos.add(signer.toASN1Structure()); } // // add the SignerInfo objects // ASN1ObjectIdentifier contentTypeOID = content.getContentType(); ASN1OctetString octs = null; if (content != null) { ByteArrayOutputStream bOut = null; if (encapsulate) { bOut = new ByteArrayOutputStream(); } OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut); // Just in case it's unencapsulated and there are no signers! cOut = CMSUtils.getSafeOutputStream(cOut); try { content.write(cOut); cOut.close(); } catch (IOException e) { throw new CMSException("data processing exception: " + e.getMessage(), e); } if (encapsulate) { octs = new BEROctetString(bOut.toByteArray()); } } for (Iterator it = signerGens.iterator(); it.hasNext();) { SignerInfoGenerator sGen = (SignerInfoGenerator)it.next(); SignerInfo inf = sGen.generate(contentTypeOID); digestAlgs.add(inf.getDigestAlgorithm()); signerInfos.add(inf); byte[] calcDigest = sGen.getCalculatedDigest(); if (calcDigest != null) { digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest); } } ASN1Set certificates = null; if (certs.size() != 0) { certificates = CMSUtils.createBerSetFromList(certs); } ASN1Set certrevlist = null; if (crls.size() != 0) { certrevlist = CMSUtils.createBerSetFromList(crls); } ContentInfo encInfo = new ContentInfo(contentTypeOID, octs); SignedData sd = new SignedData( new DERSet(digestAlgs), encInfo, certificates, certrevlist, new DERSet(signerInfos)); ContentInfo contentInfo = new ContentInfo( CMSObjectIdentifiers.signedData, sd); return new CMSSignedData(content, contentInfo); } /** * generate a set of one or more SignerInformation objects representing counter signatures on * the passed in SignerInformation object. * * @param signer the signer to be countersigned * @param sigProvider the provider to be used for counter signing. * @return a store containing the signers. * @deprecated use generateCounterSigners(SignerInformation) */ public SignerInformationStore generateCounterSigners(SignerInformation signer, Provider sigProvider) throws NoSuchAlgorithmException, CMSException { return this.generate(null, new CMSProcessableByteArray(signer.getSignature()), false, sigProvider).getSignerInfos(); } /** * generate a set of one or more SignerInformation objects representing counter signatures on * the passed in SignerInformation object. * * @param signer the signer to be countersigned * @param sigProvider the provider to be used for counter signing. * @return a store containing the signers. * @deprecated use generateCounterSigners(SignerInformation) */ public SignerInformationStore generateCounterSigners(SignerInformation signer, String sigProvider) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException { return this.generate(null, new CMSProcessableByteArray(signer.getSignature()), false, CMSUtils.getProvider(sigProvider)).getSignerInfos(); } /** * generate a set of one or more SignerInformation objects representing counter signatures on * the passed in SignerInformation object. * * @param signer the signer to be countersigned * @return a store containing the signers. */ public SignerInformationStore generateCounterSigners(SignerInformation signer) throws CMSException { return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos(); } }