//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.cms;

import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;

import com.dstc.security.asn1.Asn1Exception;

import com.dstc.security.provider.OID;
import com.dstc.security.cms.v1.ContentInfo;
import com.dstc.security.cms.v1.SignedData;
import com.dstc.security.cms.v1.SignerInfo;
import com.dstc.security.cms.v1.EncapContentInfo;
import com.dstc.security.cms.v1.CertificateSet;

/**
 * <p>A class representating message signed according to CMS,
 * (for the case where the message to be signed is not carried
 * in the SignedData structure, as used in S/MIME for the mime
 * type multipart/signed).
 *
 * <p>To sign a message according to CMS, first construct a
 * Signer object. Use this together with the digest of the
 * message to be signed to construct a SignedMessage object.
 * Calling sign() on this object returns the DER encoding
 * of a ContentInfo-encapsulated SignedData structure, as
 * specified by CMS. For multiple signing, call addSignature()
 * passing in other Signer objects before calling sign().
 *
 * <p>To verify a signed message formatted as a
 * ContentInfo-encapsulated SignedData structure, use its DER
 * encoding to construct a SignedMessage object. Next, specify
 * the set of trusted CA certificates by calling setTrustedCerts().
 * Finally call verify() on the object, passing in the digest of the
 * message.
 *
 * @version 0.99, 99/01/26
 * @author Ming Yung
 */
public class SignedMessage
{
  private Set signers = null;
  private Set signerInfos = null;
  private Set trustedCerts = null;
  private Set digestAlgorithms = null;
  private Set certs = null;
  private byte[] toBeSigned = null;
  private EncapContentInfo eContentInfo = null;
  private boolean includeMessage = false;
  private ContentInfo contentInfo = null;;
  private SignedData signedData = null;

  /**
   * Constructs a SignedMessage from a DER encoding of a
   * ContentInfo-encapsulated SignedData structure
   */
  public SignedMessage(byte encoded[]) throws Asn1Exception, CMSException
  {
    this.contentInfo = new ContentInfo(encoded);
    this.signedData = new SignedData(contentInfo.getContent().encode());

    this.certs = signedData.getCertificates();

    this.signerInfos = signedData.getSignerInfos();

    this.signers = new HashSet();
    Iterator it = this.signerInfos.iterator();
    while(it.hasNext())
    {
      this.signers.add(new Signer((SignerInfo)it.next(), this.certs));
    }

    this.eContentInfo = signedData.getContentInfo();
    this.toBeSigned = this.eContentInfo.getContent();
  }

  /**
   * Constructs a SignedMessage from a Signer and a message to be signed
   */
  public SignedMessage(Signer signer, byte message[]) throws CMSException
  {
    this.toBeSigned = message;

    this.signers = new HashSet();
    this.signers.add(signer);

    this.digestAlgorithms = new HashSet();
    this.digestAlgorithms.add(signer.getDigestAlgorithm());

    this.signerInfos = new HashSet();
    this.signerInfos.add(signer.sign(message));

    this.certs = new HashSet();
    this.certs.addAll(signer.getCertificates());

    if (includeMessage)
      this.eContentInfo = new EncapContentInfo(OID.id_data, null);
    else
      this.eContentInfo = new EncapContentInfo(OID.id_data, this.toBeSigned);
  }

  /**
   * Adds the signature of a supplied Signer to this SignedMessage
   */
  public void addSignature(Signer signer) throws CMSException
  {
    this.signers.add(signer);
    this.digestAlgorithms.add(signer.getDigestAlgorithm());
    this.signerInfos.add(signer.sign(this.toBeSigned));
    this.certs.addAll(signer.getCertificates());
  }

  /**
   * Sets the option of including the message in the SignedMessage.
   * Defaults to false (the recommended usage for S/MIME v3) in which
   * case the message is carried separately.
   */
  public void setIncludeMessage(boolean truth)
  {
    this.includeMessage = truth;
  }
    
  /**
   * Returns the Set of Signers for this SignedMessage
   */
  public Set getSigners()
  {
    return this.signers;
  }

  /**
   * Sets the trusted CAs to verify this SignedMessage against
   */
  public void setTrustedCAs(Set certs)
  {
    this.trustedCerts = certs;
  }

  /**
   * Signs and returns the CMS (PKCS#7) encoding for this SignedMessage
   */
  public byte[] sign()
  {
    this.signedData 
      = new SignedData(this.digestAlgorithms, this.eContentInfo, 
          this.certs, null, this.signerInfos);
    this.contentInfo = new ContentInfo(OID.id_signedData, signedData);
    return this.contentInfo.encode();
  }

  /**
   * Returns the CMS (PKCS#7) encoding for this SignedMessage
   */
  public byte[] getEncoded()
  {
    if (this.contentInfo == null)
      return sign();
    else
      return this.contentInfo.encode();
  }

  /**
   * Verifies all the signatures in this SignedMessage against a
   * supplied message
   */
  public void verify(byte[] message) throws CMSException
  {
    if (this.trustedCerts == null)
      throw new CMSException("No trusted CAs set");

    Iterator it = this.getSigners().iterator();
    while(it.hasNext())
    {
      Signer signer = (Signer)it.next();
      signer.verify(message);
      signer.getCertPath().verify(this.trustedCerts);
    }
  }

  /**
   * Verifies all the signatures in this SignedMessage
   */
  public void verify() throws CMSException
  {
    this.verify(this.toBeSigned);
  }

  /**
   * Returns the Set of digest algorithms used in this SignedMessage
   */
  public Set getDigestAlgorithms()
  {
    return this.digestAlgorithms;
  }

  /**
   * Returns the Set of Certificates for this SignedMessage
   */
  public Set getCertificates()
  {
    return this.certs;
  }

  /**
   * Returns the message encapsulated in this SignedMessage, or
   * null if it is not included
   */
  public byte[] getMessage()
  {
    return this.toBeSigned;
  }
}
