context: using System;
  using System.Collections;
  using System.Diagnostics;
  using System.IO;
  
  using Org.BouncyCastle.Asn1;
  using Org.BouncyCastle.Asn1.Cms;
  using Org.BouncyCastle.Asn1.X509;
  using Org.BouncyCastle.Crypto;
  using Org.BouncyCastle.Crypto.IO;
  using Org.BouncyCastle.Crypto.Parameters;
  using Org.BouncyCastle.Security;
  using Org.BouncyCastle.Security.Certificates;
  using Org.BouncyCastle.Utilities;
  using Org.BouncyCastle.Utilities.Collections;
  using Org.BouncyCastle.Utilities.IO;
  using Org.BouncyCastle.X509;
  
  namespace Org.BouncyCastle.Cms
  {
      /**
      * General class for generating a pkcs7-signature message stream.
      * <p>
      * A simple example of usage.
      * </p>
      * <pre>
      *      IX509Store                   certs...
      *      CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator();
      *
      *      gen.AddSigner(privateKey, cert, CmsSignedDataStreamGenerator.DIGEST_SHA1);
      *
      *      gen.AddCertificates(certs);
      *
      *      Stream sigOut = gen.Open(bOut);
      *
      *      sigOut.Write(Encoding.UTF8.GetBytes("Hello World!"));
      *
      *      sigOut.Close();
      * </pre>
      */
      public class CmsSignedDataStreamGenerator
          : CmsSignedGenerator
      {
  		private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
  
  		private readonly IList      _signerInfs = Platform.CreateArrayList();
  		private readonly ISet		_messageDigestOids = new HashSet();
          private readonly IDictionary _messageDigests = Platform.CreateHashtable();
          private readonly IDictionary _messageHashes = Platform.CreateHashtable();
  		private bool				_messageDigestsLocked;
          private int					_bufferSize;
  
  		private class DigestAndSignerInfoGeneratorHolder
  		{
  			internal readonly SignerInfoGenerator	signerInf;
  			internal readonly string				digestOID;
  
  			internal DigestAndSignerInfoGeneratorHolder(SignerInfoGenerator signerInf, String digestOID)
  			{
  				this.signerInf = signerInf;
  				this.digestOID = digestOID;
  			}
  
  			internal AlgorithmIdentifier DigestAlgorithm
  			{
  				get { return new AlgorithmIdentifier(new DerObjectIdentifier(this.digestOID), DerNull.Instance); }
  			}
  		}
  
  		private class SignerInfoGeneratorImpl : SignerInfoGenerator
          {
  			private readonly CmsSignedDataStreamGenerator outer;
  
  			private readonly SignerIdentifier			_signerIdentifier;
  			private readonly string						_digestOID;
  			private readonly string						_encOID;
  			private readonly CmsAttributeTableGenerator	_sAttr;
  			private readonly CmsAttributeTableGenerator	_unsAttr;
  			private readonly string						_encName;
  			private readonly ISigner					_sig;
  
  			internal SignerInfoGeneratorImpl(
  				CmsSignedDataStreamGenerator	outer,
  				AsymmetricKeyParameter			key,
  				SignerIdentifier				signerIdentifier,
  				string							digestOID,
  				string							encOID,
  				CmsAttributeTableGenerator		sAttr,
  				CmsAttributeTableGenerator		unsAttr)
  			{
  				this.outer = outer;
  
  				_signerIdentifier = signerIdentifier;
  				_digestOID = digestOID;
  				_encOID = encOID;
  				_sAttr = sAttr;
  				_unsAttr = unsAttr;
  				_encName = Helper.GetEncryptionAlgName(_encOID);
  
  				string digestName = Helper.GetDigestAlgName(_digestOID);
  				string signatureName = digestName + "with" + _encName;
  
  				if (_sAttr != null)
  				{
              		_sig = Helper.GetSignatureInstance(signatureName);
  				}
  				else
  				{
  					// Note: Need to use raw signatures here since we have already calculated the digest
  					if (_encName.Equals("RSA"))
  					{
  						_sig = Helper.GetSignatureInstance("RSA");
  					}
  					else if (_encName.Equals("DSA"))
  					{
  						_sig = Helper.GetSignatureInstance("NONEwithDSA");
  					}
  					// TODO Add support for raw PSS
  //					else if (_encName.equals("RSAandMGF1"))
  //					{
  //						_sig = CMSSignedHelper.INSTANCE.getSignatureInstance("NONEWITHRSAPSS", _sigProvider);
  //						try
  //						{
  //							// Init the params this way to avoid having a 'raw' version of each PSS algorithm
  //							Signature sig2 = CMSSignedHelper.INSTANCE.getSignatureInstance(signatureName, _sigProvider);
  //							PSSParameterSpec spec = (PSSParameterSpec)sig2.getParameters().getParameterSpec(PSSParameterSpec.class);
  //							_sig.setParameter(spec);
  //						}
  //						catch (Exception e)
  //						{
  //							throw new SignatureException("algorithm: " + _encName + " could not be configured.");
  //						}
  //					}
  					else
  					{
  						throw new SignatureException("algorithm: " + _encName + " not supported in base signatures.");
  					}
  				}
  
  				_sig.Init(true, new ParametersWithRandom(key, outer.rand));
  			}
  
  			public SignerInfo Generate(DerObjectIdentifier contentType, AlgorithmIdentifier digestAlgorithm,
          		byte[] calculatedDigest)
  			{
  				try
  				{
  					string digestName = Helper.GetDigestAlgName(_digestOID);
  					string signatureName = digestName + "with" + _encName;
  
  //					AlgorithmIdentifier digAlgId = DigestAlgorithmID;
  //
  //					byte[] hash = (byte[])outer._messageHashes[Helper.GetDigestAlgName(this._digestOID)];
  //					outer._digests[_digestOID] = hash.Clone();
  
  					byte[] bytesToSign = calculatedDigest;
  
  					/* RFC 3852 5.4
  					 * The result of the message digest calculation process depends on
  					 * whether the signedAttrs field is present.  When the field is absent,
  					 * the result is just the message digest of the content as described
  					 *
  					 * above.  When the field is present, however, the result is the message
  					 * digest of the complete DER encoding of the SignedAttrs value
  					 * contained in the signedAttrs field.
  					 */
  					Asn1Set signedAttr = null;
  					if (_sAttr != null)
  					{
  						IDictionary parameters = outer.GetBaseParameters(contentType, digestAlgorithm, calculatedDigest);
  
  //						Asn1.Cms.AttributeTable signed = _sAttr.GetAttributes(Collections.unmodifiableMap(parameters));
  						Asn1.Cms.AttributeTable signed = _sAttr.GetAttributes(parameters);
  
                          if (contentType == null) //counter signature
                          {
                              if (signed != null && signed[CmsAttributes.ContentType] != null)
                              {
                                  IDictionary tmpSigned = signed.ToDictionary();
                                  tmpSigned.Remove(CmsAttributes.ContentType);
                                  signed = new Asn1.Cms.AttributeTable(tmpSigned);
                              }
                          }
  
                          signedAttr = outer.GetAttributeSet(signed);
  
                  		// sig must be composed from the DER encoding.
  						bytesToSign = signedAttr.GetEncoded(Asn1Encodable.Der);
  					}
  					else
  					{
  						// Note: Need to use raw signatures here since we have already calculated the digest
  						if (_encName.Equals("RSA"))
  						{
  							DigestInfo dInfo = new DigestInfo(digestAlgorithm, calculatedDigest);
  							bytesToSign = dInfo.GetEncoded(Asn1Encodable.Der);
  						}
  					}
  
  					_sig.BlockUpdate(bytesToSign, 0, bytesToSign.Length);
  					byte[] sigBytes = _sig.GenerateSignature();
  
  					Asn1Set unsignedAttr = null;
  					if (_unsAttr != null)
  					{
  						IDictionary parameters = outer.GetBaseParameters(
  							contentType, digestAlgorithm, calculatedDigest);
  						parameters[CmsAttributeTableParameter.Signature] = sigBytes.Clone();
  
  //						Asn1.Cms.AttributeTable unsigned = _unsAttr.getAttributes(Collections.unmodifiableMap(parameters));
  						Asn1.Cms.AttributeTable unsigned = _unsAttr.GetAttributes(parameters);
  
  						unsignedAttr = outer.GetAttributeSet(unsigned);
  					}
  
  					// TODO[RSAPSS] Need the ability to specify non-default parameters
  					Asn1Encodable sigX509Parameters = SignerUtilities.GetDefaultX509Parameters(signatureName);
  					AlgorithmIdentifier digestEncryptionAlgorithm = CmsSignedGenerator.GetEncAlgorithmIdentifier(
  						new DerObjectIdentifier(_encOID), sigX509Parameters);
  
  					return new SignerInfo(_signerIdentifier, digestAlgorithm,
  						signedAttr, digestEncryptionAlgorithm, new DerOctetString(sigBytes), unsignedAttr);
  				}
  	            catch (IOException e)
  	            {
  	                throw new CmsStreamException("encoding error.", e);
  	            }
  	            catch (SignatureException e)
  	            {
  	                throw new CmsStreamException("error creating signature.", e);
  	            }
              }
          }
  
  		public CmsSignedDataStreamGenerator()
          {
          }
  
  		/// <summary>Constructor allowing specific source of randomness</summary>
  		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
  		public CmsSignedDataStreamGenerator(
  			SecureRandom rand)
  			: base(rand)
  		{
  		}
  
  		/**
          * Set the underlying string size for encapsulated data
          *
          * @param bufferSize length of octet strings to buffer the data.
          */
          public void SetBufferSize(
              int bufferSize)
          {
              _bufferSize = bufferSize;
          }
  
  		public void AddDigests(
         		params string[] digestOids)
  		{
         		AddDigests((IEnumerable) digestOids);
  		}
  
  		public void AddDigests(
  			IEnumerable digestOids)
  		{
  			foreach (string digestOid in digestOids)
  			{
  				ConfigureDigest(digestOid);
  			}
  		}
  
  		/**
          * add a signer - no attributes other than the default ones will be
          * provided here.
          * @throws NoSuchAlgorithmException
          * @throws InvalidKeyException
          */
          public void AddSigner(
              AsymmetricKeyParameter	privateKey,
              X509Certificate			cert,
              string					digestOid)
          {
  			AddSigner(privateKey, cert, digestOid,
  				new DefaultSignedAttributeTableGenerator(), null);
  		}
  
  		/**
  		 * add a signer, specifying the digest encryption algorithm - no attributes other than the default ones will be
  		 * provided here.
  		 * @throws NoSuchProviderException
  		 * @throws NoSuchAlgorithmException
  		 * @throws InvalidKeyException
  		 */
  		public void AddSigner(
  			AsymmetricKeyParameter	privateKey,
  			X509Certificate			cert,
  			string					encryptionOid,
  			string					digestOid)
  		{
  			AddSigner(privateKey, cert, encryptionOid, digestOid,
  				new DefaultSignedAttributeTableGenerator(),
  				(CmsAttributeTableGenerator)null);
  		}
  
          /**
          * add a signer with extra signed/unsigned attributes.
          * @throws NoSuchAlgorithmException
          * @throws InvalidKeyException
          */
          public void AddSigner(
              AsymmetricKeyParameter	privateKey,
              X509Certificate			cert,
              string					digestOid,
              Asn1.Cms.AttributeTable	signedAttr,
              Asn1.Cms.AttributeTable	unsignedAttr)
          {
  			AddSigner(privateKey, cert, digestOid,
  				new DefaultSignedAttributeTableGenerator(signedAttr),
  				new SimpleAttributeTableGenerator(unsignedAttr));
  		}
  
  		/**
  		 * add a signer with extra signed/unsigned attributes - specifying digest
  		 * encryption algorithm.
  		 * @throws NoSuchProviderException
  		 * @throws NoSuchAlgorithmException
  		 * @throws InvalidKeyException
  		 */
  		public void AddSigner(
  			AsymmetricKeyParameter	privateKey,
  			X509Certificate			cert,
  			string					encryptionOid,
  			string					digestOid,
  			Asn1.Cms.AttributeTable	signedAttr,
  			Asn1.Cms.AttributeTable	unsignedAttr)
  		{
  			AddSigner(privateKey, cert, encryptionOid, digestOid,
  				new DefaultSignedAttributeTableGenerator(signedAttr),
  				new SimpleAttributeTableGenerator(unsignedAttr));
  		}
  
  		public void AddSigner(
  			AsymmetricKeyParameter		privateKey,
  			X509Certificate				cert,
  			string						digestOid,
  			CmsAttributeTableGenerator  signedAttrGenerator,
  			CmsAttributeTableGenerator  unsignedAttrGenerator)
  		{
  			AddSigner(privateKey, cert, GetEncOid(privateKey, digestOid), digestOid,
  				signedAttrGenerator, unsignedAttrGenerator);
          }
  
  		public void AddSigner(
  			AsymmetricKeyParameter		privateKey,
  			X509Certificate				cert,
  			string						encryptionOid,
  			string						digestOid,
  			CmsAttributeTableGenerator  signedAttrGenerator,
  			CmsAttributeTableGenerator  unsignedAttrGenerator)
  		{
  			DoAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOid, digestOid,
  				signedAttrGenerator, unsignedAttrGenerator);
  		}
  
  		/**
  		* add a signer - no attributes other than the default ones will be
  		* provided here.
  		* @throws NoSuchAlgorithmException
  		* @throws InvalidKeyException
  		*/
  		public void AddSigner(
  			AsymmetricKeyParameter	privateKey,
  			byte[]					subjectKeyID,
  			string					digestOid)
  		{
  			AddSigner(privateKey, subjectKeyID, digestOid, new DefaultSignedAttributeTableGenerator(),
  				(CmsAttributeTableGenerator)null);
  		}
  
  		/**
  		 * add a signer - no attributes other than the default ones will be
  		 * provided here.
  		 * @throws NoSuchProviderException
  		 * @throws NoSuchAlgorithmException
  		 * @throws InvalidKeyException
  		 */
  		public void AddSigner(
  			AsymmetricKeyParameter	privateKey,
  			byte[]					subjectKeyID,
  			string					encryptionOid,
  			string					digestOid)
  		{
  			AddSigner(privateKey, subjectKeyID, encryptionOid, digestOid,
  				new DefaultSignedAttributeTableGenerator(),
  				(CmsAttributeTableGenerator)null);
  		}
  
  		/**
  		* add a signer with extra signed/unsigned attributes.
  		* @throws NoSuchAlgorithmException
  		* @throws InvalidKeyException
  		*/
  		public void AddSigner(
  			AsymmetricKeyParameter	privateKey,
  			byte[]					subjectKeyID,
  			string					digestOid,
  			Asn1.Cms.AttributeTable	signedAttr,
  			Asn1.Cms.AttributeTable	unsignedAttr)
  	    {
  	        AddSigner(privateKey, subjectKeyID, digestOid,
  				new DefaultSignedAttributeTableGenerator(signedAttr),
  				new SimpleAttributeTableGenerator(unsignedAttr));
  		}
  
  		public void AddSigner(
  			AsymmetricKeyParameter		privateKey,
  			byte[]						subjectKeyID,
  			string						digestOid,
  			CmsAttributeTableGenerator	signedAttrGenerator,
  			CmsAttributeTableGenerator	unsignedAttrGenerator)
  		{
  			AddSigner(privateKey, subjectKeyID, GetEncOid(privateKey, digestOid),
  				digestOid, signedAttrGenerator, unsignedAttrGenerator);
  		}
  
  		public void AddSigner(
  			AsymmetricKeyParameter		privateKey,
  			byte[]						subjectKeyID,
  			string						encryptionOid,
  			string						digestOid,
  			CmsAttributeTableGenerator	signedAttrGenerator,
  			CmsAttributeTableGenerator	unsignedAttrGenerator)
  		{
  			DoAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOid, digestOid,
  				signedAttrGenerator, unsignedAttrGenerator);
  		}
  
  		private void DoAddSigner(
  			AsymmetricKeyParameter		privateKey,
  			SignerIdentifier			signerIdentifier,
  			string						encryptionOid,
  			string						digestOid,
  			CmsAttributeTableGenerator	signedAttrGenerator,
  			CmsAttributeTableGenerator	unsignedAttrGenerator)
  		{
  			ConfigureDigest(digestOid);
  
  			SignerInfoGeneratorImpl signerInf = new SignerInfoGeneratorImpl(this, privateKey,
  				signerIdentifier, digestOid, encryptionOid, signedAttrGenerator, unsignedAttrGenerator);
  
  			_signerInfs.Add(new DigestAndSignerInfoGeneratorHolder(signerInf, digestOid));
  		}
  
  		internal override void AddSignerCallback(
  			SignerInformation si)
  		{
  			// FIXME If there were parameters in si.DigestAlgorithmID.Parameters, they are lost
  			// NB: Would need to call FixAlgID on the DigestAlgorithmID
  
  			// For precalculated signers, just need to register the algorithm, not configure a digest
  			RegisterDigestOid(si.DigestAlgorithmID.ObjectID.Id);
  		}
  
  		/**
          * generate a signed object that for a CMS Signed Data object
          */
          public Stream Open(
              Stream outStream)
          {
              return Open(outStream, false);
          }
  
          /**
          * generate a signed object that for a CMS Signed Data
          * object - if encapsulate is true a copy
          * of the message will be included in the signature with the
          * default content type "data".
          */
          public Stream Open(
              Stream	outStream,
              bool	encapsulate)
          {
              return Open(outStream, Data, encapsulate);
          }
  
  		/**
  		 * generate a signed object that for a CMS Signed Data
  		 * object using the given provider - if encapsulate is true a copy
  		 * of the message will be included in the signature with the
  		 * default content type "data". If dataOutputStream is non null the data
  		 * being signed will be written to the stream as it is processed.
  		 * @param out stream the CMS object is to be written to.
  		 * @param encapsulate true if data should be encapsulated.
  		 * @param dataOutputStream output stream to copy the data being signed to.
  		 */
  		public Stream Open(
  			Stream	outStream,
  			bool	encapsulate,
  			Stream	dataOutputStream)
  		{
  			return Open(outStream, Data, encapsulate, dataOutputStream);
  		}
  
  		/**
          * generate a signed object that for a CMS Signed Data
          * object - if encapsulate is true a copy
          * of the message will be included in the signature. The content type
          * is set according to the OID represented by the string signedContentType.
          */
          public Stream Open(
              Stream	outStream,
              string	signedContentType,
              bool	encapsulate)
          {
  			return Open(outStream, signedContentType, encapsulate, null);
  		}
  
  		/**
  		* generate a signed object that for a CMS Signed Data
  		* object using the given provider - if encapsulate is true a copy
  		* of the message will be included in the signature. The content type
  		* is set according to the OID represented by the string signedContentType.
  		* @param out stream the CMS object is to be written to.
  		* @param signedContentType OID for data to be signed.
  		* @param encapsulate true if data should be encapsulated.
  		* @param dataOutputStream output stream to copy the data being signed to.
  		*/
  		public Stream Open(
  			Stream	outStream,
  			string	signedContentType,
  			bool	encapsulate,
  			Stream	dataOutputStream)
  		{
  			if (outStream == null)
  				throw new ArgumentNullException("outStream");
  			if (!outStream.CanWrite)
  				throw new ArgumentException("Expected writeable stream", "outStream");
  			if (dataOutputStream != null && !dataOutputStream.CanWrite)
  				throw new ArgumentException("Expected writeable stream", "dataOutputStream");
  
  			_messageDigestsLocked = true;
  
  			//
              // ContentInfo
              //
              BerSequenceGenerator sGen = new BerSequenceGenerator(outStream);
  
  			sGen.AddObject(CmsObjectIdentifiers.SignedData);
  
  			//
              // Signed Data
              //
              BerSequenceGenerator sigGen = new BerSequenceGenerator(
  				sGen.GetRawOutputStream(), 0, true);
  
              bool isCounterSignature = (signedContentType == null);
  
              DerObjectIdentifier contentTypeOid = isCounterSignature
                  ? null
                  : new DerObjectIdentifier(signedContentType);
  
              sigGen.AddObject(CalculateVersion(contentTypeOid));
  
  			Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
  
  			foreach (string digestOid in _messageDigestOids)
              {
  				digestAlgs.Add(
              		new AlgorithmIdentifier(new DerObjectIdentifier(digestOid), DerNull.Instance));
              }
  
              {
  				byte[] tmp = new DerSet(digestAlgs).GetEncoded();
  				sigGen.GetRawOutputStream().Write(tmp, 0, tmp.Length);
  			}
  
  			BerSequenceGenerator eiGen = new BerSequenceGenerator(sigGen.GetRawOutputStream());
              eiGen.AddObject(contentTypeOid);
  
          	// If encapsulating, add the data as an octet string in the sequence
  			Stream encapStream = encapsulate
  				?	CmsUtilities.CreateBerOctetOutputStream(eiGen.GetRawOutputStream(), 0, true, _bufferSize)
  				:	null;
  
          	// Also send the data to 'dataOutputStream' if necessary
  			Stream teeStream = GetSafeTeeOutputStream(dataOutputStream, encapStream);
  
          	// Let all the digests see the data as it is written
  			Stream digStream = AttachDigestsToOutputStream(_messageDigests.Values, teeStream);
  
  			return new CmsSignedDataOutputStream(this, digStream, signedContentType, sGen, sigGen, eiGen);
          }
  
  		private void RegisterDigestOid(
  			string digestOid)
  		{
         		if (_messageDigestsLocked)
         		{
         			if (!_messageDigestOids.Contains(digestOid))
  					throw new InvalidOperationException("Cannot register new digest OIDs after the data stream is opened");
         		}
         		else
         		{
  				_messageDigestOids.Add(digestOid);
         		}
  		}
  
  		private void ConfigureDigest(
  			string digestOid)
  		{
         		RegisterDigestOid(digestOid);
  
         		string digestName = Helper.GetDigestAlgName(digestOid);
  			IDigest dig = (IDigest)_messageDigests[digestName];
  			if (dig == null)
  			{
  				if (_messageDigestsLocked)
  					throw new InvalidOperationException("Cannot configure new digests after the data stream is opened");
  
  
gt:             	dig = Helper.GetDigestInstance(digestName);