1package org.bouncycastle.cms;
2
3import java.io.IOException;
4import java.io.OutputStream;
5import java.util.ArrayList;
6import java.util.Enumeration;
7import java.util.Iterator;
8import java.util.List;
9
10import org.bouncycastle.asn1.ASN1Encodable;
11import org.bouncycastle.asn1.ASN1EncodableVector;
12import org.bouncycastle.asn1.ASN1Encoding;
13import org.bouncycastle.asn1.ASN1ObjectIdentifier;
14import org.bouncycastle.asn1.ASN1OctetString;
15import org.bouncycastle.asn1.ASN1Primitive;
16import org.bouncycastle.asn1.ASN1Set;
17import org.bouncycastle.asn1.DERNull;
18import org.bouncycastle.asn1.DERSet;
19import org.bouncycastle.asn1.cms.Attribute;
20import org.bouncycastle.asn1.cms.AttributeTable;
21import org.bouncycastle.asn1.cms.CMSAttributes;
22import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
23import org.bouncycastle.asn1.cms.SignerIdentifier;
24import org.bouncycastle.asn1.cms.SignerInfo;
25import org.bouncycastle.asn1.cms.Time;
26import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
27import org.bouncycastle.asn1.x509.DigestInfo;
28import org.bouncycastle.cert.X509CertificateHolder;
29import org.bouncycastle.operator.ContentVerifier;
30import org.bouncycastle.operator.DigestCalculator;
31import org.bouncycastle.operator.OperatorCreationException;
32import org.bouncycastle.operator.RawContentVerifier;
33import org.bouncycastle.util.Arrays;
34import org.bouncycastle.util.io.TeeOutputStream;
35
36/**
37 * an expanded SignerInfo block from a CMS Signed message
38 */
39public class SignerInformation
40{
41    private SignerId                sid;
42    private SignerInfo              info;
43    private AlgorithmIdentifier     digestAlgorithm;
44    private AlgorithmIdentifier     encryptionAlgorithm;
45    private final ASN1Set           signedAttributeSet;
46    private final ASN1Set           unsignedAttributeSet;
47    private CMSProcessable          content;
48    private byte[]                  signature;
49    private ASN1ObjectIdentifier    contentType;
50    private byte[]                  resultDigest;
51
52    // Derived
53    private AttributeTable          signedAttributeValues;
54    private AttributeTable          unsignedAttributeValues;
55    private boolean                 isCounterSignature;
56
57    SignerInformation(
58        SignerInfo          info,
59        ASN1ObjectIdentifier contentType,
60        CMSProcessable      content,
61        byte[]              resultDigest)
62    {
63        this.info = info;
64        this.contentType = contentType;
65        this.isCounterSignature = contentType == null;
66
67        SignerIdentifier   s = info.getSID();
68
69        if (s.isTagged())
70        {
71            ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());
72
73            sid = new SignerId(octs.getOctets());
74        }
75        else
76        {
77            IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(s.getId());
78
79            sid = new SignerId(iAnds.getName(), iAnds.getSerialNumber().getValue());
80        }
81
82        this.digestAlgorithm = info.getDigestAlgorithm();
83        this.signedAttributeSet = info.getAuthenticatedAttributes();
84        this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
85        this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
86        this.signature = info.getEncryptedDigest().getOctets();
87
88        this.content = content;
89        this.resultDigest = resultDigest;
90    }
91
92    public boolean isCounterSignature()
93    {
94        return isCounterSignature;
95    }
96
97    public ASN1ObjectIdentifier getContentType()
98    {
99        return this.contentType;
100    }
101
102    private byte[] encodeObj(
103        ASN1Encodable    obj)
104        throws IOException
105    {
106        if (obj != null)
107        {
108            return obj.toASN1Primitive().getEncoded();
109        }
110
111        return null;
112    }
113
114    public SignerId getSID()
115    {
116        return sid;
117    }
118
119    /**
120     * return the version number for this objects underlying SignerInfo structure.
121     */
122    public int getVersion()
123    {
124        return info.getVersion().getValue().intValue();
125    }
126
127    public AlgorithmIdentifier getDigestAlgorithmID()
128    {
129        return digestAlgorithm;
130    }
131
132    /**
133     * return the object identifier for the signature.
134     */
135    public String getDigestAlgOID()
136    {
137        return digestAlgorithm.getAlgorithm().getId();
138    }
139
140    /**
141     * return the signature parameters, or null if there aren't any.
142     */
143    public byte[] getDigestAlgParams()
144    {
145        try
146        {
147            return encodeObj(digestAlgorithm.getParameters());
148        }
149        catch (Exception e)
150        {
151            throw new RuntimeException("exception getting digest parameters " + e);
152        }
153    }
154
155    /**
156     * return the content digest that was calculated during verification.
157     */
158    public byte[] getContentDigest()
159    {
160        if (resultDigest == null)
161        {
162            throw new IllegalStateException("method can only be called after verify.");
163        }
164
165        return Arrays.clone(resultDigest);
166    }
167
168    /**
169     * return the object identifier for the signature.
170     */
171    public String getEncryptionAlgOID()
172    {
173        return encryptionAlgorithm.getAlgorithm().getId();
174    }
175
176    /**
177     * return the signature/encryption algorithm parameters, or null if
178     * there aren't any.
179     */
180    public byte[] getEncryptionAlgParams()
181    {
182        try
183        {
184            return encodeObj(encryptionAlgorithm.getParameters());
185        }
186        catch (Exception e)
187        {
188            throw new RuntimeException("exception getting encryption parameters " + e);
189        }
190    }
191
192    /**
193     * return a table of the signed attributes - indexed by
194     * the OID of the attribute.
195     */
196    public AttributeTable getSignedAttributes()
197    {
198        if (signedAttributeSet != null && signedAttributeValues == null)
199        {
200            signedAttributeValues = new AttributeTable(signedAttributeSet);
201        }
202
203        return signedAttributeValues;
204    }
205
206    /**
207     * return a table of the unsigned attributes indexed by
208     * the OID of the attribute.
209     */
210    public AttributeTable getUnsignedAttributes()
211    {
212        if (unsignedAttributeSet != null && unsignedAttributeValues == null)
213        {
214            unsignedAttributeValues = new AttributeTable(unsignedAttributeSet);
215        }
216
217        return unsignedAttributeValues;
218    }
219
220    /**
221     * return the encoded signature
222     */
223    public byte[] getSignature()
224    {
225        return Arrays.clone(signature);
226    }
227
228    /**
229     * Return a SignerInformationStore containing the counter signatures attached to this
230     * signer. If no counter signatures are present an empty store is returned.
231     */
232    public SignerInformationStore getCounterSignatures()
233    {
234        // TODO There are several checks implied by the RFC3852 comments that are missing
235
236        /*
237        The countersignature attribute MUST be an unsigned attribute; it MUST
238        NOT be a signed attribute, an authenticated attribute, an
239        unauthenticated attribute, or an unprotected attribute.
240        */
241        AttributeTable unsignedAttributeTable = getUnsignedAttributes();
242        if (unsignedAttributeTable == null)
243        {
244            return new SignerInformationStore(new ArrayList(0));
245        }
246
247        List counterSignatures = new ArrayList();
248
249        /*
250        The UnsignedAttributes syntax is defined as a SET OF Attributes.  The
251        UnsignedAttributes in a signerInfo may include multiple instances of
252        the countersignature attribute.
253        */
254        ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature);
255
256        for (int i = 0; i < allCSAttrs.size(); ++i)
257        {
258            Attribute counterSignatureAttribute = (Attribute)allCSAttrs.get(i);
259
260            /*
261            A countersignature attribute can have multiple attribute values.  The
262            syntax is defined as a SET OF AttributeValue, and there MUST be one
263            or more instances of AttributeValue present.
264            */
265            ASN1Set values = counterSignatureAttribute.getAttrValues();
266            if (values.size() < 1)
267            {
268                // TODO Throw an appropriate exception?
269            }
270
271            for (Enumeration en = values.getObjects(); en.hasMoreElements();)
272            {
273                /*
274                Countersignature values have the same meaning as SignerInfo values
275                for ordinary signatures, except that:
276
277                   1. The signedAttributes field MUST NOT contain a content-type
278                      attribute; there is no content type for countersignatures.
279
280                   2. The signedAttributes field MUST contain a message-digest
281                      attribute if it contains any other attributes.
282
283                   3. The input to the message-digesting process is the contents
284                      octets of the DER encoding of the signatureValue field of the
285                      SignerInfo value with which the attribute is associated.
286                */
287                SignerInfo si = SignerInfo.getInstance(en.nextElement());
288
289                counterSignatures.add(new SignerInformation(si, null, new CMSProcessableByteArray(getSignature()), null));
290            }
291        }
292
293        return new SignerInformationStore(counterSignatures);
294    }
295
296    /**
297     * return the DER encoding of the signed attributes.
298     * @throws IOException if an encoding error occurs.
299     */
300    public byte[] getEncodedSignedAttributes()
301        throws IOException
302    {
303        if (signedAttributeSet != null)
304        {
305            return signedAttributeSet.getEncoded(ASN1Encoding.DER);
306        }
307
308        return null;
309    }
310
311    private boolean doVerify(
312        SignerInformationVerifier verifier)
313        throws CMSException
314    {
315        String          encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
316        ContentVerifier contentVerifier;
317
318        try
319        {
320            contentVerifier = verifier.getContentVerifier(encryptionAlgorithm, info.getDigestAlgorithm());
321        }
322        catch (OperatorCreationException e)
323        {
324            throw new CMSException("can't create content verifier: " + e.getMessage(), e);
325        }
326
327        try
328        {
329            OutputStream sigOut = contentVerifier.getOutputStream();
330
331            if (resultDigest == null)
332            {
333                DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID());
334                if (content != null)
335                {
336                    OutputStream      digOut = calc.getOutputStream();
337
338                    if (signedAttributeSet == null)
339                    {
340                        if (contentVerifier instanceof RawContentVerifier)
341                        {
342                            content.write(digOut);
343                        }
344                        else
345                        {
346                            OutputStream cOut = new TeeOutputStream(digOut, sigOut);
347
348                            content.write(cOut);
349
350                            cOut.close();
351                        }
352                    }
353                    else
354                    {
355                        content.write(digOut);
356                        sigOut.write(this.getEncodedSignedAttributes());
357                    }
358
359                    digOut.close();
360                }
361                else if (signedAttributeSet != null)
362                {
363                    sigOut.write(this.getEncodedSignedAttributes());
364                }
365                else
366                {
367                    // TODO Get rid of this exception and just treat content==null as empty not missing?
368                    throw new CMSException("data not encapsulated in signature - use detached constructor.");
369                }
370
371                resultDigest = calc.getDigest();
372            }
373            else
374            {
375                if (signedAttributeSet == null)
376                {
377                    if (content != null)
378                    {
379                        content.write(sigOut);
380                    }
381                }
382                else
383                {
384                    sigOut.write(this.getEncodedSignedAttributes());
385                }
386            }
387
388            sigOut.close();
389        }
390        catch (IOException e)
391        {
392            throw new CMSException("can't process mime object to create signature.", e);
393        }
394        catch (OperatorCreationException e)
395        {
396            throw new CMSException("can't create digest calculator: " + e.getMessage(), e);
397        }
398
399        // RFC 3852 11.1 Check the content-type attribute is correct
400        {
401            ASN1Primitive validContentType = getSingleValuedSignedAttribute(
402                CMSAttributes.contentType, "content-type");
403            if (validContentType == null)
404            {
405                if (!isCounterSignature && signedAttributeSet != null)
406                {
407                    throw new CMSException("The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
408                }
409            }
410            else
411            {
412                if (isCounterSignature)
413                {
414                    throw new CMSException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
415                }
416
417                if (!(validContentType instanceof ASN1ObjectIdentifier))
418                {
419                    throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
420                }
421
422                ASN1ObjectIdentifier signedContentType = (ASN1ObjectIdentifier)validContentType;
423
424                if (!signedContentType.equals(contentType))
425                {
426                    throw new CMSException("content-type attribute value does not match eContentType");
427                }
428            }
429        }
430
431        // RFC 3852 11.2 Check the message-digest attribute is correct
432        {
433            ASN1Primitive validMessageDigest = getSingleValuedSignedAttribute(
434                CMSAttributes.messageDigest, "message-digest");
435            if (validMessageDigest == null)
436            {
437                if (signedAttributeSet != null)
438                {
439                    throw new CMSException("the message-digest signed attribute type MUST be present when there are any signed attributes present");
440                }
441            }
442            else
443            {
444                if (!(validMessageDigest instanceof ASN1OctetString))
445                {
446                    throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
447                }
448
449                ASN1OctetString signedMessageDigest = (ASN1OctetString)validMessageDigest;
450
451                if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets()))
452                {
453                    throw new CMSSignerDigestMismatchException("message-digest attribute value does not match calculated value");
454                }
455            }
456        }
457
458        // RFC 3852 11.4 Validate countersignature attribute(s)
459        {
460            AttributeTable signedAttrTable = this.getSignedAttributes();
461            if (signedAttrTable != null
462                && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0)
463            {
464                throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
465            }
466
467            AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
468            if (unsignedAttrTable != null)
469            {
470                ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
471                for (int i = 0; i < csAttrs.size(); ++i)
472                {
473                    Attribute csAttr = (Attribute)csAttrs.get(i);
474                    if (csAttr.getAttrValues().size() < 1)
475                    {
476                        throw new CMSException("A countersignature attribute MUST contain at least one AttributeValue");
477                    }
478
479                    // Note: We don't recursively validate the countersignature value
480                }
481            }
482        }
483
484        try
485        {
486            if (signedAttributeSet == null && resultDigest != null)
487            {
488                if (contentVerifier instanceof RawContentVerifier)
489                {
490                    RawContentVerifier rawVerifier = (RawContentVerifier)contentVerifier;
491
492                    if (encName.equals("RSA"))
493                    {
494                        DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(digestAlgorithm.getAlgorithm(), DERNull.INSTANCE), resultDigest);
495
496                        return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature());
497                    }
498
499                    return rawVerifier.verify(resultDigest, this.getSignature());
500                }
501            }
502
503            return contentVerifier.verify(this.getSignature());
504        }
505        catch (IOException e)
506        {
507            throw new CMSException("can't process mime object to create signature.", e);
508        }
509    }
510
511    /**
512     * Verify that the given verifier can successfully verify the signature on
513     * this SignerInformation object.
514     *
515     * @param verifier a suitably configured SignerInformationVerifier.
516     * @return true if the signer information is verified, false otherwise.
517     * @throws org.bouncycastle.cms.CMSVerifierCertificateNotValidException if the provider has an associated certificate and the certificate is not valid at the time given as the SignerInfo's signing time.
518     * @throws org.bouncycastle.cms.CMSException if the verifier is unable to create a ContentVerifiers or DigestCalculators.
519     */
520    public boolean verify(SignerInformationVerifier verifier)
521        throws CMSException
522    {
523        Time signingTime = getSigningTime();   // has to be validated if present.
524
525        if (verifier.hasAssociatedCertificate())
526        {
527            if (signingTime != null)
528            {
529                X509CertificateHolder dcv = verifier.getAssociatedCertificate();
530
531                if (!dcv.isValidOn(signingTime.getDate()))
532                {
533                    throw new CMSVerifierCertificateNotValidException("verifier not valid at signingTime");
534                }
535            }
536        }
537
538        return doVerify(verifier);
539    }
540
541    /**
542     * Return the underlying ASN.1 object defining this SignerInformation object.
543     *
544     * @return a SignerInfo.
545     */
546    public SignerInfo toASN1Structure()
547    {
548        return info;
549    }
550
551    private ASN1Primitive getSingleValuedSignedAttribute(
552        ASN1ObjectIdentifier attrOID, String printableName)
553        throws CMSException
554    {
555        AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
556        if (unsignedAttrTable != null
557            && unsignedAttrTable.getAll(attrOID).size() > 0)
558        {
559            throw new CMSException("The " + printableName
560                + " attribute MUST NOT be an unsigned attribute");
561        }
562
563        AttributeTable signedAttrTable = this.getSignedAttributes();
564        if (signedAttrTable == null)
565        {
566            return null;
567        }
568
569        ASN1EncodableVector v = signedAttrTable.getAll(attrOID);
570        switch (v.size())
571        {
572            case 0:
573                return null;
574            case 1:
575            {
576                Attribute t = (Attribute)v.get(0);
577                ASN1Set attrValues = t.getAttrValues();
578                if (attrValues.size() != 1)
579                {
580                    throw new CMSException("A " + printableName
581                        + " attribute MUST have a single attribute value");
582                }
583
584                return attrValues.getObjectAt(0).toASN1Primitive();
585            }
586            default:
587                throw new CMSException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
588                    + printableName + " attribute");
589        }
590    }
591
592    private Time getSigningTime() throws CMSException
593    {
594        ASN1Primitive validSigningTime = getSingleValuedSignedAttribute(
595            CMSAttributes.signingTime, "signing-time");
596
597        if (validSigningTime == null)
598        {
599            return null;
600        }
601
602        try
603        {
604            return Time.getInstance(validSigningTime);
605        }
606        catch (IllegalArgumentException e)
607        {
608            throw new CMSException("signing-time attribute value not a valid 'Time' structure");
609        }
610    }
611
612    /**
613     * Return a signer information object with the passed in unsigned
614     * attributes replacing the ones that are current associated with
615     * the object passed in.
616     *
617     * @param signerInformation the signerInfo to be used as the basis.
618     * @param unsignedAttributes the unsigned attributes to add.
619     * @return a copy of the original SignerInformationObject with the changed attributes.
620     */
621    public static SignerInformation replaceUnsignedAttributes(
622        SignerInformation   signerInformation,
623        AttributeTable      unsignedAttributes)
624    {
625        SignerInfo  sInfo = signerInformation.info;
626        ASN1Set     unsignedAttr = null;
627
628        if (unsignedAttributes != null)
629        {
630            unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector());
631        }
632
633        return new SignerInformation(
634                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
635                    sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), unsignedAttr),
636                    signerInformation.contentType, signerInformation.content, null);
637    }
638
639    /**
640     * Return a signer information object with passed in SignerInformationStore representing counter
641     * signatures attached as an unsigned attribute.
642     *
643     * @param signerInformation the signerInfo to be used as the basis.
644     * @param counterSigners signer info objects carrying counter signature.
645     * @return a copy of the original SignerInformationObject with the changed attributes.
646     */
647    public static SignerInformation addCounterSigners(
648        SignerInformation        signerInformation,
649        SignerInformationStore   counterSigners)
650    {
651        // TODO Perform checks from RFC 3852 11.4
652
653        SignerInfo          sInfo = signerInformation.info;
654        AttributeTable      unsignedAttr = signerInformation.getUnsignedAttributes();
655        ASN1EncodableVector v;
656
657        if (unsignedAttr != null)
658        {
659            v = unsignedAttr.toASN1EncodableVector();
660        }
661        else
662        {
663            v = new ASN1EncodableVector();
664        }
665
666        ASN1EncodableVector sigs = new ASN1EncodableVector();
667
668        for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();)
669        {
670            sigs.add(((SignerInformation)it.next()).toASN1Structure());
671        }
672
673        v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));
674
675        return new SignerInformation(
676                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
677                    sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)),
678                    signerInformation.contentType, signerInformation.content, null);
679    }
680}
681