package org.bouncycastle.asn1.cms;

import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

/**
 * <a href="https://tools.ietf.org/html/rfc5652#section-5.3">RFC 5652</a>:
 * Signature container per Signer, see {@link SignerIdentifier}.
 * <pre>
 * PKCS#7:
 *
 * SignerInfo ::= SEQUENCE {
 *     version                   Version,
 *     sid                       SignerIdentifier,
 *     digestAlgorithm           DigestAlgorithmIdentifier,
 *     authenticatedAttributes   [0] IMPLICIT Attributes OPTIONAL,
 *     digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
 *     encryptedDigest           EncryptedDigest,
 *     unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
 * }
 *
 * EncryptedDigest ::= OCTET STRING
 *
 * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
 *
 * DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
 *
 * -----------------------------------------
 *
 * RFC 5652:
 *
 * SignerInfo ::= SEQUENCE {
 *     version            CMSVersion,
 *     sid                SignerIdentifier,
 *     digestAlgorithm    DigestAlgorithmIdentifier,
 *     signedAttrs        [0] IMPLICIT SignedAttributes OPTIONAL,
 *     signatureAlgorithm SignatureAlgorithmIdentifier,
 *     signature          SignatureValue,
 *     unsignedAttrs      [1] IMPLICIT UnsignedAttributes OPTIONAL
 * }
 *
 * -- {@link SignerIdentifier} referenced certificates are at containing
 * -- {@link SignedData} certificates element.
 *
 * SignerIdentifier ::= CHOICE {
 *     issuerAndSerialNumber {@link IssuerAndSerialNumber},
 *     subjectKeyIdentifier  [0] SubjectKeyIdentifier }
 *
 * -- See {@link Attributes} for generalized SET OF {@link Attribute}
 *
 * SignedAttributes   ::= SET SIZE (1..MAX) OF Attribute
 * UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute
 * 
 * {@link Attribute} ::= SEQUENCE {
 *     attrType   OBJECT IDENTIFIER,
 *     attrValues SET OF AttributeValue }
 *
 * AttributeValue ::= ANY
 * 
 * SignatureValue ::= OCTET STRING
 * </pre>
 */
