org.bouncycastle.cms.SignerInformation.java Source code

Java tutorial

Introduction

Here is the source code for org.bouncycastle.cms.SignerInformation.java

Source

package org.bouncycastle.cms;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAlgorithmProtection;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.asn1.cms.SignerIdentifier;
import org.bouncycastle.asn1.cms.SignerInfo;
import org.bouncycastle.asn1.cms.Time;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.operator.ContentVerifier;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.RawContentVerifier;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.io.TeeOutputStream;

/**
 * an expanded SignerInfo block from a CMS Signed message
 */
public class SignerInformation {
    private final SignerId sid;
    private final CMSProcessable content;
    private final byte[] signature;
    private final ASN1ObjectIdentifier contentType;
    private final boolean isCounterSignature;

    // Derived
    private AttributeTable signedAttributeValues;
    private AttributeTable unsignedAttributeValues;
    private byte[] resultDigest;

    protected final SignerInfo info;
    protected final AlgorithmIdentifier digestAlgorithm;
    protected final AlgorithmIdentifier encryptionAlgorithm;
    protected final ASN1Set signedAttributeSet;
    protected final ASN1Set unsignedAttributeSet;

    SignerInformation(SignerInfo info, ASN1ObjectIdentifier contentType, CMSProcessable content,
            byte[] resultDigest) {
        this.info = info;
        this.contentType = contentType;
        this.isCounterSignature = contentType == null;

        SignerIdentifier s = info.getSID();

        if (s.isTagged()) {
            ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());

            sid = new SignerId(octs.getOctets());
        } else {
            IssuerAndSerialNumber iAnds = IssuerAndSerialNumber.getInstance(s.getId());

            sid = new SignerId(iAnds.getName(), iAnds.getSerialNumber().getValue());
        }

        this.digestAlgorithm = info.getDigestAlgorithm();
        this.signedAttributeSet = info.getAuthenticatedAttributes();
        this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
        this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
        this.signature = info.getEncryptedDigest().getOctets();

