Home | History | Annotate | Download | only in cms
      1 package org.bouncycastle.cms;
      2 
      3 import java.io.IOException;
      4 import java.io.OutputStream;
      5 import java.util.ArrayList;
      6 import java.util.Enumeration;
      7 import java.util.Iterator;
      8 import java.util.List;
      9 
     10 import org.bouncycastle.asn1.ASN1Encodable;
     11 import org.bouncycastle.asn1.ASN1EncodableVector;
     12 import org.bouncycastle.asn1.ASN1Encoding;
     13 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
     14 import org.bouncycastle.asn1.ASN1OctetString;
     15 import org.bouncycastle.asn1.ASN1Primitive;
     16 import org.bouncycastle.asn1.ASN1Set;
     17 import org.bouncycastle.asn1.DERNull;
     18 import org.bouncycastle.asn1.DERSet;
     19 import org.bouncycastle.asn1.cms.Attribute;
     20 import org.bouncycastle.asn1.cms.AttributeTable;
     21 import org.bouncycastle.asn1.cms.CMSAttributes;
     22 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
     23 import org.bouncycastle.asn1.cms.SignerIdentifier;
     24 import org.bouncycastle.asn1.cms.SignerInfo;
     25 import org.bouncycastle.asn1.cms.Time;
     26 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
     27 import org.bouncycastle.asn1.x509.DigestInfo;
     28 import org.bouncycastle.cert.X509CertificateHolder;
     29 import org.bouncycastle.operator.ContentVerifier;
     30 import org.bouncycastle.operator.DigestCalculator;
     31 import org.bouncycastle.operator.OperatorCreationException;
     32 import org.bouncycastle.operator.RawContentVerifier;
     33 import org.bouncycastle.util.Arrays;
     34 import org.bouncycastle.util.io.TeeOutputStream;
     35 
     36 /**
     37  * an expanded SignerInfo block from a CMS Signed message
     38  */
     39 public 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