public class SignerInfo
    extends ASN1Object
{
    private ASN1Integer              version;
    private SignerIdentifier        sid;
    private AlgorithmIdentifier     digAlgorithm;
    private ASN1Set                 authenticatedAttributes;
    private AlgorithmIdentifier     digEncryptionAlgorithm;
    private ASN1OctetString         encryptedDigest;
    private ASN1Set                 unauthenticatedAttributes;

    /**
     * Return a SignerInfo object from the given input
     * <p>
     * Accepted inputs:
     * <ul>
     * <li> null &rarr; null
     * <li> {@link SignerInfo} object
     * <li> {@link org.bouncycastle.asn1.ASN1Sequence#getInstance(java.lang.Object) ASN1Sequence} input formats with SignerInfo structure inside
     * </ul>
     *
     * @param o the object we want converted.
     * @exception IllegalArgumentException if the object cannot be converted.
     */
    public static SignerInfo getInstance(
        Object  o)
        throws IllegalArgumentException
    {
        if (o instanceof SignerInfo)
        {
            return (SignerInfo)o;
        }
        else if (o != null)
        {
            return new SignerInfo(ASN1Sequence.getInstance(o));
        }

        return null;
    }

    /**
     *
     * @param sid
     * @param digAlgorithm            CMS knows as 'digestAlgorithm'
     * @param authenticatedAttributes CMS knows as 'signedAttrs'
     * @param digEncryptionAlgorithm  CMS knows as 'signatureAlgorithm'
     * @param encryptedDigest         CMS knows as 'signature'
     * @param unauthenticatedAttributes CMS knows as 'unsignedAttrs'
     */
    public SignerInfo(
        SignerIdentifier        sid,
        AlgorithmIdentifier     digAlgorithm,
        ASN1Set                 authenticatedAttributes,
        AlgorithmIdentifier     digEncryptionAlgorithm,
        ASN1OctetString         encryptedDigest,
        ASN1Set                 unauthenticatedAttributes)
    {
        if (sid.isTagged())
        {
            this.version = new ASN1Integer(3);
        }
        else
        {
            this.version = new ASN1Integer(1);
        }

        this.sid = sid;
        this.digAlgorithm = digAlgorithm;
        this.authenticatedAttributes = authenticatedAttributes;
        this.digEncryptionAlgorithm = digEncryptionAlgorithm;
        this.encryptedDigest = encryptedDigest;
        this.unauthenticatedAttributes = unauthenticatedAttributes;
    }

    /**
     *
     * @param sid
     * @param digAlgorithm            CMS knows as 'digestAlgorithm'
     * @param authenticatedAttributes CMS knows as 'signedAttrs'
     * @param digEncryptionAlgorithm  CMS knows as 'signatureAlgorithm'
     * @param encryptedDigest         CMS knows as 'signature'
     * @param unauthenticatedAttributes CMS knows as 'unsignedAttrs'
     */
    public SignerInfo(
        SignerIdentifier        sid,
        AlgorithmIdentifier     digAlgorithm,
        Attributes              authenticatedAttributes,
        AlgorithmIdentifier     digEncryptionAlgorithm,
        ASN1OctetString         encryptedDigest,
        Attributes              unauthenticatedAttributes)
    {
        if (sid.isTagged())
        {
            this.version = new ASN1Integer(3);
        }
        else
        {
            this.version = new ASN1Integer(1);
        }

        this.sid = sid;
        this.digAlgorithm = digAlgorithm;
        this.authenticatedAttributes = ASN1Set.getInstance(authenticatedAttributes);
        this.digEncryptionAlgorithm = digEncryptionAlgorithm;
        this.encryptedDigest = encryptedDigest;
        this.unauthenticatedAttributes = ASN1Set.getInstance(unauthenticatedAttributes);
    }

    private SignerInfo(
        ASN1Sequence seq)
    {
        Enumeration     e = seq.getObjects();

        version = (ASN1Integer)e.nextElement();
        sid = SignerIdentifier.getInstance(e.nextElement());
        digAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement());

        Object obj = e.nextElement();

        if (obj instanceof ASN1TaggedObject)
        {
            authenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)obj, false);

            digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(e.nextElement());
        }
        else
        {
            authenticatedAttributes = null;
            digEncryptionAlgorithm = AlgorithmIdentifier.getInstance(obj);
        }

        encryptedDigest = DEROctetString.getInstance(e.nextElement());

        if (e.hasMoreElements())
        {
            unauthenticatedAttributes = ASN1Set.getInstance((ASN1TaggedObject)e.nextElement(), false);
        }
        else
        {
            unauthenticatedAttributes = null;
        }
    }

    public ASN1Integer getVersion()
    {
        return version;
    }

    public SignerIdentifier getSID()
    {
        return sid;
    }

    public ASN1Set getAuthenticatedAttributes()
    {
        return authenticatedAttributes;
    }

    public AlgorithmIdentifier getDigestAlgorithm()
    {
        return digAlgorithm;
    }

    public ASN1OctetString getEncryptedDigest()
    {
        return encryptedDigest;
    }

    public AlgorithmIdentifier getDigestEncryptionAlgorithm()
    {
        return digEncryptionAlgorithm;
    }

    public ASN1Set getUnauthenticatedAttributes()
    {
        return unauthenticatedAttributes;
    }

    /**
     * Produce an object suitable for an ASN1OutputStream.
     */
    public ASN1Primitive toASN1Primitive()
    {
        ASN1EncodableVector v = new ASN1EncodableVector(7);

        v.add(version);
        v.add(sid);
        v.add(digAlgorithm);

        if (authenticatedAttributes != null)
        {
            v.add(new DERTaggedObject(false, 0, authenticatedAttributes));
        }

        v.add(digEncryptionAlgorithm);
        v.add(encryptedDigest);

        if (unauthenticatedAttributes != null)
        {
            v.add(new DERTaggedObject(false, 1, unauthenticatedAttributes));
        }

        return new DERSequence(v);
    }
}