        this.content = content;
        this.resultDigest = resultDigest;
    }

    /**
     * Protected constructor. In some cases clients have their own idea about how to encode
     * the signed attributes and calculate the signature. This constructor is to allow developers
     * to deal with that by extending off the class and overridng methods like getSignedAttributes().
     *
     * @param baseInfo the SignerInformation to base this one on.
     */
    protected SignerInformation(SignerInformation baseInfo) {
        this.info = baseInfo.info;
        this.contentType = baseInfo.contentType;
        this.isCounterSignature = baseInfo.isCounterSignature();
        this.sid = baseInfo.getSID();
        this.digestAlgorithm = info.getDigestAlgorithm();
        this.signedAttributeSet = info.getAuthenticatedAttributes();
        this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
        this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
        this.signature = info.getEncryptedDigest().getOctets();
        this.content = baseInfo.content;
        this.resultDigest = baseInfo.resultDigest;
        this.signedAttributeValues = baseInfo.signedAttributeValues;
        this.unsignedAttributeValues = baseInfo.unsignedAttributeValues;
    }

    public boolean isCounterSignature() {
        return isCounterSignature;
    }

    public ASN1ObjectIdentifier getContentType() {
        return this.contentType;
    }

    private byte[] encodeObj(ASN1Encodable obj) throws IOException {
        if (obj != null) {
            return obj.toASN1Primitive().getEncoded();
        }

        return null;
    }

    public SignerId getSID() {
        return sid;
    }

    /**
     * return the version number for this objects underlying SignerInfo structure.
     */
    public int getVersion() {
        return info.getVersion().intValueExact();
    }

    public AlgorithmIdentifier getDigestAlgorithmID() {
        return digestAlgorithm;
    }

    /**
     * return the object identifier for the signature.
     */
    public String getDigestAlgOID() {
        return digestAlgorithm.getAlgorithm().getId();
    }

    /**
     * return the signature parameters, or null if there aren't any.
     */
    public byte[] getDigestAlgParams() {
        try {
            return encodeObj(digestAlgorithm.getParameters());
        } catch (Exception e) {
            throw new RuntimeException("exception getting digest parameters " + e);
        }
    }

    /**
     * return the content digest that was calculated during verification.
     */
    public byte[] getContentDigest() {
        if (resultDigest == null) {
            throw new IllegalStateException("method can only be called after verify.");
        }

        return Arrays.clone(resultDigest);
    }

    /**
     * return the object identifier for the signature.
     */
    public String getEncryptionAlgOID() {
        return encryptionAlgorithm.getAlgorithm().getId();
    }

    /**
     * return the signature/encryption algorithm parameters, or null if
     * there aren't any.
     */
    public byte[] getEncryptionAlgParams() {
        try {
            return encodeObj(encryptionAlgorithm.getParameters());
        } catch (Exception e) {
            throw new RuntimeException("exception getting encryption parameters " + e);
        }
    }

    /**
     * return a table of the signed attributes - indexed by
     * the OID of the attribute.
     */
    public AttributeTable getSignedAttributes() {
        if (signedAttributeSet != null && signedAttributeValues == null) {
            signedAttributeValues = new AttributeTable(signedAttributeSet);
        }

        return signedAttributeValues;
    }

    /**
     * return a table of the unsigned attributes indexed by
     * the OID of the attribute.
     */
    public AttributeTable getUnsignedAttributes() {
        if (unsignedAttributeSet != null && unsignedAttributeValues == null) {
            unsignedAttributeValues = new AttributeTable(unsignedAttributeSet);
        }

        return unsignedAttributeValues;
    }

    /**
     * return the encoded signature
     */
    public byte[] getSignature() {
        return Arrays.clone(signature);
    }

    /**
     * Return a SignerInformationStore containing the counter signatures attached to this
     * signer. If no counter signatures are present an empty store is returned.
     */
    public SignerInformationStore getCounterSignatures() {
        // TODO There are several checks implied by the RFC3852 comments that are missing

        /*
        The countersignature attribute MUST be an unsigned attribute; it MUST
        NOT be a signed attribute, an authenticated attribute, an
        unauthenticated attribute, or an unprotected attribute.
        */
        AttributeTable unsignedAttributeTable = getUnsignedAttributes();
        if (unsignedAttributeTable == null) {
            return new SignerInformationStore(new ArrayList(0));
        }

        List counterSignatures = new ArrayList();

        /*
        The UnsignedAttributes syntax is defined as a SET OF Attributes.  The
        UnsignedAttributes in a signerInfo may include multiple instances of
        the countersignature attribute.
        */
        ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature);

        for (int i = 0; i < allCSAttrs.size(); ++i) {
            Attribute counterSignatureAttribute = (Attribute) allCSAttrs.get(i);

            /*
            A countersignature attribute can have multiple attribute values.  The
            syntax is defined as a SET OF AttributeValue, and there MUST be one
            or more instances of AttributeValue present.
            */
            ASN1Set values = counterSignatureAttribute.getAttrValues();
            if (values.size() < 1) {
                // TODO Throw an appropriate exception?
            }

            for (Enumeration en = values.getObjects(); en.hasMoreElements();) {
                /*
                Countersignature values have the same meaning as SignerInfo values
                for ordinary signatures, except that:
                    
                   1. The signedAttributes field MUST NOT contain a content-type
                  attribute; there is no content type for countersignatures.
                    
                   2. The signedAttributes field MUST contain a message-digest
                  attribute if it contains any other attributes.
                    
                   3. The input to the message-digesting process is the contents
                  octets of the DER encoding of the signatureValue field of the
                  SignerInfo value with which the attribute is associated.
                */
                SignerInfo si = SignerInfo.getInstance(en.nextElement());

                counterSignatures
                        .add(new SignerInformation(si, null, new CMSProcessableByteArray(getSignature()), null));
            }
        }

        return new SignerInformationStore(counterSignatures);
    }

    /**
     * return the DER encoding of the signed attributes.
     * @throws IOException if an encoding error occurs.
     */
    public byte[] getEncodedSignedAttributes() throws IOException {
        if (signedAttributeSet != null) {
            return signedAttributeSet.getEncoded(ASN1Encoding.DER);
        }

        return null;
    }

    private boolean doVerify(SignerInformationVerifier verifier) throws CMSException {
        String encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
        ContentVerifier contentVerifier;

        try {
            contentVerifier = verifier.getContentVerifier(encryptionAlgorithm, info.getDigestAlgorithm());
        } catch (OperatorCreationException e) {
            throw new CMSException("can't create content verifier: " + e.getMessage(), e);
        }

        try {
            OutputStream sigOut = contentVerifier.getOutputStream();

            if (resultDigest == null) {
                DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID());
                if (content != null) {
                    OutputStream digOut = calc.getOutputStream();

                    if (signedAttributeSet == null) {
                        if (contentVerifier instanceof RawContentVerifier) {
                            content.write(digOut);
                        } else {
                            OutputStream cOut = new TeeOutputStream(digOut, sigOut);

                            content.write(cOut);

                            cOut.close();
                        }
                    } else {
                        content.write(digOut);
                        sigOut.write(this.getEncodedSignedAttributes());
                    }

                    digOut.close();
                } else if (signedAttributeSet != null) {
                    sigOut.write(this.getEncodedSignedAttributes());
                } else {
                    // TODO Get rid of this exception and just treat content==null as empty not missing?
                    throw new CMSException("data not encapsulated in signature - use detached constructor.");
                }

                resultDigest = calc.getDigest();
            } else {
                if (signedAttributeSet == null) {
                    if (content != null) {
                        content.write(sigOut);
                    }
                } else {
                    sigOut.write(this.getEncodedSignedAttributes());
                }
            }

            sigOut.close();
        } catch (IOException e) {
            throw new CMSException("can't process mime object to create signature.", e);
        } catch (OperatorCreationException e) {
            throw new CMSException("can't create digest calculator: " + e.getMessage(), e);
        }

        // RFC 3852 11.1 Check the content-type attribute is correct
        {
            ASN1Primitive validContentType = getSingleValuedSignedAttribute(CMSAttributes.contentType,
                    "content-type");
            if (validContentType == null) {
                if (!isCounterSignature && signedAttributeSet != null) {
                    throw new CMSException(
                            "The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
                }
            } else {
                if (isCounterSignature) {
                    throw new CMSException(
                            "[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
                }

                if (!(validContentType instanceof ASN1ObjectIdentifier)) {
                    throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
                }

                ASN1ObjectIdentifier signedContentType = (ASN1ObjectIdentifier) validContentType;

                if (!signedContentType.equals(contentType)) {
                    throw new CMSException("content-type attribute value does not match eContentType");
                }
            }
        }

        AttributeTable signedAttrTable = this.getSignedAttributes();

        // RFC 6211 Validate Algorithm Identifier protection attribute if present
        {
            AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
            if (unsignedAttrTable != null
                    && unsignedAttrTable.getAll(CMSAttributes.cmsAlgorithmProtect).size() > 0) {
                throw new CMSException("A cmsAlgorithmProtect attribute MUST be a signed attribute");
            }
            if (signedAttrTable != null) {
                ASN1EncodableVector protectionAttributes = signedAttrTable
                        .getAll(CMSAttributes.cmsAlgorithmProtect);
                if (protectionAttributes.size() > 1) {
                    throw new CMSException("Only one instance of a cmsAlgorithmProtect attribute can be present");
                }

                if (protectionAttributes.size() > 0) {
                    Attribute attr = Attribute.getInstance(protectionAttributes.get(0));
                    if (attr.getAttrValues().size() != 1) {
                        throw new CMSException("A cmsAlgorithmProtect attribute MUST contain exactly one value");
                    }

                    CMSAlgorithmProtection algorithmProtection = CMSAlgorithmProtection
                            .getInstance(attr.getAttributeValues()[0]);

                    if (!CMSUtils.isEquivalent(algorithmProtection.getDigestAlgorithm(),
                            info.getDigestAlgorithm())) {
                        throw new CMSException(
                                "CMS Algorithm Identifier Protection check failed for digestAlgorithm");
                    }

                    if (!CMSUtils.isEquivalent(algorithmProtection.getSignatureAlgorithm(),
                            info.getDigestEncryptionAlgorithm())) {
                        throw new CMSException(
                                "CMS Algorithm Identifier Protection check failed for signatureAlgorithm");
                    }
                }
            }
        }

        // RFC 3852 11.2 Check the message-digest attribute is correct
        {
            ASN1Primitive validMessageDigest = getSingleValuedSignedAttribute(CMSAttributes.messageDigest,
                    "message-digest");
            if (validMessageDigest == null) {
                if (signedAttributeSet != null) {
                    throw new CMSException(
                            "the message-digest signed attribute type MUST be present when there are any signed attributes present");
                }
            } else {
                if (!(validMessageDigest instanceof ASN1OctetString)) {
                    throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
                }

                ASN1OctetString signedMessageDigest = (ASN1OctetString) validMessageDigest;

                if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets())) {
                    throw new CMSSignerDigestMismatchException(
                            "message-digest attribute value does not match calculated value");
                }
            }
        }

        // RFC 3852 11.4 Validate countersignature attribute(s)
        {
            if (signedAttrTable != null && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0) {
                throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
            }

            AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
            if (unsignedAttrTable != null) {
                ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
                for (int i = 0; i < csAttrs.size(); ++i) {
                    Attribute csAttr = Attribute.getInstance(csAttrs.get(i));
                    if (csAttr.getAttrValues().size() < 1) {
                        throw new CMSException(
                                "A countersignature attribute MUST contain at least one AttributeValue");
                    }

                    // Note: We don't recursively validate the countersignature value
                }
            }
        }

        try {
            if (signedAttributeSet == null && resultDigest != null) {
                if (contentVerifier instanceof RawContentVerifier) {
                    RawContentVerifier rawVerifier = (RawContentVerifier) contentVerifier;

                    if (encName.equals("RSA")) {
                        DigestInfo digInfo = new DigestInfo(
                                new AlgorithmIdentifier(digestAlgorithm.getAlgorithm(), DERNull.INSTANCE),
                                resultDigest);

                        return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature());
                    }

                    return rawVerifier.verify(resultDigest, this.getSignature());
                }
            }

            return contentVerifier.verify(this.getSignature());
        } catch (IOException e) {
            throw new CMSException("can't process mime object to create signature.", e);
        }
    }

    /**
     * Verify that the given verifier can successfully verify the signature on
     * this SignerInformation object.
     *
     * @param verifier a suitably configured SignerInformationVerifier.
     * @return true if the signer information is verified, false otherwise.
     * @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.
     * @throws org.bouncycastle.cms.CMSException if the verifier is unable to create a ContentVerifiers or DigestCalculators.
     */
    public boolean verify(SignerInformationVerifier verifier) throws CMSException {
        Time signingTime = getSigningTime(); // has to be validated if present.

        if (verifier.hasAssociatedCertificate()) {
            if (signingTime != null) {
                X509CertificateHolder dcv = verifier.getAssociatedCertificate();

                if (!dcv.isValidOn(signingTime.getDate())) {
                    throw new CMSVerifierCertificateNotValidException("verifier not valid at signingTime");
                }
            }
        }

        return doVerify(verifier);
    }

    /**
     * Return the underlying ASN.1 object defining this SignerInformation object.
     *
     * @return a SignerInfo.
     */
    public SignerInfo toASN1Structure() {
        return info;
    }

    private ASN1Primitive getSingleValuedSignedAttribute(ASN1ObjectIdentifier attrOID, String printableName)
            throws CMSException {
        AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
        if (unsignedAttrTable != null && unsignedAttrTable.getAll(attrOID).size() > 0) {
            throw new CMSException("The " + printableName + " attribute MUST NOT be an unsigned attribute");
        }

        AttributeTable signedAttrTable = this.getSignedAttributes();
        if (signedAttrTable == null) {
            return null;
        }

        ASN1EncodableVector v = signedAttrTable.getAll(attrOID);
        switch (v.size()) {
        case 0:
            return null;
        case 1: {
            Attribute t = (Attribute) v.get(0);
            ASN1Set attrValues = t.getAttrValues();
            if (attrValues.size() != 1) {
                throw new CMSException("A " + printableName + " attribute MUST have a single attribute value");
            }

            return attrValues.getObjectAt(0).toASN1Primitive();
        }
        default:
            throw new CMSException(
                    "The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
                            + printableName + " attribute");
        }
    }

    private Time getSigningTime() throws CMSException {
        ASN1Primitive validSigningTime = getSingleValuedSignedAttribute(CMSAttributes.signingTime, "signing-time");

        if (validSigningTime == null) {
            return null;
        }

        try {
            return Time.getInstance(validSigningTime);
        } catch (IllegalArgumentException e) {
            throw new CMSException("signing-time attribute value not a valid 'Time' structure");
        }
    }

    /**
     * Return a signer information object with the passed in unsigned
     * attributes replacing the ones that are current associated with
     * the object passed in.
     * 
     * @param signerInformation the signerInfo to be used as the basis.
     * @param unsignedAttributes the unsigned attributes to add.
     * @return a copy of the original SignerInformationObject with the changed attributes.
     */
    public static SignerInformation replaceUnsignedAttributes(SignerInformation signerInformation,
            AttributeTable unsignedAttributes) {
        SignerInfo sInfo = signerInformation.info;
        ASN1Set unsignedAttr = null;

        if (unsignedAttributes != null) {
            unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector());
        }

        return new SignerInformation(
                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(), sInfo.getAuthenticatedAttributes(),
                        sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), unsignedAttr),
                signerInformation.contentType, signerInformation.content, null);
    }

    /**
     * Return a signer information object with passed in SignerInformationStore representing counter
     * signatures attached as an unsigned attribute.
     *
     * @param signerInformation the signerInfo to be used as the basis.
     * @param counterSigners signer info objects carrying counter signature.
     * @return a copy of the original SignerInformationObject with the changed attributes.
     */
    public static SignerInformation addCounterSigners(SignerInformation signerInformation,
            SignerInformationStore counterSigners) {
        // TODO Perform checks from RFC 3852 11.4

        SignerInfo sInfo = signerInformation.info;
        AttributeTable unsignedAttr = signerInformation.getUnsignedAttributes();
        ASN1EncodableVector v;

        if (unsignedAttr != null) {
            v = unsignedAttr.toASN1EncodableVector();
        } else {
            v = new ASN1EncodableVector();
        }

        ASN1EncodableVector sigs = new ASN1EncodableVector();

        for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();) {
            sigs.add(((SignerInformation) it.next()).toASN1Structure());
        }

        v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));

        return new SignerInformation(
                new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(), sInfo.getAuthenticatedAttributes(),
                        sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)),
                signerInformation.contentType, signerInformation.content, null);
    }
}