1package org.bouncycastle.cms;
2
3import java.io.IOException;
4import java.io.OutputStream;
5import java.util.Collections;
6import java.util.HashMap;
7import java.util.Map;
8
9import org.bouncycastle.asn1.ASN1Encoding;
10import org.bouncycastle.asn1.ASN1ObjectIdentifier;
11import org.bouncycastle.asn1.ASN1Set;
12import org.bouncycastle.asn1.DERObjectIdentifier;
13import org.bouncycastle.asn1.DEROctetString;
14import org.bouncycastle.asn1.DERSet;
15import org.bouncycastle.asn1.cms.AttributeTable;
16import org.bouncycastle.asn1.cms.SignerIdentifier;
17import org.bouncycastle.asn1.cms.SignerInfo;
18import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
19import org.bouncycastle.cert.X509CertificateHolder;
20import org.bouncycastle.operator.ContentSigner;
21import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
22import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
23import org.bouncycastle.operator.DigestCalculator;
24import org.bouncycastle.operator.DigestCalculatorProvider;
25import org.bouncycastle.operator.OperatorCreationException;
26import org.bouncycastle.util.io.TeeOutputStream;
27
28public class SignerInfoGenerator
29{
30    private final SignerIdentifier signerIdentifier;
31    private final CMSAttributeTableGenerator sAttrGen;
32    private final CMSAttributeTableGenerator unsAttrGen;
33    private final ContentSigner signer;
34    private final DigestCalculator digester;
35    private final DigestAlgorithmIdentifierFinder digAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
36    private final CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder;
37
38    private byte[] calculatedDigest = null;
39    private X509CertificateHolder certHolder;
40
41    SignerInfoGenerator(
42        SignerIdentifier signerIdentifier,
43        ContentSigner signer,
44        DigestCalculatorProvider digesterProvider,
45        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder)
46        throws OperatorCreationException
47    {
48        this(signerIdentifier, signer, digesterProvider, sigEncAlgFinder, false);
49    }
50
51    SignerInfoGenerator(
52        SignerIdentifier signerIdentifier,
53        ContentSigner signer,
54        DigestCalculatorProvider digesterProvider,
55        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
56        boolean isDirectSignature)
57        throws OperatorCreationException
58    {
59        this.signerIdentifier = signerIdentifier;
60        this.signer = signer;
61
62        if (digesterProvider != null)
63        {
64            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
65        }
66        else
67        {
68            this.digester = null;
69        }
70
71        if (isDirectSignature)
72        {
73            this.sAttrGen = null;
74            this.unsAttrGen = null;
75        }
76        else
77        {
78            this.sAttrGen = new DefaultSignedAttributeTableGenerator();
79            this.unsAttrGen = null;
80        }
81
82        this.sigEncAlgFinder = sigEncAlgFinder;
83    }
84
85    public SignerInfoGenerator(
86        SignerInfoGenerator original,
87        CMSAttributeTableGenerator sAttrGen,
88        CMSAttributeTableGenerator unsAttrGen)
89    {
90        this.signerIdentifier = original.signerIdentifier;
91        this.signer = original.signer;
92        this.digester = original.digester;
93        this.sigEncAlgFinder = original.sigEncAlgFinder;
94        this.sAttrGen = sAttrGen;
95        this.unsAttrGen = unsAttrGen;
96    }
97
98    SignerInfoGenerator(
99        SignerIdentifier signerIdentifier,
100        ContentSigner signer,
101        DigestCalculatorProvider digesterProvider,
102        CMSSignatureEncryptionAlgorithmFinder sigEncAlgFinder,
103        CMSAttributeTableGenerator sAttrGen,
104        CMSAttributeTableGenerator unsAttrGen)
105        throws OperatorCreationException
106    {
107        this.signerIdentifier = signerIdentifier;
108        this.signer = signer;
109
110        if (digesterProvider != null)
111        {
112            this.digester = digesterProvider.get(digAlgFinder.find(signer.getAlgorithmIdentifier()));
113        }
114        else
115        {
116            this.digester = null;
117        }
118
119        this.sAttrGen = sAttrGen;
120        this.unsAttrGen = unsAttrGen;
121        this.sigEncAlgFinder = sigEncAlgFinder;
122    }
123
124    public boolean hasAssociatedCertificate()
125    {
126        return certHolder != null;
127    }
128
129    public X509CertificateHolder getAssociatedCertificate()
130    {
131        return certHolder;
132    }
133
134    public AlgorithmIdentifier getDigestAlgorithm()
135    {
136        if (digester != null)
137        {
138            return digester.getAlgorithmIdentifier();
139        }
140
141        return digAlgFinder.find(signer.getAlgorithmIdentifier());
142    }
143
144    public OutputStream getCalculatingOutputStream()
145    {
146        if (digester != null)
147        {
148            if (sAttrGen == null)
149            {
150                return new TeeOutputStream(digester.getOutputStream(), signer.getOutputStream());
151            }
152            return digester.getOutputStream();
153        }
154        else
155        {
156            return signer.getOutputStream();
157        }
158    }
159
160    public SignerInfo generate(ASN1ObjectIdentifier contentType)
161        throws CMSException
162    {
163        try
164        {
165            /* RFC 3852 5.4
166             * The result of the message digest calculation process depends on
167             * whether the signedAttrs field is present.  When the field is absent,
168             * the result is just the message digest of the content as described
169             *
170             * above.  When the field is present, however, the result is the message
171             * digest of the complete DER encoding of the SignedAttrs value
172             * contained in the signedAttrs field.
173             */
174            ASN1Set signedAttr = null;
175
176            AlgorithmIdentifier digestAlg = null;
177
178            if (sAttrGen != null)
179            {
180                digestAlg = digester.getAlgorithmIdentifier();
181                calculatedDigest = digester.getDigest();
182                Map parameters = getBaseParameters(contentType, digester.getAlgorithmIdentifier(), calculatedDigest);
183                AttributeTable signed = sAttrGen.getAttributes(Collections.unmodifiableMap(parameters));
184
185                signedAttr = getAttributeSet(signed);
186
187                // sig must be composed from the DER encoding.
188                OutputStream sOut = signer.getOutputStream();
189
190                sOut.write(signedAttr.getEncoded(ASN1Encoding.DER));
191
192                sOut.close();
193            }
194            else
195            {
196                if (digester != null)
197                {
198                    digestAlg = digester.getAlgorithmIdentifier();
199                    calculatedDigest = digester.getDigest();
200                }
201                else
202                {
203                    digestAlg = digAlgFinder.find(signer.getAlgorithmIdentifier());
204                    calculatedDigest = null;
205                }
206            }
207
208            byte[] sigBytes = signer.getSignature();
209
210            ASN1Set unsignedAttr = null;
211            if (unsAttrGen != null)
212            {
213                Map parameters = getBaseParameters(contentType, digestAlg, calculatedDigest);
214                parameters.put(CMSAttributeTableGenerator.SIGNATURE, sigBytes.clone());
215
216                AttributeTable unsigned = unsAttrGen.getAttributes(Collections.unmodifiableMap(parameters));
217
218                unsignedAttr = getAttributeSet(unsigned);
219            }
220
221            AlgorithmIdentifier digestEncryptionAlgorithm = sigEncAlgFinder.findEncryptionAlgorithm(signer.getAlgorithmIdentifier());
222
223            return new SignerInfo(signerIdentifier, digestAlg,
224                signedAttr, digestEncryptionAlgorithm, new DEROctetString(sigBytes), unsignedAttr);
225        }
226        catch (IOException e)
227        {
228            throw new CMSException("encoding error.", e);
229        }
230    }
231
232    void setAssociatedCertificate(X509CertificateHolder certHolder)
233    {
234        this.certHolder = certHolder;
235    }
236
237    private ASN1Set getAttributeSet(
238        AttributeTable attr)
239    {
240        if (attr != null)
241        {
242            return new DERSet(attr.toASN1EncodableVector());
243        }
244
245        return null;
246    }
247
248    private Map getBaseParameters(DERObjectIdentifier contentType, AlgorithmIdentifier digAlgId, byte[] hash)
249    {
250        Map param = new HashMap();
251
252        if (contentType != null)
253        {
254            param.put(CMSAttributeTableGenerator.CONTENT_TYPE, contentType);
255        }
256
257        param.put(CMSAttributeTableGenerator.DIGEST_ALGORITHM_IDENTIFIER, digAlgId);
258        param.put(CMSAttributeTableGenerator.DIGEST,  hash.clone());
259        return param;
260    }
261
262    public byte[] getCalculatedDigest()
263    {
264        if (calculatedDigest != null)
265        {
266            return (byte[])calculatedDigest.clone();
267        }
268
269        return null;
270    }
271
272    public CMSAttributeTableGenerator getSignedAttributeTableGenerator()
273    {
274        return sAttrGen;
275    }
276
277    public CMSAttributeTableGenerator getUnsignedAttributeTableGenerator()
278    {
279        return unsAttrGen;
280    }
281}
282