summary refs log tree commit diff
path: root/Crypto/src/cms
diff options
context:
space:
mode:
Diffstat (limited to 'Crypto/src/cms')
-rw-r--r--Crypto/src/cms/BaseDigestCalculator.cs23
-rw-r--r--Crypto/src/cms/CMSAttributeTableGenerationException.cs25
-rw-r--r--Crypto/src/cms/CMSAttributeTableGenerator.cs25
-rw-r--r--Crypto/src/cms/CMSAuthEnvelopedData.cs112
-rw-r--r--Crypto/src/cms/CMSAuthEnvelopedGenerator.cs16
-rw-r--r--Crypto/src/cms/CMSAuthenticatedData.cs137
-rw-r--r--Crypto/src/cms/CMSAuthenticatedDataGenerator.cs156
-rw-r--r--Crypto/src/cms/CMSAuthenticatedDataParser.cs214
-rw-r--r--Crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs275
-rw-r--r--Crypto/src/cms/CMSAuthenticatedGenerator.cs35
-rw-r--r--Crypto/src/cms/CMSCompressedData.cs107
-rw-r--r--Crypto/src/cms/CMSCompressedDataGenerator.cs66
-rw-r--r--Crypto/src/cms/CMSCompressedDataParser.cs57
-rw-r--r--Crypto/src/cms/CMSCompressedDataStreamGenerator.cs144
-rw-r--r--Crypto/src/cms/CMSContentInfoParser.cs47
-rw-r--r--Crypto/src/cms/CMSEnvelopedData.cs115
-rw-r--r--Crypto/src/cms/CMSEnvelopedDataGenerator.cs178
-rw-r--r--Crypto/src/cms/CMSEnvelopedDataParser.cs161
-rw-r--r--Crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs285
-rw-r--r--Crypto/src/cms/CMSEnvelopedGenerator.cs331
-rw-r--r--Crypto/src/cms/CMSEnvelopedHelper.cs311
-rw-r--r--Crypto/src/cms/CMSException.cs25
-rw-r--r--Crypto/src/cms/CMSPBEKey.cs109
-rw-r--r--Crypto/src/cms/CMSProcessable.cs19
-rw-r--r--Crypto/src/cms/CMSProcessableByteArray.cs36
-rw-r--r--Crypto/src/cms/CMSProcessableFile.cs57
-rw-r--r--Crypto/src/cms/CMSProcessableInputStream.cs52
-rw-r--r--Crypto/src/cms/CMSReadable.cs10
-rw-r--r--Crypto/src/cms/CMSSecureReadable.cs14
-rw-r--r--Crypto/src/cms/CMSSignedData.cs425
-rw-r--r--Crypto/src/cms/CMSSignedDataGenerator.cs551
-rw-r--r--Crypto/src/cms/CMSSignedDataParser.cs455
-rw-r--r--Crypto/src/cms/CMSSignedDataStreamGenerator.cs917
-rw-r--r--Crypto/src/cms/CMSSignedGenerator.cs261
-rw-r--r--Crypto/src/cms/CMSSignedHelper.cs319
-rw-r--r--Crypto/src/cms/CMSStreamException.cs26
-rw-r--r--Crypto/src/cms/CMSTypedStream.cs72
-rw-r--r--Crypto/src/cms/CMSUtils.cs186
-rw-r--r--Crypto/src/cms/CounterSignatureDigestCalculator.cs28
-rw-r--r--Crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs90
-rw-r--r--Crypto/src/cms/DefaultSignedAttributeTableGenerator.cs124
-rw-r--r--Crypto/src/cms/DigOutputStream.cs28
-rw-r--r--Crypto/src/cms/IDigestCalculator.cs9
-rw-r--r--Crypto/src/cms/KEKRecipientInfoGenerator.cs137
-rw-r--r--Crypto/src/cms/KEKRecipientInformation.cs62
-rw-r--r--Crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs171
-rw-r--r--Crypto/src/cms/KeyAgreeRecipientInformation.cs226
-rw-r--r--Crypto/src/cms/KeyTransRecipientInfoGenerator.cs87
-rw-r--r--Crypto/src/cms/KeyTransRecipientInformation.cs113
-rw-r--r--Crypto/src/cms/MacOutputStream.cs28
-rw-r--r--Crypto/src/cms/NullOutputStream.cs20
-rw-r--r--Crypto/src/cms/OriginatorId.cs51
-rw-r--r--Crypto/src/cms/PKCS5Scheme2PBEKey.cs64
-rw-r--r--Crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs64
-rw-r--r--Crypto/src/cms/PasswordRecipientInfoGenerator.cs69
-rw-r--r--Crypto/src/cms/PasswordRecipientInformation.cs79
-rw-r--r--Crypto/src/cms/RecipientId.cs58
-rw-r--r--Crypto/src/cms/RecipientInfoGenerator.cs26
-rw-r--r--Crypto/src/cms/RecipientInformation.cs126
-rw-r--r--Crypto/src/cms/RecipientInformationStore.cs86
-rw-r--r--Crypto/src/cms/SigOutputStream.cs43
-rw-r--r--Crypto/src/cms/SignerId.cs51
-rw-r--r--Crypto/src/cms/SignerInfoGenerator.cs14
-rw-r--r--Crypto/src/cms/SignerInformation.cs758
-rw-r--r--Crypto/src/cms/SignerInformationStore.cs74
-rw-r--r--Crypto/src/cms/SimpleAttributeTableGenerator.cs28
66 files changed, 9068 insertions, 0 deletions
diff --git a/Crypto/src/cms/BaseDigestCalculator.cs b/Crypto/src/cms/BaseDigestCalculator.cs
new file mode 100644
index 000000000..3dcbca753
--- /dev/null
+++ b/Crypto/src/cms/BaseDigestCalculator.cs
@@ -0,0 +1,23 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class BaseDigestCalculator
+		: IDigestCalculator
+	{
+		private readonly byte[] digest;
+
+		internal BaseDigestCalculator(
+			byte[] digest)
+		{
+			this.digest = digest;
+		}
+
+		public byte[] GetDigest()
+		{
+			return Arrays.Clone(digest);
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSAttributeTableGenerationException.cs b/Crypto/src/cms/CMSAttributeTableGenerationException.cs
new file mode 100644
index 000000000..2bc8ce5d1
--- /dev/null
+++ b/Crypto/src/cms/CMSAttributeTableGenerationException.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Org.BouncyCastle.Cms
+{
+	public class CmsAttributeTableGenerationException
+		: CmsException
+	{
+		public CmsAttributeTableGenerationException()
+		{
+		}
+
+		public CmsAttributeTableGenerationException(
+			string name)
+			: base(name)
+		{
+		}
+
+		public CmsAttributeTableGenerationException(
+			string		name,
+			Exception	e)
+			: base(name, e)
+		{
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSAttributeTableGenerator.cs b/Crypto/src/cms/CMSAttributeTableGenerator.cs
new file mode 100644
index 000000000..92c9a29d9
--- /dev/null
+++ b/Crypto/src/cms/CMSAttributeTableGenerator.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1.Cms;
+
+namespace Org.BouncyCastle.Cms
+{
+	/// <remarks>
+	/// The 'Signature' parameter is only available when generating unsigned attributes.
+	/// </remarks>
+	public enum CmsAttributeTableParameter
+	{
+//		const string ContentType = "contentType";
+//		const string Digest = "digest";
+//		const string Signature = "encryptedDigest";
+//		const string DigestAlgorithmIdentifier = "digestAlgID";
+
+		ContentType, Digest, Signature, DigestAlgorithmIdentifier
+	}
+
+	public interface CmsAttributeTableGenerator
+	{
+		AttributeTable GetAttributes(IDictionary parameters);
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthEnvelopedData.cs b/Crypto/src/cms/CMSAuthEnvelopedData.cs
new file mode 100644
index 000000000..d35e946ae
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthEnvelopedData.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* containing class for an CMS AuthEnveloped Data object
+	*/
+	internal class CmsAuthEnvelopedData
+	{
+		internal RecipientInformationStore recipientInfoStore;
+		internal ContentInfo contentInfo;
+
+		private OriginatorInfo      originator;
+		private AlgorithmIdentifier authEncAlg;
+		private Asn1Set             authAttrs;
+		private byte[]              mac;
+		private Asn1Set             unauthAttrs;
+	
+		public CmsAuthEnvelopedData(
+			byte[] authEnvData)
+			: this(CmsUtilities.ReadContentInfo(authEnvData))
+		{
+		}
+
+		public CmsAuthEnvelopedData(
+			Stream authEnvData)
+			: this(CmsUtilities.ReadContentInfo(authEnvData))
+		{
+		}
+
+		public CmsAuthEnvelopedData(
+			ContentInfo contentInfo)
+		{
+			this.contentInfo = contentInfo;
+
+			AuthEnvelopedData authEnvData = AuthEnvelopedData.GetInstance(contentInfo.Content);
+
+			this.originator = authEnvData.OriginatorInfo;
+
+			//
+	        // read the recipients
+	        //
+	        Asn1Set recipientInfos = authEnvData.RecipientInfos;
+
+			//
+			// read the auth-encrypted content info
+			//
+			EncryptedContentInfo authEncInfo = authEnvData.AuthEncryptedContentInfo;
+			this.authEncAlg = authEncInfo.ContentEncryptionAlgorithm;
+			CmsSecureReadable secureReadable = new AuthEnvelopedSecureReadable(this);
+
+			//
+			// build the RecipientInformationStore
+			//
+			this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore(
+				recipientInfos, secureReadable);
+
+			// FIXME These need to be passed to the AEAD cipher as AAD (Additional Authenticated Data)
+			this.authAttrs = authEnvData.AuthAttrs;
+			this.mac = authEnvData.Mac.GetOctets();
+			this.unauthAttrs = authEnvData.UnauthAttrs;
+		}
+
+		private class AuthEnvelopedSecureReadable : CmsSecureReadable
+		{
+			private readonly CmsAuthEnvelopedData parent;
+
+			internal AuthEnvelopedSecureReadable(CmsAuthEnvelopedData parent)
+			{
+				this.parent = parent;
+			}
+
+			public AlgorithmIdentifier Algorithm
+			{
+				get { return parent.authEncAlg; }
+			}
+
+			public object CryptoObject
+			{
+				get { return null; }
+			}
+
+			public CmsReadable GetReadable(KeyParameter key)
+			{
+				// TODO Create AEAD cipher instance to decrypt and calculate tag ( MAC)
+				throw new CmsException("AuthEnveloped data decryption not yet implemented");
+
+//				RFC 5084 ASN.1 Module
+//				-- Parameters for AlgorithmIdentifier
+//				
+//				CCMParameters ::= SEQUENCE {
+//				  aes-nonce         OCTET STRING (SIZE(7..13)),
+//				  aes-ICVlen        AES-CCM-ICVlen DEFAULT 12 }
+//				
+//				AES-CCM-ICVlen ::= INTEGER (4 | 6 | 8 | 10 | 12 | 14 | 16)
+//				
+//				GCMParameters ::= SEQUENCE {
+//				  aes-nonce        OCTET STRING, -- recommended size is 12 octets
+//				  aes-ICVlen       AES-GCM-ICVlen DEFAULT 12 }
+//				
+//				AES-GCM-ICVlen ::= INTEGER (12 | 13 | 14 | 15 | 16)
+			}            
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthEnvelopedGenerator.cs b/Crypto/src/cms/CMSAuthEnvelopedGenerator.cs
new file mode 100644
index 000000000..4273cff29
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthEnvelopedGenerator.cs
@@ -0,0 +1,16 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Nist;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class CmsAuthEnvelopedGenerator
+	{
+		public static readonly string Aes128Ccm = NistObjectIdentifiers.IdAes128Ccm.Id;
+		public static readonly string Aes192Ccm = NistObjectIdentifiers.IdAes192Ccm.Id;
+		public static readonly string Aes256Ccm = NistObjectIdentifiers.IdAes256Ccm.Id;
+		public static readonly string Aes128Gcm = NistObjectIdentifiers.IdAes128Gcm.Id;
+		public static readonly string Aes192Gcm = NistObjectIdentifiers.IdAes192Gcm.Id;
+		public static readonly string Aes256Gcm = NistObjectIdentifiers.IdAes256Gcm.Id;
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthenticatedData.cs b/Crypto/src/cms/CMSAuthenticatedData.cs
new file mode 100644
index 000000000..5e234da2b
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthenticatedData.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* containing class for an CMS Authenticated Data object
+	*/
+	public class CmsAuthenticatedData
+	{
+		internal RecipientInformationStore recipientInfoStore;
+		internal ContentInfo contentInfo;
+
+		private AlgorithmIdentifier macAlg;
+		private Asn1Set authAttrs;
+		private Asn1Set unauthAttrs;
+		private byte[] mac;
+
+		public CmsAuthenticatedData(
+			byte[] authData)
+			: this(CmsUtilities.ReadContentInfo(authData))
+		{
+		}
+
+		public CmsAuthenticatedData(
+			Stream authData)
+			: this(CmsUtilities.ReadContentInfo(authData))
+		{
+		}
+
+		public CmsAuthenticatedData(
+			ContentInfo contentInfo)
+		{
+			this.contentInfo = contentInfo;
+
+			AuthenticatedData authData = AuthenticatedData.GetInstance(contentInfo.Content);
+
+			//
+			// read the recipients
+			//
+			Asn1Set recipientInfos = authData.RecipientInfos;
+
+			this.macAlg = authData.MacAlgorithm;
+
+			//
+			// read the authenticated content info
+			//
+			ContentInfo encInfo = authData.EncapsulatedContentInfo;
+			CmsReadable readable = new CmsProcessableByteArray(
+				Asn1OctetString.GetInstance(encInfo.Content).GetOctets());
+			CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsAuthenticatedSecureReadable(
+				this.macAlg, readable);
+
+			//
+			// build the RecipientInformationStore
+			//
+			this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore(
+				recipientInfos, secureReadable);
+
+			this.authAttrs = authData.AuthAttrs;
+			this.mac = authData.Mac.GetOctets();
+			this.unauthAttrs = authData.UnauthAttrs;
+		}
+
+		public byte[] GetMac()
+		{
+			return Arrays.Clone(mac);
+		}
+
+		public AlgorithmIdentifier MacAlgorithmID
+		{
+			get { return macAlg; }
+		}
+
+		/**
+		* return the object identifier for the content MAC algorithm.
+		*/
+		public string MacAlgOid
+		{
+			get { return macAlg.ObjectID.Id; }
+		}
+
+		/**
+		* return a store of the intended recipients for this message
+		*/
+		public RecipientInformationStore GetRecipientInfos()
+		{
+			return recipientInfoStore;
+		}
+
+		/**
+		 * return the ContentInfo 
+		 */
+		public ContentInfo ContentInfo
+		{
+			get { return contentInfo; }
+		}
+
+		/**
+		* return a table of the digested attributes indexed by
+		* the OID of the attribute.
+		*/
+		public Asn1.Cms.AttributeTable GetAuthAttrs()
+		{
+			if (authAttrs == null)
+				return null;
+
+			return new Asn1.Cms.AttributeTable(authAttrs);
+		}
+
+		/**
+		* return a table of the undigested attributes indexed by
+		* the OID of the attribute.
+		*/
+		public Asn1.Cms.AttributeTable GetUnauthAttrs()
+		{
+			if (unauthAttrs == null)
+				return null;
+
+			return new Asn1.Cms.AttributeTable(unauthAttrs);
+		}
+
+		/**
+		* return the ASN.1 encoded representation of this object.
+		*/
+		public byte[] GetEncoded()
+		{
+			return contentInfo.GetEncoded();
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthenticatedDataGenerator.cs b/Crypto/src/cms/CMSAuthenticatedDataGenerator.cs
new file mode 100644
index 000000000..0a37ca4f5
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthenticatedDataGenerator.cs
@@ -0,0 +1,156 @@
+using System;
+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.Generators;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	 * General class for generating a CMS authenticated-data message.
+	 *
+	 * A simple example of usage.
+	 *
+	 * <pre>
+	 *      CMSAuthenticatedDataGenerator  fact = new CMSAuthenticatedDataGenerator();
+	 *
+	 *      fact.addKeyTransRecipient(cert);
+	 *
+	 *      CMSAuthenticatedData         data = fact.generate(content, algorithm, "BC");
+	 * </pre>
+	 */
+	public class CmsAuthenticatedDataGenerator
+	    : CmsAuthenticatedGenerator
+	{
+	    /**
+	     * base constructor
+	     */
+	    public CmsAuthenticatedDataGenerator()
+	    {
+	    }
+
+	    /**
+	     * constructor allowing specific source of randomness
+	     * @param rand instance of SecureRandom to use
+	     */
+	    public CmsAuthenticatedDataGenerator(
+	        SecureRandom rand)
+	        : base(rand)
+	    {
+	    }
+
+	    /**
+	     * generate an enveloped object that contains an CMS Enveloped Data
+	     * object using the given provider and the passed in key generator.
+	     */
+		private CmsAuthenticatedData Generate(
+			CmsProcessable		content,
+			string				macOid,
+			CipherKeyGenerator	keyGen)
+		{
+			AlgorithmIdentifier macAlgId;
+			KeyParameter encKey;
+			Asn1OctetString encContent;
+			Asn1OctetString macResult;
+
+			try
+			{
+				// FIXME Will this work for macs?
+				byte[] encKeyBytes = keyGen.GenerateKey();
+				encKey = ParameterUtilities.CreateKeyParameter(macOid, encKeyBytes);
+
+				Asn1Encodable asn1Params = GenerateAsn1Parameters(macOid, encKeyBytes);
+
+				ICipherParameters cipherParameters;
+				macAlgId = GetAlgorithmIdentifier(
+				macOid, encKey, asn1Params, out cipherParameters);
+
+				IMac mac = MacUtilities.GetMac(macOid);
+				// TODO Confirm no ParametersWithRandom needed
+				// FIXME Only passing key at the moment
+//	            mac.Init(cipherParameters);
+				mac.Init(encKey);
+
+				MemoryStream bOut = new MemoryStream();
+				Stream mOut = new TeeOutputStream(bOut, new MacOutputStream(mac));
+
+				content.Write(mOut);
+
+                mOut.Dispose();
+                bOut.Dispose();
+
+				encContent = new BerOctetString(bOut.ToArray());
+
+				byte[] macOctets = MacUtilities.DoFinal(mac);
+				macResult = new DerOctetString(macOctets);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("exception decoding algorithm parameters.", e);
+			}
+
+			Asn1EncodableVector recipientInfos = new Asn1EncodableVector();
+
+			foreach (RecipientInfoGenerator rig in recipientInfoGenerators) 
+			{
+				try
+				{
+					recipientInfos.Add(rig.Generate(encKey, rand));
+				}
+				catch (InvalidKeyException e)
+				{
+					throw new CmsException("key inappropriate for algorithm.", e);
+				}
+				catch (GeneralSecurityException e)
+				{
+					throw new CmsException("error making encrypted content.", e);
+				}
+			}
+			
+			ContentInfo eci = new ContentInfo(CmsObjectIdentifiers.Data, encContent);
+			
+			ContentInfo contentInfo = new ContentInfo(
+			CmsObjectIdentifiers.AuthenticatedData,
+			new AuthenticatedData(null, new DerSet(recipientInfos), macAlgId, null, eci, null, macResult, null));
+			
+			return new CmsAuthenticatedData(contentInfo);
+		}
+
+	    /**
+	     * generate an authenticated object that contains an CMS Authenticated Data object
+	     */
+	    public CmsAuthenticatedData Generate(
+	        CmsProcessable	content,
+	        string			encryptionOid)
+	    {
+            try
+            {
+				// FIXME Will this work for macs?
+				CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+				keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+
+				return Generate(content, encryptionOid, keyGen);
+            }
+            catch (SecurityUtilityException e)
+            {
+                throw new CmsException("can't find key generation algorithm.", e);
+            }
+	    }
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthenticatedDataParser.cs b/Crypto/src/cms/CMSAuthenticatedDataParser.cs
new file mode 100644
index 000000000..c99aac61c
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthenticatedDataParser.cs
@@ -0,0 +1,214 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* Parsing class for an CMS Authenticated Data object from an input stream.
+	* <p>
+	* Note: that because we are in a streaming mode only one recipient can be tried and it is important
+	* that the methods on the parser are called in the appropriate order.
+	* </p>
+	* <p>
+	* Example of use - assuming the first recipient matches the private key we have.
+	* <pre>
+	*      CMSAuthenticatedDataParser     ad = new CMSAuthenticatedDataParser(inputStream);
+	*
+	*      RecipientInformationStore  recipients = ad.getRecipientInfos();
+	*
+	*      Collection  c = recipients.getRecipients();
+	*      Iterator    it = c.iterator();
+	*
+	*      if (it.hasNext())
+	*      {
+	*          RecipientInformation   recipient = (RecipientInformation)it.next();
+	*
+	*          CMSTypedStream recData = recipient.getContentStream(privateKey, "BC");
+	*
+	*          processDataStream(recData.getContentStream());
+	*
+	*          if (!Arrays.equals(ad.getMac(), recipient.getMac())
+	*          {
+	*              System.err.println("Data corrupted!!!!");
+	*          }
+	*      }
+	*  </pre>
+	*  Note: this class does not introduce buffering - if you are processing large files you should create
+	*  the parser with:
+	*  <pre>
+	*          CMSAuthenticatedDataParser     ep = new CMSAuthenticatedDataParser(new BufferedInputStream(inputStream, bufSize));
+	*  </pre>
+	*  where bufSize is a suitably large buffer size.
+	* </p>
+	*/
+	public class CmsAuthenticatedDataParser
+		: CmsContentInfoParser
+	{
+		internal RecipientInformationStore	_recipientInfoStore;
+		internal AuthenticatedDataParser	authData;
+
+		private AlgorithmIdentifier			macAlg;
+		private byte[]						mac;
+		private Asn1.Cms.AttributeTable		authAttrs;
+		private Asn1.Cms.AttributeTable		unauthAttrs;
+
+		private bool authAttrNotRead;
+		private bool unauthAttrNotRead;
+
+		public CmsAuthenticatedDataParser(
+			byte[] envelopedData)
+			: this(new MemoryStream(envelopedData, false))
+		{
+		}
+
+		public CmsAuthenticatedDataParser(
+			Stream envelopedData)
+			: base(envelopedData)
+		{
+			this.authAttrNotRead = true;
+			this.authData = new AuthenticatedDataParser(
+				(Asn1SequenceParser)contentInfo.GetContent(Asn1Tags.Sequence));
+
+			// TODO Validate version?
+			//DerInteger version = this.authData.getVersion();
+
+			//
+			// read the recipients
+			//
+			Asn1Set recipientInfos = Asn1Set.GetInstance(authData.GetRecipientInfos().ToAsn1Object());
+
+			this.macAlg = authData.GetMacAlgorithm();
+
+			//
+			// read the authenticated content info
+			//
+			ContentInfoParser data = authData.GetEnapsulatedContentInfo();
+			CmsReadable readable = new CmsProcessableInputStream(
+				((Asn1OctetStringParser)data.GetContent(Asn1Tags.OctetString)).GetOctetStream());
+			CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsAuthenticatedSecureReadable(
+				this.macAlg, readable);
+
+			//
+			// build the RecipientInformationStore
+			//
+			this._recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore(
+				recipientInfos, secureReadable);
+		}
+
+		public AlgorithmIdentifier MacAlgorithmID
+		{
+			get { return macAlg; }
+		}
+
+		/**
+		* return the object identifier for the mac algorithm.
+		*/
+		public string MacAlgOid
+		{
+			get { return macAlg.ObjectID.Id; }
+		}
+
+
+		/**
+		 * return the ASN.1 encoded encryption algorithm parameters, or null if
+		 * there aren't any.
+		 */
+		public Asn1Object MacAlgParams
+		{
+			get
+			{
+				Asn1Encodable ae = macAlg.Parameters;
+
+				return ae == null ? null : ae.ToAsn1Object();
+			}
+		}
+
+		/**
+		* return a store of the intended recipients for this message
+		*/
+		public RecipientInformationStore GetRecipientInfos()
+		{
+			return _recipientInfoStore;
+		}
+
+		public byte[] GetMac()
+		{
+			if (mac == null)
+			{
+				GetAuthAttrs();
+				mac = authData.GetMac().GetOctets();
+			}
+			return Arrays.Clone(mac);
+		}
+
+		/**
+		* return a table of the unauthenticated attributes indexed by
+		* the OID of the attribute.
+		* @exception java.io.IOException
+		*/
+		public Asn1.Cms.AttributeTable GetAuthAttrs()
+		{
+			if (authAttrs == null && authAttrNotRead)
+			{
+				Asn1SetParser s = authData.GetAuthAttrs();
+
+				authAttrNotRead = false;
+
+				if (s != null)
+				{
+					Asn1EncodableVector v = new Asn1EncodableVector();
+
+					IAsn1Convertible o;
+					while ((o = s.ReadObject()) != null)
+					{
+						Asn1SequenceParser seq = (Asn1SequenceParser)o;
+
+						v.Add(seq.ToAsn1Object());
+					}
+
+					authAttrs = new Asn1.Cms.AttributeTable(new DerSet(v));
+				}
+			}
+
+			return authAttrs;
+		}
+
+		/**
+		* return a table of the unauthenticated attributes indexed by
+		* the OID of the attribute.
+		* @exception java.io.IOException
+		*/
+		public Asn1.Cms.AttributeTable GetUnauthAttrs()
+		{
+			if (unauthAttrs == null && unauthAttrNotRead)
+			{
+				Asn1SetParser s = authData.GetUnauthAttrs();
+
+				unauthAttrNotRead = false;
+
+				if (s != null)
+				{
+					Asn1EncodableVector v = new Asn1EncodableVector();
+
+					IAsn1Convertible o;
+					while ((o = s.ReadObject()) != null)
+					{
+						Asn1SequenceParser seq = (Asn1SequenceParser)o;
+
+						v.Add(seq.ToAsn1Object());
+					}
+
+					unauthAttrs = new Asn1.Cms.AttributeTable(new DerSet(v));
+				}
+			}
+
+			return unauthAttrs;
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs b/Crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
new file mode 100644
index 000000000..c133ae240
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
@@ -0,0 +1,275 @@
+using System;
+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.Generators;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* General class for generating a CMS authenticated-data message stream.
+	* <p>
+	* A simple example of usage.
+	* <pre>
+	*      CMSAuthenticatedDataStreamGenerator edGen = new CMSAuthenticatedDataStreamGenerator();
+	*
+	*      edGen.addKeyTransRecipient(cert);
+	*
+	*      ByteArrayOutputStream  bOut = new ByteArrayOutputStream();
+	*
+	*      OutputStream out = edGen.open(
+	*                              bOut, CMSAuthenticatedDataGenerator.AES128_CBC, "BC");*
+	*      out.write(data);
+	*
+	*      out.close();
+	* </pre>
+	* </p>
+	*/
+	public class CmsAuthenticatedDataStreamGenerator
+		: CmsAuthenticatedGenerator
+	{
+		// TODO Add support
+//		private object              _originatorInfo = null;
+//		private object              _unprotectedAttributes = null;
+		private int                 _bufferSize;
+		private bool                _berEncodeRecipientSet;
+
+		/**
+		* base constructor
+		*/
+		public CmsAuthenticatedDataStreamGenerator()
+		{
+		}
+
+		/**
+		* constructor allowing specific source of randomness
+		* @param rand instance of SecureRandom to use
+		*/
+		public CmsAuthenticatedDataStreamGenerator(
+			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;
+		}
+
+		/**
+		* Use a BER Set to store the recipient information
+		*/
+		public void SetBerEncodeRecipients(
+			bool berEncodeRecipientSet)
+		{
+			_berEncodeRecipientSet = berEncodeRecipientSet;
+		}
+
+		/**
+		* generate an enveloped object that contains an CMS Enveloped Data
+		* object using the given provider and the passed in key generator.
+		* @throws java.io.IOException
+		*/
+		private Stream Open(
+			Stream				outStr,
+			string				macOid,
+			CipherKeyGenerator	keyGen)
+		{
+			// FIXME Will this work for macs?
+			byte[] encKeyBytes = keyGen.GenerateKey();
+			KeyParameter encKey = ParameterUtilities.CreateKeyParameter(macOid, encKeyBytes);
+
+			Asn1Encodable asn1Params = GenerateAsn1Parameters(macOid, encKeyBytes);
+
+			ICipherParameters cipherParameters;
+			AlgorithmIdentifier macAlgId = GetAlgorithmIdentifier(
+				macOid, encKey, asn1Params, out cipherParameters);
+
+			Asn1EncodableVector recipientInfos = new Asn1EncodableVector();
+
+			foreach (RecipientInfoGenerator rig in recipientInfoGenerators)
+			{
+				try
+				{
+					recipientInfos.Add(rig.Generate(encKey, rand));
+				}
+				catch (InvalidKeyException e)
+				{
+					throw new CmsException("key inappropriate for algorithm.", e);
+				}
+				catch (GeneralSecurityException e)
+				{
+					throw new CmsException("error making encrypted content.", e);
+				}
+			}
+
+			// FIXME Only passing key at the moment
+//			return Open(outStr, macAlgId, cipherParameters, recipientInfos);
+			return Open(outStr, macAlgId, encKey, recipientInfos);
+		}
+
+		protected Stream Open(
+			Stream        			outStr,
+			AlgorithmIdentifier		macAlgId,
+			ICipherParameters		cipherParameters,
+			Asn1EncodableVector		recipientInfos)
+		{
+			try
+			{
+				//
+				// ContentInfo
+				//
+				BerSequenceGenerator cGen = new BerSequenceGenerator(outStr);
+
+				cGen.AddObject(CmsObjectIdentifiers.AuthenticatedData);
+
+				//
+				// Authenticated Data
+				//
+				BerSequenceGenerator authGen = new BerSequenceGenerator(
+					cGen.GetRawOutputStream(), 0, true);
+
+				authGen.AddObject(new DerInteger(AuthenticatedData.CalculateVersion(null)));
+
+				Stream authRaw = authGen.GetRawOutputStream();
+				Asn1Generator recipGen = _berEncodeRecipientSet
+					?	(Asn1Generator) new BerSetGenerator(authRaw)
+					:	new DerSetGenerator(authRaw);
+
+				foreach (Asn1Encodable ae in recipientInfos)
+				{
+					recipGen.AddObject(ae);
+				}
+
+				recipGen.Close();
+
+				authGen.AddObject(macAlgId);
+
+				BerSequenceGenerator eiGen = new BerSequenceGenerator(authRaw);
+				eiGen.AddObject(CmsObjectIdentifiers.Data);
+
+				Stream octetOutputStream = CmsUtilities.CreateBerOctetOutputStream(
+					eiGen.GetRawOutputStream(), 0, false, _bufferSize);
+
+				IMac mac = MacUtilities.GetMac(macAlgId.ObjectID);
+				// TODO Confirm no ParametersWithRandom needed
+	            mac.Init(cipherParameters);
+				Stream mOut = new TeeOutputStream(octetOutputStream, new MacOutputStream(mac));
+
+				return new CmsAuthenticatedDataOutputStream(mOut, mac, cGen, authGen, eiGen);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("exception decoding algorithm parameters.", e);
+			}
+		}
+
+		/**
+		* generate an enveloped object that contains an CMS Enveloped Data object
+		*/
+		public Stream Open(
+			Stream	outStr,
+			string	encryptionOid)
+		{
+			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+			keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+
+			return Open(outStr, encryptionOid, keyGen);
+		}
+
+		/**
+		* generate an enveloped object that contains an CMS Enveloped Data object
+		*/
+		public Stream Open(
+			Stream	outStr,
+			string	encryptionOid,
+			int		keySize)
+		{
+			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+			keyGen.Init(new KeyGenerationParameters(rand, keySize));
+
+			return Open(outStr, encryptionOid, keyGen);
+		}
+
+		private class CmsAuthenticatedDataOutputStream
+			: BaseOutputStream
+		{
+			private readonly Stream					macStream;
+			private readonly IMac					mac;
+			private readonly BerSequenceGenerator	cGen;
+			private readonly BerSequenceGenerator	authGen;
+			private readonly BerSequenceGenerator	eiGen;
+
+			public CmsAuthenticatedDataOutputStream(
+				Stream					macStream,
+				IMac					mac,
+				BerSequenceGenerator	cGen,
+				BerSequenceGenerator	authGen,
+				BerSequenceGenerator	eiGen)
+			{
+				this.macStream = macStream;
+				this.mac = mac;
+				this.cGen = cGen;
+				this.authGen = authGen;
+				this.eiGen = eiGen;
+			}
+
+			public override void WriteByte(
+				byte b)
+			{
+				macStream.WriteByte(b);
+			}
+
+			public override void Write(
+				byte[]	bytes,
+				int		off,
+				int		len)
+			{
+				macStream.Write(bytes, off, len);
+			}
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    macStream.Dispose();
+
+                    // TODO Parent context(s) should really be be closed explicitly
+
+                    eiGen.Close();
+
+                    // [TODO] auth attributes go here 
+                    byte[] macOctets = MacUtilities.DoFinal(mac);
+                    authGen.AddObject(new DerOctetString(macOctets));
+                    // [TODO] unauth attributes go here
+
+                    authGen.Close();
+                    cGen.Close();
+                }
+            }
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSAuthenticatedGenerator.cs b/Crypto/src/cms/CMSAuthenticatedGenerator.cs
new file mode 100644
index 000000000..8824d1913
--- /dev/null
+++ b/Crypto/src/cms/CMSAuthenticatedGenerator.cs
@@ -0,0 +1,35 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Date;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	public class CmsAuthenticatedGenerator
+		: CmsEnvelopedGenerator
+	{
+		/**
+		* base constructor
+		*/
+		public CmsAuthenticatedGenerator()
+		{
+		}
+
+		/**
+		* constructor allowing specific source of randomness
+		*
+		* @param rand instance of SecureRandom to use
+		*/
+		public CmsAuthenticatedGenerator(
+			SecureRandom rand)
+			: base(rand)
+		{
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSCompressedData.cs b/Crypto/src/cms/CMSCompressedData.cs
new file mode 100644
index 000000000..55d06b5b8
--- /dev/null
+++ b/Crypto/src/cms/CMSCompressedData.cs
@@ -0,0 +1,107 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * containing class for an CMS Compressed Data object
+    */
+    public class CmsCompressedData
+    {
+        internal ContentInfo contentInfo;
+
+		public CmsCompressedData(
+            byte[] compressedData)
+            : this(CmsUtilities.ReadContentInfo(compressedData))
+        {
+        }
+
+		public CmsCompressedData(
+            Stream compressedDataStream)
+            : this(CmsUtilities.ReadContentInfo(compressedDataStream))
+        {
+        }
+
+		public CmsCompressedData(
+            ContentInfo contentInfo)
+        {
+            this.contentInfo = contentInfo;
+        }
+
+		/**
+		 * Return the uncompressed content.
+		 *
+		 * @return the uncompressed content
+		 * @throws CmsException if there is an exception uncompressing the data.
+		 */
+		public byte[] GetContent()
+        {
+            CompressedData comData = CompressedData.GetInstance(contentInfo.Content);
+            ContentInfo content = comData.EncapContentInfo;
+
+			Asn1OctetString bytes = (Asn1OctetString) content.Content;
+			ZInputStream zIn = new ZInputStream(bytes.GetOctetStream());
+
+			try
+			{
+				return CmsUtilities.StreamToByteArray(zIn);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("exception reading compressed stream.", e);
+			}
+			finally
+			{
+                zIn.Dispose();
+			}
+        }
+
+	    /**
+	     * Return the uncompressed content, throwing an exception if the data size
+	     * is greater than the passed in limit. If the content is exceeded getCause()
+	     * on the CMSException will contain a StreamOverflowException
+	     *
+	     * @param limit maximum number of bytes to read
+	     * @return the content read
+	     * @throws CMSException if there is an exception uncompressing the data.
+	     */
+		public byte[] GetContent(int limit)
+		{
+			CompressedData  comData = CompressedData.GetInstance(contentInfo.Content);
+			ContentInfo     content = comData.EncapContentInfo;
+
+			Asn1OctetString bytes = (Asn1OctetString)content.Content;
+
+			ZInputStream zIn = new ZInputStream(new MemoryStream(bytes.GetOctets(), false));
+
+			try
+			{
+				return CmsUtilities.StreamToByteArray(zIn, limit);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("exception reading compressed stream.", e);
+			}
+		}
+
+		/**
+		 * return the ContentInfo 
+		 */
+		public ContentInfo ContentInfo
+		{
+			get { return contentInfo; }
+		}
+
+		/**
+        * return the ASN.1 encoded representation of this object.
+        */
+        public byte[] GetEncoded()
+        {
+			return contentInfo.GetEncoded();
+        }
+    }
+}
diff --git a/Crypto/src/cms/CMSCompressedDataGenerator.cs b/Crypto/src/cms/CMSCompressedDataGenerator.cs
new file mode 100644
index 000000000..a6381ced6
--- /dev/null
+++ b/Crypto/src/cms/CMSCompressedDataGenerator.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * General class for generating a compressed CMS message.
+    * <p>
+    * A simple example of usage.</p>
+    * <p>
+    * <pre>
+    *      CMSCompressedDataGenerator fact = new CMSCompressedDataGenerator();
+    *      CMSCompressedData data = fact.Generate(content, algorithm);
+    * </pre>
+	* </p>
+    */
+    public class CmsCompressedDataGenerator
+    {
+        public const string ZLib = "1.2.840.113549.1.9.16.3.8";
+
+		public CmsCompressedDataGenerator()
+        {
+        }
+
+		/**
+        * Generate an object that contains an CMS Compressed Data
+        */
+        public CmsCompressedData Generate(
+            CmsProcessable	content,
+            string			compressionOid)
+        {
+            AlgorithmIdentifier comAlgId;
+            Asn1OctetString comOcts;
+
+            try
+            {
+                MemoryStream bOut = new MemoryStream();
+                ZOutputStream zOut = new ZOutputStream(bOut, JZlib.Z_DEFAULT_COMPRESSION);
+
+				content.Write(zOut);
+
+                zOut.Dispose();
+
+				comAlgId = new AlgorithmIdentifier(new DerObjectIdentifier(compressionOid));
+				comOcts = new BerOctetString(bOut.ToArray());
+            }
+            catch (IOException e)
+            {
+                throw new CmsException("exception encoding data.", e);
+            }
+
+            ContentInfo comContent = new ContentInfo(CmsObjectIdentifiers.Data, comOcts);
+            ContentInfo contentInfo = new ContentInfo(
+                CmsObjectIdentifiers.CompressedData,
+                new CompressedData(comAlgId, comContent));
+
+			return new CmsCompressedData(contentInfo);
+        }
+    }
+}
diff --git a/Crypto/src/cms/CMSCompressedDataParser.cs b/Crypto/src/cms/CMSCompressedDataParser.cs
new file mode 100644
index 000000000..93dfa1286
--- /dev/null
+++ b/Crypto/src/cms/CMSCompressedDataParser.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * Class for reading a CMS Compressed Data stream.
+    * <pre>
+    *     CMSCompressedDataParser cp = new CMSCompressedDataParser(inputStream);
+    *
+    *     process(cp.GetContent().GetContentStream());
+    * </pre>
+    *  Note: this class does not introduce buffering - if you are processing large files you should create
+    *  the parser with:
+    *  <pre>
+    *      CMSCompressedDataParser     ep = new CMSCompressedDataParser(new BufferedInputStream(inputStream, bufSize));
+    *  </pre>
+    *  where bufSize is a suitably large buffer size.
+    */
+    public class CmsCompressedDataParser
+        : CmsContentInfoParser
+    {
+        public CmsCompressedDataParser(
+            byte[] compressedData)
+            : this(new MemoryStream(compressedData, false))
+        {
+        }
+
+        public CmsCompressedDataParser(
+            Stream compressedData)
+            : base(compressedData)
+        {
+        }
+
+		public CmsTypedStream GetContent()
+        {
+            try
+            {
+                CompressedDataParser comData = new CompressedDataParser((Asn1SequenceParser)this.contentInfo.GetContent(Asn1Tags.Sequence));
+                ContentInfoParser content = comData.GetEncapContentInfo();
+
+                Asn1OctetStringParser bytes = (Asn1OctetStringParser)content.GetContent(Asn1Tags.OctetString);
+
+                return new CmsTypedStream(content.ContentType.ToString(), new ZInputStream(bytes.GetOctetStream()));
+            }
+            catch (IOException e)
+            {
+                throw new CmsException("IOException reading compressed content.", e);
+            }
+        }
+    }
+}
diff --git a/Crypto/src/cms/CMSCompressedDataStreamGenerator.cs b/Crypto/src/cms/CMSCompressedDataStreamGenerator.cs
new file mode 100644
index 000000000..13006d4b0
--- /dev/null
+++ b/Crypto/src/cms/CMSCompressedDataStreamGenerator.cs
@@ -0,0 +1,144 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Utilities.Zlib;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* General class for generating a compressed CMS message stream.
+	* <p>
+	* A simple example of usage.
+	* </p>
+	* <pre>
+	*      CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();
+	*
+	*      Stream cOut = gen.Open(outputStream, CMSCompressedDataStreamGenerator.ZLIB);
+	*
+	*      cOut.Write(data);
+	*
+	*      cOut.Close();
+	* </pre>
+	*/
+	public class CmsCompressedDataStreamGenerator
+	{
+		public const string ZLib = "1.2.840.113549.1.9.16.3.8";
+
+		private int _bufferSize;
+		
+		/**
+		* base constructor
+		*/
+		public CmsCompressedDataStreamGenerator()
+		{
+		}
+
+		/**
+		* 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 Stream Open(
+			Stream	outStream,
+			string	compressionOID)
+		{
+			return Open(outStream, CmsObjectIdentifiers.Data.Id, compressionOID);
+		}
+
+		public Stream Open(
+			Stream	outStream,
+			string	contentOID,
+			string	compressionOID)
+		{
+			BerSequenceGenerator sGen = new BerSequenceGenerator(outStream);
+
+			sGen.AddObject(CmsObjectIdentifiers.CompressedData);
+
+			//
+			// Compressed Data
+			//
+			BerSequenceGenerator cGen = new BerSequenceGenerator(
+				sGen.GetRawOutputStream(), 0, true);
+
+			// CMSVersion
+			cGen.AddObject(new DerInteger(0));
+
+			// CompressionAlgorithmIdentifier
+			cGen.AddObject(new AlgorithmIdentifier(new DerObjectIdentifier(ZLib)));
+
+			//
+			// Encapsulated ContentInfo
+			//
+			BerSequenceGenerator eiGen = new BerSequenceGenerator(cGen.GetRawOutputStream());
+
+			eiGen.AddObject(new DerObjectIdentifier(contentOID));
+
+			Stream octetStream = CmsUtilities.CreateBerOctetOutputStream(
+				eiGen.GetRawOutputStream(), 0, true, _bufferSize);
+
+			return new CmsCompressedOutputStream(
+				new ZOutputStream(octetStream, JZlib.Z_DEFAULT_COMPRESSION), sGen, cGen, eiGen);
+		}
+
+		private class CmsCompressedOutputStream
+			: BaseOutputStream
+		{
+			private ZOutputStream _out;
+			private BerSequenceGenerator _sGen;
+			private BerSequenceGenerator _cGen;
+			private BerSequenceGenerator _eiGen;
+
+			internal CmsCompressedOutputStream(
+				ZOutputStream			outStream,
+				BerSequenceGenerator	sGen,
+				BerSequenceGenerator	cGen,
+				BerSequenceGenerator	eiGen)
+			{
+				_out = outStream;
+				_sGen = sGen;
+				_cGen = cGen;
+				_eiGen = eiGen;
+			}
+
+			public override void WriteByte(
+				byte b)
+			{
+				_out.WriteByte(b);
+			}
+
+			public override void Write(
+				byte[]	bytes,
+				int		off,
+				int		len)
+			{
+				_out.Write(bytes, off, len);
+			}
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    _out.Dispose();
+
+                    // TODO Parent context(s) should really be be closed explicitly
+
+                    _eiGen.Close();
+                    _cGen.Close();
+                    _sGen.Close();
+                }
+
+                base.Dispose(disposing);
+            }
+        }
+	}
+}
diff --git a/Crypto/src/cms/CMSContentInfoParser.cs b/Crypto/src/cms/CMSContentInfoParser.cs
new file mode 100644
index 000000000..5b1606394
--- /dev/null
+++ b/Crypto/src/cms/CMSContentInfoParser.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+
+namespace Org.BouncyCastle.Cms
+{
+	public class CmsContentInfoParser
+	{
+		protected ContentInfoParser	contentInfo;
+		protected Stream data;
+
+		protected CmsContentInfoParser(
+			Stream data)
+		{
+			if (data == null)
+				throw new ArgumentNullException("data");
+
+			this.data = data;
+
+			try
+			{
+				Asn1StreamParser inStream = new Asn1StreamParser(data);
+
+				this.contentInfo = new ContentInfoParser((Asn1SequenceParser)inStream.ReadObject());
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("IOException reading content.", e);
+			}
+			catch (InvalidCastException e)
+			{
+				throw new CmsException("Unexpected object reading content.", e);
+			}
+		}
+
+		/**
+		* Close the underlying data stream.
+		* @throws IOException if the close fails.
+		*/
+		public void Close()
+		{
+            this.data.Dispose();
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSEnvelopedData.cs b/Crypto/src/cms/CMSEnvelopedData.cs
new file mode 100644
index 000000000..0731c307e
--- /dev/null
+++ b/Crypto/src/cms/CMSEnvelopedData.cs
@@ -0,0 +1,115 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * containing class for an CMS Enveloped Data object
+    */
+    public class CmsEnvelopedData
+    {
+        internal RecipientInformationStore	recipientInfoStore;
+        internal ContentInfo				contentInfo;
+
+		private AlgorithmIdentifier	encAlg;
+        private Asn1Set				unprotectedAttributes;
+
+		public CmsEnvelopedData(
+            byte[] envelopedData)
+            : this(CmsUtilities.ReadContentInfo(envelopedData))
+        {
+        }
+
+        public CmsEnvelopedData(
+            Stream envelopedData)
+            : this(CmsUtilities.ReadContentInfo(envelopedData))
+        {
+        }
+
+        public CmsEnvelopedData(
+            ContentInfo contentInfo)
+        {
+            this.contentInfo = contentInfo;
+
+			EnvelopedData envData = EnvelopedData.GetInstance(contentInfo.Content);
+
+			//
+			// read the recipients
+			//
+			Asn1Set recipientInfos = envData.RecipientInfos;
+
+			//
+			// read the encrypted content info
+			//
+			EncryptedContentInfo encInfo = envData.EncryptedContentInfo;
+			this.encAlg = encInfo.ContentEncryptionAlgorithm;
+			CmsReadable readable = new CmsProcessableByteArray(encInfo.EncryptedContent.GetOctets());
+			CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsEnvelopedSecureReadable(
+				this.encAlg, readable);
+
+			//
+			// build the RecipientInformationStore
+			//
+			this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore(
+				recipientInfos, secureReadable);
+
+			this.unprotectedAttributes = envData.UnprotectedAttrs;
+        }
+
+		public AlgorithmIdentifier EncryptionAlgorithmID
+		{
+			get { return encAlg; }
+		}
+
+		/**
+        * return the object identifier for the content encryption algorithm.
+        */
+        public string EncryptionAlgOid
+        {
+			get { return encAlg.ObjectID.Id; }
+        }
+
+		/**
+        * return a store of the intended recipients for this message
+        */
+        public RecipientInformationStore GetRecipientInfos()
+        {
+            return recipientInfoStore;
+        }
+
+		/**
+		 * return the ContentInfo 
+		 */
+		public ContentInfo ContentInfo
+		{
+			get { return contentInfo; }
+		}
+
+		/**
+        * return a table of the unprotected attributes indexed by
+        * the OID of the attribute.
+        */
+        public Asn1.Cms.AttributeTable GetUnprotectedAttributes()
+        {
+            if (unprotectedAttributes == null)
+                return null;
+
+			return new Asn1.Cms.AttributeTable(unprotectedAttributes);
+        }
+
+		/**
+        * return the ASN.1 encoded representation of this object.
+        */
+        public byte[] GetEncoded()
+        {
+			return contentInfo.GetEncoded();
+        }
+    }
+}
diff --git a/Crypto/src/cms/CMSEnvelopedDataGenerator.cs b/Crypto/src/cms/CMSEnvelopedDataGenerator.cs
new file mode 100644
index 000000000..5071af4ad
--- /dev/null
+++ b/Crypto/src/cms/CMSEnvelopedDataGenerator.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+    /// <remarks>
+    /// General class for generating a CMS enveloped-data message.
+    ///
+    /// A simple example of usage.
+    ///
+    /// <pre>
+    ///      CmsEnvelopedDataGenerator  fact = new CmsEnvelopedDataGenerator();
+    ///
+    ///      fact.AddKeyTransRecipient(cert);
+    ///
+    ///      CmsEnvelopedData         data = fact.Generate(content, algorithm);
+    /// </pre>
+    /// </remarks>
+    public class CmsEnvelopedDataGenerator
+		: CmsEnvelopedGenerator
+    {
+		public CmsEnvelopedDataGenerator()
+        {
+        }
+
+		/// <summary>Constructor allowing specific source of randomness</summary>
+		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsEnvelopedDataGenerator(
+			SecureRandom rand)
+			: base(rand)
+		{
+		}
+
+		/// <summary>
+		/// Generate an enveloped object that contains a CMS Enveloped Data
+		/// object using the passed in key generator.
+		/// </summary>
+        private CmsEnvelopedData Generate(
+            CmsProcessable		content,
+            string				encryptionOid,
+            CipherKeyGenerator	keyGen)
+        {
+            AlgorithmIdentifier encAlgId = null;
+			KeyParameter encKey;
+            Asn1OctetString encContent;
+
+			try
+			{
+				byte[] encKeyBytes = keyGen.GenerateKey();
+				encKey = ParameterUtilities.CreateKeyParameter(encryptionOid, encKeyBytes);
+
+				Asn1Encodable asn1Params = GenerateAsn1Parameters(encryptionOid, encKeyBytes);
+
+				ICipherParameters cipherParameters;
+				encAlgId = GetAlgorithmIdentifier(
+					encryptionOid, encKey, asn1Params, out cipherParameters);
+
+				IBufferedCipher cipher = CipherUtilities.GetCipher(encryptionOid);
+				cipher.Init(true, new ParametersWithRandom(cipherParameters, rand));
+
+				MemoryStream bOut = new MemoryStream();
+				CipherStream cOut = new CipherStream(bOut, null, cipher);
+
+				content.Write(cOut);
+
+                cOut.Dispose();
+
+				encContent = new BerOctetString(bOut.ToArray());
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("exception decoding algorithm parameters.", e);
+			}
+
+
+			Asn1EncodableVector recipientInfos = new Asn1EncodableVector();
+
+            foreach (RecipientInfoGenerator rig in recipientInfoGenerators)
+            {
+                try
+                {
+                    recipientInfos.Add(rig.Generate(encKey, rand));
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new CmsException("key inappropriate for algorithm.", e);
+                }
+                catch (GeneralSecurityException e)
+                {
+                    throw new CmsException("error making encrypted content.", e);
+                }
+            }
+
+            EncryptedContentInfo eci = new EncryptedContentInfo(
+                CmsObjectIdentifiers.Data,
+                encAlgId,
+                encContent);
+
+			Asn1Set unprotectedAttrSet = null;
+            if (unprotectedAttributeGenerator != null)
+            {
+                Asn1.Cms.AttributeTable attrTable = unprotectedAttributeGenerator.GetAttributes(Platform.CreateHashtable());
+
+                unprotectedAttrSet = new BerSet(attrTable.ToAsn1EncodableVector());
+            }
+
+			ContentInfo contentInfo = new ContentInfo(
+                CmsObjectIdentifiers.EnvelopedData,
+                new EnvelopedData(null, new DerSet(recipientInfos), eci, unprotectedAttrSet));
+
+            return new CmsEnvelopedData(contentInfo);
+        }
+
+		/// <summary>Generate an enveloped object that contains an CMS Enveloped Data object.</summary>
+        public CmsEnvelopedData Generate(
+            CmsProcessable	content,
+            string			encryptionOid)
+        {
+            try
+            {
+				CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+				keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+
+				return Generate(content, encryptionOid, keyGen);
+            }
+            catch (SecurityUtilityException e)
+            {
+                throw new CmsException("can't find key generation algorithm.", e);
+            }
+        }
+
+		/// <summary>Generate an enveloped object that contains an CMS Enveloped Data object.</summary>
+        public CmsEnvelopedData Generate(
+            CmsProcessable  content,
+            string          encryptionOid,
+            int             keySize)
+        {
+            try
+            {
+				CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+				keyGen.Init(new KeyGenerationParameters(rand, keySize));
+
+				return Generate(content, encryptionOid, keyGen);
+            }
+            catch (SecurityUtilityException e)
+            {
+                throw new CmsException("can't find key generation algorithm.", e);
+            }
+        }
+    }
+}
diff --git a/Crypto/src/cms/CMSEnvelopedDataParser.cs b/Crypto/src/cms/CMSEnvelopedDataParser.cs
new file mode 100644
index 000000000..01a949d47
--- /dev/null
+++ b/Crypto/src/cms/CMSEnvelopedDataParser.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* Parsing class for an CMS Enveloped Data object from an input stream.
+	* <p>
+	* Note: that because we are in a streaming mode only one recipient can be tried and it is important
+	* that the methods on the parser are called in the appropriate order.
+	* </p>
+	* <p>
+	* Example of use - assuming the first recipient matches the private key we have.
+	* <pre>
+	*      CmsEnvelopedDataParser     ep = new CmsEnvelopedDataParser(inputStream);
+	*
+	*      RecipientInformationStore  recipients = ep.GetRecipientInfos();
+	*
+	*      Collection  c = recipients.getRecipients();
+	*      Iterator    it = c.iterator();
+	*
+	*      if (it.hasNext())
+	*      {
+	*          RecipientInformation   recipient = (RecipientInformation)it.next();
+	*
+	*          CMSTypedStream recData = recipient.getContentStream(privateKey);
+	*
+	*          processDataStream(recData.getContentStream());
+	*      }
+	*  </pre>
+	*  Note: this class does not introduce buffering - if you are processing large files you should create
+	*  the parser with:
+	*  <pre>
+	*          CmsEnvelopedDataParser     ep = new CmsEnvelopedDataParser(new BufferedInputStream(inputStream, bufSize));
+	*  </pre>
+	*  where bufSize is a suitably large buffer size.
+	* </p>
+	*/
+	public class CmsEnvelopedDataParser
+		: CmsContentInfoParser
+	{
+		internal RecipientInformationStore	recipientInfoStore;
+		internal EnvelopedDataParser		envelopedData;
+
+		private AlgorithmIdentifier			_encAlg;
+		private Asn1.Cms.AttributeTable		_unprotectedAttributes;
+		private bool						_attrNotRead;
+
+		public CmsEnvelopedDataParser(
+			byte[] envelopedData)
+			: this(new MemoryStream(envelopedData, false))
+		{
+		}
+
+		public CmsEnvelopedDataParser(
+			Stream envelopedData)
+			: base(envelopedData)
+		{
+			this._attrNotRead = true;
+			this.envelopedData = new EnvelopedDataParser(
+				(Asn1SequenceParser)this.contentInfo.GetContent(Asn1Tags.Sequence));
+
+			// TODO Validate version?
+			//DerInteger version = this.envelopedData.Version;
+
+			//
+			// read the recipients
+			//
+			Asn1Set recipientInfos = Asn1Set.GetInstance(this.envelopedData.GetRecipientInfos().ToAsn1Object());
+
+			//
+			// read the encrypted content info
+			//
+			EncryptedContentInfoParser encInfo = this.envelopedData.GetEncryptedContentInfo();
+			this._encAlg = encInfo.ContentEncryptionAlgorithm;
+			CmsReadable readable = new CmsProcessableInputStream(
+				((Asn1OctetStringParser)encInfo.GetEncryptedContent(Asn1Tags.OctetString)).GetOctetStream());
+			CmsSecureReadable secureReadable = new CmsEnvelopedHelper.CmsEnvelopedSecureReadable(
+				this._encAlg, readable);
+
+			//
+			// build the RecipientInformationStore
+			//
+			this.recipientInfoStore = CmsEnvelopedHelper.BuildRecipientInformationStore(
+				recipientInfos, secureReadable);
+		}
+
+		public AlgorithmIdentifier EncryptionAlgorithmID
+		{
+			get { return _encAlg; }
+		}
+
+		/**
+		 * return the object identifier for the content encryption algorithm.
+		 */
+		public string EncryptionAlgOid
+		{
+			get { return _encAlg.ObjectID.Id; }
+		}
+
+		/**
+		 * return the ASN.1 encoded encryption algorithm parameters, or null if
+		 * there aren't any.
+		 */
+		public Asn1Object EncryptionAlgParams
+		{
+			get
+			{
+				Asn1Encodable ae = _encAlg.Parameters;
+
+				return ae == null ? null : ae.ToAsn1Object();
+			}
+		}
+
+		/**
+		 * return a store of the intended recipients for this message
+		 */
+		public RecipientInformationStore GetRecipientInfos()
+		{
+			return this.recipientInfoStore;
+		}
+
+		/**
+		 * return a table of the unprotected attributes indexed by
+		 * the OID of the attribute.
+		 * @throws IOException
+		 */
+		public Asn1.Cms.AttributeTable GetUnprotectedAttributes()
+		{
+			if (_unprotectedAttributes == null && _attrNotRead)
+			{
+				Asn1SetParser asn1Set = this.envelopedData.GetUnprotectedAttrs();
+
+				_attrNotRead = false;
+
+				if (asn1Set != null)
+				{
+					Asn1EncodableVector v = new Asn1EncodableVector();
+					IAsn1Convertible o;
+
+					while ((o = asn1Set.ReadObject()) != null)
+					{
+						Asn1SequenceParser seq = (Asn1SequenceParser)o;
+
+						v.Add(seq.ToAsn1Object());
+					}
+
+					_unprotectedAttributes = new Asn1.Cms.AttributeTable(new DerSet(v));
+				}
+			}
+
+			return _unprotectedAttributes;
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs b/Crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
new file mode 100644
index 000000000..820ddfe16
--- /dev/null
+++ b/Crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
@@ -0,0 +1,285 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Generators;
+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.IO;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* General class for generating a CMS enveloped-data message stream.
+	* <p>
+	* A simple example of usage.
+	* <pre>
+	*      CmsEnvelopedDataStreamGenerator edGen = new CmsEnvelopedDataStreamGenerator();
+	*
+	*      edGen.AddKeyTransRecipient(cert);
+	*
+	*      MemoryStream  bOut = new MemoryStream();
+	*
+	*      Stream out = edGen.Open(
+	*                              bOut, CMSEnvelopedDataGenerator.AES128_CBC);*
+	*      out.Write(data);
+	*
+	*      out.Close();
+	* </pre>
+	* </p>
+	*/
+	public class CmsEnvelopedDataStreamGenerator
+		: CmsEnvelopedGenerator
+	{
+		private object	_originatorInfo = null;
+		private object	_unprotectedAttributes = null;
+		private int		_bufferSize;
+		private bool	_berEncodeRecipientSet;
+
+		public CmsEnvelopedDataStreamGenerator()
+		{
+		}
+
+		/// <summary>Constructor allowing specific source of randomness</summary>
+		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsEnvelopedDataStreamGenerator(
+			SecureRandom rand)
+			: base(rand)
+		{
+		}
+
+		/// <summary>Set the underlying string size for encapsulated data.</summary>
+		/// <param name="bufferSize">Length of octet strings to buffer the data.</param>
+		public void SetBufferSize(
+			int bufferSize)
+		{
+			_bufferSize = bufferSize;
+		}
+
+		/// <summary>Use a BER Set to store the recipient information.</summary>
+		public void SetBerEncodeRecipients(
+			bool berEncodeRecipientSet)
+		{
+			_berEncodeRecipientSet = berEncodeRecipientSet;
+		}
+
+		private DerInteger Version
+		{
+			get
+			{
+				int version = (_originatorInfo != null || _unprotectedAttributes != null)
+					?	2
+					:	0;
+
+				return new DerInteger(version);
+			}
+		}
+
+		/// <summary>
+		/// Generate an enveloped object that contains an CMS Enveloped Data
+		/// object using the passed in key generator.
+		/// </summary>
+		private Stream Open(
+			Stream				outStream,
+			string				encryptionOid,
+			CipherKeyGenerator	keyGen)
+		{
+			byte[] encKeyBytes = keyGen.GenerateKey();
+			KeyParameter encKey = ParameterUtilities.CreateKeyParameter(encryptionOid, encKeyBytes);
+
+			Asn1Encodable asn1Params = GenerateAsn1Parameters(encryptionOid, encKeyBytes);
+
+			ICipherParameters cipherParameters;
+			AlgorithmIdentifier encAlgID = GetAlgorithmIdentifier(
+				encryptionOid, encKey, asn1Params, out cipherParameters);
+
+			Asn1EncodableVector recipientInfos = new Asn1EncodableVector();
+
+			foreach (RecipientInfoGenerator rig in recipientInfoGenerators)
+			{
+				try
+				{
+					recipientInfos.Add(rig.Generate(encKey, rand));
+				}
+				catch (InvalidKeyException e)
+				{
+					throw new CmsException("key inappropriate for algorithm.", e);
+				}
+				catch (GeneralSecurityException e)
+				{
+					throw new CmsException("error making encrypted content.", e);
+				}
+			}
+
+			return Open(outStream, encAlgID, cipherParameters, recipientInfos);
+		}
+
+		private Stream Open(
+			Stream				outStream,
+			AlgorithmIdentifier	encAlgID,
+			ICipherParameters	cipherParameters,
+			Asn1EncodableVector	recipientInfos)
+		{
+			try
+			{
+				//
+				// ContentInfo
+				//
+				BerSequenceGenerator cGen = new BerSequenceGenerator(outStream);
+
+				cGen.AddObject(CmsObjectIdentifiers.EnvelopedData);
+
+				//
+				// Encrypted Data
+				//
+				BerSequenceGenerator envGen = new BerSequenceGenerator(
+					cGen.GetRawOutputStream(), 0, true);
+
+				envGen.AddObject(this.Version);
+
+				Stream envRaw = envGen.GetRawOutputStream();
+				Asn1Generator recipGen = _berEncodeRecipientSet
+					?	(Asn1Generator) new BerSetGenerator(envRaw)
+					:	new DerSetGenerator(envRaw);
+
+				foreach (Asn1Encodable ae in recipientInfos)
+				{
+					recipGen.AddObject(ae);
+				}
+
+				recipGen.Close();
+
+				BerSequenceGenerator eiGen = new BerSequenceGenerator(envRaw);
+				eiGen.AddObject(CmsObjectIdentifiers.Data);
+				eiGen.AddObject(encAlgID);
+
+				Stream octetOutputStream = CmsUtilities.CreateBerOctetOutputStream(
+					eiGen.GetRawOutputStream(), 0, false, _bufferSize);
+
+				IBufferedCipher cipher = CipherUtilities.GetCipher(encAlgID.ObjectID);
+				cipher.Init(true, new ParametersWithRandom(cipherParameters, rand));
+				CipherStream cOut = new CipherStream(octetOutputStream, null, cipher);
+
+				return new CmsEnvelopedDataOutputStream(this, cOut, cGen, envGen, eiGen);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("exception decoding algorithm parameters.", e);
+			}
+		}
+
+		/**
+		* generate an enveloped object that contains an CMS Enveloped Data object
+		* @throws IOException
+		*/
+		public Stream Open(
+			Stream	outStream,
+			string	encryptionOid)
+		{
+			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+			keyGen.Init(new KeyGenerationParameters(rand, keyGen.DefaultStrength));
+
+			return Open(outStream, encryptionOid, keyGen);
+		}
+
+		/**
+		* generate an enveloped object that contains an CMS Enveloped Data object
+		* @throws IOException
+		*/
+		public Stream Open(
+			Stream	outStream,
+			string	encryptionOid,
+			int		keySize)
+		{
+			CipherKeyGenerator keyGen = GeneratorUtilities.GetKeyGenerator(encryptionOid);
+
+			keyGen.Init(new KeyGenerationParameters(rand, keySize));
+
+			return Open(outStream, encryptionOid, keyGen);
+		}
+
+		private class CmsEnvelopedDataOutputStream
+			: BaseOutputStream
+		{
+            private readonly CmsEnvelopedGenerator _outer;
+
+			private readonly CipherStream			_out;
+			private readonly BerSequenceGenerator	_cGen;
+			private readonly BerSequenceGenerator	_envGen;
+			private readonly BerSequenceGenerator	_eiGen;
+
+			public CmsEnvelopedDataOutputStream(
+				CmsEnvelopedGenerator	outer,
+				CipherStream			outStream,
+				BerSequenceGenerator	cGen,
+				BerSequenceGenerator	envGen,
+				BerSequenceGenerator	eiGen)
+			{
+				_outer = outer;
+				_out = outStream;
+				_cGen = cGen;
+				_envGen = envGen;
+				_eiGen = eiGen;
+			}
+
+			public override void WriteByte(
+				byte b)
+			{
+				_out.WriteByte(b);
+			}
+
+			public override void Write(
+				byte[]	bytes,
+				int		off,
+				int		len)
+			{
+				_out.Write(bytes, off, len);
+			}
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    _out.Dispose();
+
+                    // TODO Parent context(s) should really be be closed explicitly
+
+                    _eiGen.Close();
+
+                    if (_outer.unprotectedAttributeGenerator != null)
+                    {
+                        Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(Platform.CreateHashtable());
+
+                        Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector());
+
+                        _envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs));
+                    }
+
+                    _envGen.Close();
+                    _cGen.Close();
+                }
+
+                base.Dispose(disposing);
+            }
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSEnvelopedGenerator.cs b/Crypto/src/cms/CMSEnvelopedGenerator.cs
new file mode 100644
index 000000000..f92ae3824
--- /dev/null
+++ b/Crypto/src/cms/CMSEnvelopedGenerator.cs
@@ -0,0 +1,331 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Kisa;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Ntt;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* General class for generating a CMS enveloped-data message.
+	*
+	* A simple example of usage.
+	*
+	* <pre>
+	*      CMSEnvelopedDataGenerator  fact = new CMSEnvelopedDataGenerator();
+	*
+	*      fact.addKeyTransRecipient(cert);
+	*
+	*      CMSEnvelopedData         data = fact.generate(content, algorithm, "BC");
+	* </pre>
+	*/
+	public class CmsEnvelopedGenerator
+	{
+		// Note: These tables are complementary: If rc2Table[i]==j, then rc2Ekb[j]==i
+		internal static readonly short[] rc2Table =
+		{
+			0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0,
+			0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a,
+			0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
+			0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c,
+			0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60,
+			0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
+			0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e,
+			0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf,
+			0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
+			0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3,
+			0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c,
+			0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
+			0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5,
+			0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5,
+			0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
+			0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab
+		};
+
+//		internal static readonly short[] rc2Ekb =
+//		{
+//			0x5d, 0xbe, 0x9b, 0x8b, 0x11, 0x99, 0x6e, 0x4d, 0x59, 0xf3, 0x85, 0xa6, 0x3f, 0xb7, 0x83, 0xc5,
+//			0xe4, 0x73, 0x6b, 0x3a, 0x68, 0x5a, 0xc0, 0x47, 0xa0, 0x64, 0x34, 0x0c, 0xf1, 0xd0, 0x52, 0xa5,
+//			0xb9, 0x1e, 0x96, 0x43, 0x41, 0xd8, 0xd4, 0x2c, 0xdb, 0xf8, 0x07, 0x77, 0x2a, 0xca, 0xeb, 0xef,
+//			0x10, 0x1c, 0x16, 0x0d, 0x38, 0x72, 0x2f, 0x89, 0xc1, 0xf9, 0x80, 0xc4, 0x6d, 0xae, 0x30, 0x3d,
+//			0xce, 0x20, 0x63, 0xfe, 0xe6, 0x1a, 0xc7, 0xb8, 0x50, 0xe8, 0x24, 0x17, 0xfc, 0x25, 0x6f, 0xbb,
+//			0x6a, 0xa3, 0x44, 0x53, 0xd9, 0xa2, 0x01, 0xab, 0xbc, 0xb6, 0x1f, 0x98, 0xee, 0x9a, 0xa7, 0x2d,
+//			0x4f, 0x9e, 0x8e, 0xac, 0xe0, 0xc6, 0x49, 0x46, 0x29, 0xf4, 0x94, 0x8a, 0xaf, 0xe1, 0x5b, 0xc3,
+//			0xb3, 0x7b, 0x57, 0xd1, 0x7c, 0x9c, 0xed, 0x87, 0x40, 0x8c, 0xe2, 0xcb, 0x93, 0x14, 0xc9, 0x61,
+//			0x2e, 0xe5, 0xcc, 0xf6, 0x5e, 0xa8, 0x5c, 0xd6, 0x75, 0x8d, 0x62, 0x95, 0x58, 0x69, 0x76, 0xa1,
+//			0x4a, 0xb5, 0x55, 0x09, 0x78, 0x33, 0x82, 0xd7, 0xdd, 0x79, 0xf5, 0x1b, 0x0b, 0xde, 0x26, 0x21,
+//			0x28, 0x74, 0x04, 0x97, 0x56, 0xdf, 0x3c, 0xf0, 0x37, 0x39, 0xdc, 0xff, 0x06, 0xa4, 0xea, 0x42,
+//			0x08, 0xda, 0xb4, 0x71, 0xb0, 0xcf, 0x12, 0x7a, 0x4e, 0xfa, 0x6c, 0x1d, 0x84, 0x00, 0xc8, 0x7f,
+//			0x91, 0x45, 0xaa, 0x2b, 0xc2, 0xb1, 0x8f, 0xd5, 0xba, 0xf2, 0xad, 0x19, 0xb2, 0x67, 0x36, 0xf7,
+//			0x0f, 0x0a, 0x92, 0x7d, 0xe3, 0x9d, 0xe9, 0x90, 0x3e, 0x23, 0x27, 0x66, 0x13, 0xec, 0x81, 0x15,
+//			0xbd, 0x22, 0xbf, 0x9f, 0x7e, 0xa9, 0x51, 0x4b, 0x4c, 0xfb, 0x02, 0xd3, 0x70, 0x86, 0x31, 0xe7,
+//			0x3b, 0x05, 0x03, 0x54, 0x60, 0x48, 0x65, 0x18, 0xd2, 0xcd, 0x5f, 0x32, 0x88, 0x0e, 0x35, 0xfd
+//		};
+
+
+		// TODO Create named constants for all of these
+		public static readonly string DesEde3Cbc		= PkcsObjectIdentifiers.DesEde3Cbc.Id;
+		public static readonly string RC2Cbc			= PkcsObjectIdentifiers.RC2Cbc.Id;
+		public const string IdeaCbc						= "1.3.6.1.4.1.188.7.1.1.2";
+		public const string Cast5Cbc					= "1.2.840.113533.7.66.10";
+		public static readonly string Aes128Cbc			= NistObjectIdentifiers.IdAes128Cbc.Id;
+		public static readonly string Aes192Cbc			= NistObjectIdentifiers.IdAes192Cbc.Id;
+		public static readonly string Aes256Cbc			= NistObjectIdentifiers.IdAes256Cbc.Id;
+		public static readonly string Camellia128Cbc	= NttObjectIdentifiers.IdCamellia128Cbc.Id;
+		public static readonly string Camellia192Cbc	= NttObjectIdentifiers.IdCamellia192Cbc.Id;
+		public static readonly string Camellia256Cbc	= NttObjectIdentifiers.IdCamellia256Cbc.Id;
+		public static readonly string SeedCbc			= KisaObjectIdentifiers.IdSeedCbc.Id;
+
+		public static readonly string DesEde3Wrap		= PkcsObjectIdentifiers.IdAlgCms3DesWrap.Id;
+		public static readonly string Aes128Wrap		= NistObjectIdentifiers.IdAes128Wrap.Id;
+		public static readonly string Aes192Wrap		= NistObjectIdentifiers.IdAes192Wrap.Id;
+		public static readonly string Aes256Wrap		= NistObjectIdentifiers.IdAes256Wrap.Id;
+		public static readonly string Camellia128Wrap	= NttObjectIdentifiers.IdCamellia128Wrap.Id;
+		public static readonly string Camellia192Wrap	= NttObjectIdentifiers.IdCamellia192Wrap.Id;
+		public static readonly string Camellia256Wrap	= NttObjectIdentifiers.IdCamellia256Wrap.Id;
+		public static readonly string SeedWrap			= KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap.Id;
+
+		public static readonly string ECDHSha1Kdf		= X9ObjectIdentifiers.DHSinglePassStdDHSha1KdfScheme.Id;
+		public static readonly string ECMqvSha1Kdf		= X9ObjectIdentifiers.MqvSinglePassSha1KdfScheme.Id;
+
+		internal readonly IList recipientInfoGenerators = Platform.CreateArrayList();
+		internal readonly SecureRandom rand;
+
+        internal CmsAttributeTableGenerator unprotectedAttributeGenerator = null;
+
+		public CmsEnvelopedGenerator()
+			: this(new SecureRandom())
+		{
+		}
+
+		/// <summary>Constructor allowing specific source of randomness</summary>
+		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsEnvelopedGenerator(
+			SecureRandom rand)
+		{
+			this.rand = rand;
+		}
+
+        public CmsAttributeTableGenerator UnprotectedAttributeGenerator
+        {
+            get { return this.unprotectedAttributeGenerator; }
+            set { this.unprotectedAttributeGenerator = value; }
+        }
+
+		/**
+		 * add a recipient.
+		 *
+		 * @param cert recipient's public key certificate
+		 * @exception ArgumentException if there is a problem with the certificate
+		 */
+		public void AddKeyTransRecipient(
+			X509Certificate cert)
+		{
+			KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
+			ktrig.RecipientCert = cert;
+
+			recipientInfoGenerators.Add(ktrig);
+		}
+
+		/**
+		* add a recipient
+		*
+		* @param key the public key used by the recipient
+		* @param subKeyId the identifier for the recipient's public key
+		* @exception ArgumentException if there is a problem with the key
+		*/
+		public void AddKeyTransRecipient(
+			AsymmetricKeyParameter	pubKey,
+			byte[]					subKeyId)
+		{
+			KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
+			ktrig.RecipientPublicKey = pubKey;
+			ktrig.SubjectKeyIdentifier = new DerOctetString(subKeyId);
+
+			recipientInfoGenerators.Add(ktrig);
+		}
+
+		/**
+		 * add a KEK recipient.
+		 * @param key the secret key to use for wrapping
+		 * @param keyIdentifier the byte string that identifies the key
+		 */
+		public void AddKekRecipient(
+			string			keyAlgorithm, // TODO Remove need for this parameter
+			KeyParameter	key,
+			byte[]			keyIdentifier)
+		{
+			AddKekRecipient(keyAlgorithm, key, new KekIdentifier(keyIdentifier, null, null));
+		}
+
+		/**
+		* add a KEK recipient.
+		* @param key the secret key to use for wrapping
+		* @param keyIdentifier the byte string that identifies the key
+		*/
+		public void AddKekRecipient(
+			string			keyAlgorithm, // TODO Remove need for this parameter
+			KeyParameter	key,
+			KekIdentifier	kekIdentifier)
+		{
+			KekRecipientInfoGenerator kekrig = new KekRecipientInfoGenerator();
+			kekrig.KekIdentifier = kekIdentifier;
+			kekrig.KeyEncryptionKeyOID = keyAlgorithm;
+			kekrig.KeyEncryptionKey = key;
+
+			recipientInfoGenerators.Add(kekrig);
+		}
+
+		public void AddPasswordRecipient(
+			CmsPbeKey	pbeKey,
+			string		kekAlgorithmOid)
+		{
+			Pbkdf2Params p = new Pbkdf2Params(pbeKey.Salt, pbeKey.IterationCount);
+
+			PasswordRecipientInfoGenerator prig = new PasswordRecipientInfoGenerator();
+			prig.KeyDerivationAlgorithm = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdPbkdf2, p);
+			prig.KeyEncryptionKeyOID = kekAlgorithmOid;
+			prig.KeyEncryptionKey = pbeKey.GetEncoded(kekAlgorithmOid);
+
+			recipientInfoGenerators.Add(prig);
+		}
+
+		/**
+		* Add a key agreement based recipient.
+		*
+		* @param agreementAlgorithm key agreement algorithm to use.
+		* @param senderPrivateKey private key to initialise sender side of agreement with.
+		* @param senderPublicKey sender public key to include with message.
+		* @param recipientCert recipient's public key certificate.
+		* @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+		* @exception SecurityUtilityException if the algorithm requested cannot be found
+		* @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+		*/
+		public void AddKeyAgreementRecipient(
+			string					agreementAlgorithm,
+			AsymmetricKeyParameter	senderPrivateKey,
+			AsymmetricKeyParameter	senderPublicKey,
+			X509Certificate			recipientCert,
+			string					cekWrapAlgorithm)
+		{
+            IList recipientCerts = Platform.CreateArrayList(1);
+			recipientCerts.Add(recipientCert);
+
+			AddKeyAgreementRecipients(agreementAlgorithm, senderPrivateKey, senderPublicKey,
+				recipientCerts, cekWrapAlgorithm);
+		}
+
+		/**
+		 * Add multiple key agreement based recipients (sharing a single KeyAgreeRecipientInfo structure).
+		 *
+		 * @param agreementAlgorithm key agreement algorithm to use.
+		 * @param senderPrivateKey private key to initialise sender side of agreement with.
+		 * @param senderPublicKey sender public key to include with message.
+		 * @param recipientCerts recipients' public key certificates.
+		 * @param cekWrapAlgorithm OID for key wrapping algorithm to use.
+		 * @exception SecurityUtilityException if the algorithm requested cannot be found
+		 * @exception InvalidKeyException if the keys are inappropriate for the algorithm specified
+		 */
+		public void AddKeyAgreementRecipients(
+			string					agreementAlgorithm,
+			AsymmetricKeyParameter	senderPrivateKey,
+			AsymmetricKeyParameter	senderPublicKey,
+			ICollection				recipientCerts,
+			string					cekWrapAlgorithm)
+		{
+			if (!senderPrivateKey.IsPrivate)
+				throw new ArgumentException("Expected private key", "senderPrivateKey");
+			if (senderPublicKey.IsPrivate)
+				throw new ArgumentException("Expected public key", "senderPublicKey");
+
+			/* TODO
+			 * "a recipient X.509 version 3 certificate that contains a key usage extension MUST
+			 * assert the keyAgreement bit."
+			 */
+
+			KeyAgreeRecipientInfoGenerator karig = new KeyAgreeRecipientInfoGenerator();
+			karig.KeyAgreementOID = new DerObjectIdentifier(agreementAlgorithm);
+			karig.KeyEncryptionOID = new DerObjectIdentifier(cekWrapAlgorithm);
+			karig.RecipientCerts = recipientCerts;
+			karig.SenderKeyPair = new AsymmetricCipherKeyPair(senderPublicKey, senderPrivateKey);
+
+			recipientInfoGenerators.Add(karig);
+		}
+
+        protected internal virtual AlgorithmIdentifier GetAlgorithmIdentifier(
+			string					encryptionOid,
+			KeyParameter			encKey,
+			Asn1Encodable			asn1Params,
+			out ICipherParameters	cipherParameters)
+		{
+			Asn1Object asn1Object;
+			if (asn1Params != null)
+			{
+				asn1Object = asn1Params.ToAsn1Object();
+				cipherParameters = ParameterUtilities.GetCipherParameters(
+					encryptionOid, encKey, asn1Object);
+			}
+			else
+			{
+				asn1Object = DerNull.Instance;
+				cipherParameters = encKey;
+			}
+
+			return new AlgorithmIdentifier(
+				new DerObjectIdentifier(encryptionOid),
+				asn1Object);
+		}
+
+		protected internal virtual Asn1Encodable GenerateAsn1Parameters(
+			string	encryptionOid,
+			byte[]	encKeyBytes)
+		{
+			Asn1Encodable asn1Params = null;
+
+			try
+			{
+				if (encryptionOid.Equals(RC2Cbc))
+				{
+					byte[] iv = new byte[8];
+					rand.NextBytes(iv);
+
+					// TODO Is this detailed repeat of Java version really necessary?
+					int effKeyBits = encKeyBytes.Length * 8;
+					int parameterVersion;
+
+					if (effKeyBits < 256)
+					{
+						parameterVersion = rc2Table[effKeyBits];
+					}
+					else
+					{
+						parameterVersion = effKeyBits;
+					}
+
+					asn1Params = new RC2CbcParameter(parameterVersion, iv);
+				}
+				else
+				{
+					asn1Params = ParameterUtilities.GenerateParameters(encryptionOid, rand);
+				}
+			}
+			catch (SecurityUtilityException)
+			{
+				// No problem... no parameters generated
+			}
+
+			return asn1Params;
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSEnvelopedHelper.cs b/Crypto/src/cms/CMSEnvelopedHelper.cs
new file mode 100644
index 000000000..fe2b14cd9
--- /dev/null
+++ b/Crypto/src/cms/CMSEnvelopedHelper.cs
@@ -0,0 +1,311 @@
+using System;
+using System.Collections;
+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.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	class CmsEnvelopedHelper
+	{
+		internal static readonly CmsEnvelopedHelper Instance = new CmsEnvelopedHelper();
+
+		private static readonly IDictionary KeySizes = Platform.CreateHashtable();
+		private static readonly IDictionary BaseCipherNames = Platform.CreateHashtable();
+
+		static CmsEnvelopedHelper()
+		{
+			KeySizes.Add(CmsEnvelopedGenerator.DesEde3Cbc, 192);
+			KeySizes.Add(CmsEnvelopedGenerator.Aes128Cbc, 128);
+			KeySizes.Add(CmsEnvelopedGenerator.Aes192Cbc, 192);
+			KeySizes.Add(CmsEnvelopedGenerator.Aes256Cbc, 256);
+
+			BaseCipherNames.Add(CmsEnvelopedGenerator.DesEde3Cbc,  "DESEDE");
+			BaseCipherNames.Add(CmsEnvelopedGenerator.Aes128Cbc,  "AES");
+			BaseCipherNames.Add(CmsEnvelopedGenerator.Aes192Cbc,  "AES");
+			BaseCipherNames.Add(CmsEnvelopedGenerator.Aes256Cbc,  "AES");
+		}
+
+		private string GetAsymmetricEncryptionAlgName(
+			string encryptionAlgOid)
+		{
+			if (Asn1.Pkcs.PkcsObjectIdentifiers.RsaEncryption.Id.Equals(encryptionAlgOid))
+			{
+				return "RSA/ECB/PKCS1Padding";
+			}
+
+			return encryptionAlgOid;
+		}
+
+		internal IBufferedCipher CreateAsymmetricCipher(
+			string encryptionOid)
+		{
+			string asymName = GetAsymmetricEncryptionAlgName(encryptionOid);
+			if (!asymName.Equals(encryptionOid))
+			{
+				try
+				{
+					return CipherUtilities.GetCipher(asymName);
+				}
+				catch (SecurityUtilityException)
+				{
+					// Ignore
+				}
+			}
+			return CipherUtilities.GetCipher(encryptionOid);
+		}
+
+		internal IWrapper CreateWrapper(
+			string encryptionOid)
+		{
+			try
+			{
+				return WrapperUtilities.GetWrapper(encryptionOid);
+			}
+			catch (SecurityUtilityException)
+			{
+				return WrapperUtilities.GetWrapper(GetAsymmetricEncryptionAlgName(encryptionOid));
+			}
+		}
+
+		internal string GetRfc3211WrapperName(
+			string oid)
+		{
+			if (oid == null)
+				throw new ArgumentNullException("oid");
+
+			string alg = (string) BaseCipherNames[oid];
+
+			if (alg == null)
+				throw new ArgumentException("no name for " + oid, "oid");
+
+			return alg + "RFC3211Wrap";
+		}
+
+		internal int GetKeySize(
+			string oid)
+		{
+			if (!KeySizes.Contains(oid))
+			{
+				throw new ArgumentException("no keysize for " + oid, "oid");
+			}
+
+			return (int) KeySizes[oid];
+		}
+
+		internal static RecipientInformationStore BuildRecipientInformationStore(
+			Asn1Set recipientInfos, CmsSecureReadable secureReadable)
+		{
+			IList infos = Platform.CreateArrayList();
+			for (int i = 0; i != recipientInfos.Count; i++)
+			{
+				RecipientInfo info = RecipientInfo.GetInstance(recipientInfos[i]);
+
+				ReadRecipientInfo(infos, info, secureReadable);
+			}
+			return new RecipientInformationStore(infos);
+		}
+
+		private static void ReadRecipientInfo(
+			IList infos, RecipientInfo info, CmsSecureReadable secureReadable)
+		{
+			Asn1Encodable recipInfo = info.Info;
+			if (recipInfo is KeyTransRecipientInfo)
+			{
+				infos.Add(new KeyTransRecipientInformation((KeyTransRecipientInfo)recipInfo, secureReadable));
+			}
+			else if (recipInfo is KekRecipientInfo)
+			{
+				infos.Add(new KekRecipientInformation((KekRecipientInfo)recipInfo, secureReadable));
+			}
+			else if (recipInfo is KeyAgreeRecipientInfo)
+			{
+				KeyAgreeRecipientInformation.ReadRecipientInfo(infos, (KeyAgreeRecipientInfo)recipInfo, secureReadable);
+			}
+			else if (recipInfo is PasswordRecipientInfo)
+			{
+				infos.Add(new PasswordRecipientInformation((PasswordRecipientInfo)recipInfo, secureReadable));
+			}
+		}
+
+		internal class CmsAuthenticatedSecureReadable : CmsSecureReadable
+		{
+			private AlgorithmIdentifier algorithm;
+			private IMac mac;
+			private CmsReadable readable;
+
+			internal CmsAuthenticatedSecureReadable(AlgorithmIdentifier algorithm, CmsReadable readable)
+			{
+				this.algorithm = algorithm;
+				this.readable = readable;
+			}
+
+			public AlgorithmIdentifier Algorithm
+			{
+				get { return this.algorithm; }
+			}
+
+			public object CryptoObject
+			{
+				get { return this.mac; }
+			}
+
+			public CmsReadable GetReadable(KeyParameter sKey)
+			{
+				string macAlg = this.algorithm.ObjectID.Id;
+//				Asn1Object sParams = this.algorithm.Parameters.ToAsn1Object();
+
+				try
+				{
+					this.mac = MacUtilities.GetMac(macAlg);
+
+					// FIXME Support for MAC algorithm parameters similar to cipher parameters
+//						ASN1Object sParams = (ASN1Object)macAlg.getParameters();
+//
+//						if (sParams != null && !(sParams instanceof ASN1Null))
+//						{
+//							AlgorithmParameters params = CMSEnvelopedHelper.INSTANCE.createAlgorithmParameters(macAlg.getObjectId().getId(), provider);
+//
+//							params.init(sParams.getEncoded(), "ASN.1");
+//
+//							mac.init(sKey, params.getParameterSpec(IvParameterSpec.class));
+//						}
+//						else
+					{
+						mac.Init(sKey);
+					}
+
+//						Asn1Object asn1Params = asn1Enc == null ? null : asn1Enc.ToAsn1Object();
+//
+//						ICipherParameters cipherParameters = sKey;
+//
+//						if (asn1Params != null && !(asn1Params is Asn1Null))
+//						{
+//							cipherParameters = ParameterUtilities.GetCipherParameters(
+//							macAlg.ObjectID, cipherParameters, asn1Params);
+//						}
+//						else
+//						{
+//							string alg = macAlg.ObjectID.Id;
+//							if (alg.Equals(CmsEnvelopedDataGenerator.DesEde3Cbc)
+//								|| alg.Equals(CmsEnvelopedDataGenerator.IdeaCbc)
+//								|| alg.Equals(CmsEnvelopedDataGenerator.Cast5Cbc))
+//							{
+//								cipherParameters = new ParametersWithIV(cipherParameters, new byte[8]);
+//							}
+//						}
+//
+//						mac.Init(cipherParameters);
+				}
+				catch (SecurityUtilityException e)
+				{
+					throw new CmsException("couldn't create cipher.", e);
+				}
+				catch (InvalidKeyException e)
+				{
+					throw new CmsException("key invalid in message.", e);
+				}
+				catch (IOException e)
+				{
+					throw new CmsException("error decoding algorithm parameters.", e);
+				}
+
+				try
+				{
+					return new CmsProcessableInputStream(
+						new TeeInputStream(
+							readable.GetInputStream(),
+							new MacOutputStream(this.mac)));
+				}
+				catch (IOException e)
+				{
+					throw new CmsException("error reading content.", e);
+				}
+			}
+		}
+
+		internal class CmsEnvelopedSecureReadable : CmsSecureReadable
+		{
+			private AlgorithmIdentifier algorithm;
+			private IBufferedCipher cipher;
+			private CmsReadable readable;
+
+			internal CmsEnvelopedSecureReadable(AlgorithmIdentifier algorithm, CmsReadable readable)
+			{
+				this.algorithm = algorithm;
+				this.readable = readable;
+			}
+
+			public AlgorithmIdentifier Algorithm
+			{
+				get { return this.algorithm; }
+			}
+
+			public object CryptoObject
+			{
+				get { return this.cipher; }
+			}
+
+			public CmsReadable GetReadable(KeyParameter sKey)
+			{
+				try
+				{
+					this.cipher =  CipherUtilities.GetCipher(this.algorithm.ObjectID);
+
+					Asn1Encodable asn1Enc = this.algorithm.Parameters;
+					Asn1Object asn1Params = asn1Enc == null ? null : asn1Enc.ToAsn1Object();
+
+					ICipherParameters cipherParameters = sKey;
+
+					if (asn1Params != null && !(asn1Params is Asn1Null))
+					{
+						cipherParameters = ParameterUtilities.GetCipherParameters(
+							this.algorithm.ObjectID, cipherParameters, asn1Params);
+					}
+					else
+					{
+						string alg = this.algorithm.ObjectID.Id;
+						if (alg.Equals(CmsEnvelopedDataGenerator.DesEde3Cbc)
+							|| alg.Equals(CmsEnvelopedDataGenerator.IdeaCbc)
+							|| alg.Equals(CmsEnvelopedDataGenerator.Cast5Cbc))
+						{
+							cipherParameters = new ParametersWithIV(cipherParameters, new byte[8]);
+						}
+					}
+
+					cipher.Init(false, cipherParameters);
+				}
+				catch (SecurityUtilityException e)
+				{
+					throw new CmsException("couldn't create cipher.", e);
+				}
+				catch (InvalidKeyException e)
+				{
+					throw new CmsException("key invalid in message.", e);
+				}
+				catch (IOException e)
+				{
+					throw new CmsException("error decoding algorithm parameters.", e);
+				}
+
+				try
+				{
+					return new CmsProcessableInputStream(
+						new CipherStream(readable.GetInputStream(), cipher, null));
+				}
+				catch (IOException e)
+				{
+					throw new CmsException("error reading content.", e);
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/Crypto/src/cms/CMSException.cs b/Crypto/src/cms/CMSException.cs
new file mode 100644
index 000000000..1d1cd82e3
--- /dev/null
+++ b/Crypto/src/cms/CMSException.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Org.BouncyCastle.Cms
+{
+		public class CmsException
+			: Exception
+		{
+		public CmsException()
+		{
+		}
+
+		public CmsException(
+			string msg)
+			: base(msg)
+		{
+		}
+
+		public CmsException(
+			string		msg,
+			Exception	e)
+			: base(msg, e)
+		{
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSPBEKey.cs b/Crypto/src/cms/CMSPBEKey.cs
new file mode 100644
index 000000000..cb1e54c36
--- /dev/null
+++ b/Crypto/src/cms/CMSPBEKey.cs
@@ -0,0 +1,109 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+//import javax.crypto.interfaces.PBEKey;
+
+namespace Org.BouncyCastle.Cms
+{
+	public abstract class CmsPbeKey
+		// TODO Create an equivalent interface somewhere?
+		//	: PBEKey
+		: ICipherParameters
+	{
+		internal readonly char[]	password;
+		internal readonly byte[]	salt;
+		internal readonly int		iterationCount;
+
+		[Obsolete("Use version taking 'char[]' instead")]
+		public CmsPbeKey(
+			string	password,
+			byte[]	salt,
+			int		iterationCount)
+			: this(password.ToCharArray(), salt, iterationCount)
+		{
+		}
+
+		[Obsolete("Use version taking 'char[]' instead")]
+		public CmsPbeKey(
+			string				password,
+			AlgorithmIdentifier keyDerivationAlgorithm)
+			: this(password.ToCharArray(), keyDerivationAlgorithm)
+		{
+		}
+		
+		public CmsPbeKey(
+			char[]	password,
+			byte[]	salt,
+			int		iterationCount)
+		{
+			this.password = (char[])password.Clone();
+			this.salt = Arrays.Clone(salt);
+			this.iterationCount = iterationCount;
+		}
+
+		public CmsPbeKey(
+			char[]				password,
+			AlgorithmIdentifier keyDerivationAlgorithm)
+		{
+			if (!keyDerivationAlgorithm.ObjectID.Equals(PkcsObjectIdentifiers.IdPbkdf2))
+				throw new ArgumentException("Unsupported key derivation algorithm: "
+					+ keyDerivationAlgorithm.ObjectID);
+
+			Pbkdf2Params kdfParams = Pbkdf2Params.GetInstance(
+				keyDerivationAlgorithm.Parameters.ToAsn1Object());
+
+			this.password = (char[])password.Clone();
+			this.salt = kdfParams.GetSalt();
+			this.iterationCount = kdfParams.IterationCount.IntValue;
+		}
+
+		~CmsPbeKey()
+		{
+			Array.Clear(this.password, 0, this.password.Length);
+		}
+
+		[Obsolete("Will be removed")]
+		public string Password
+		{
+			get { return new string(password); }
+		}
+
+		public byte[] Salt
+		{
+			get { return Arrays.Clone(salt); }
+		}
+
+		[Obsolete("Use 'Salt' property instead")]
+		public byte[] GetSalt()
+		{
+			return Salt;
+		}
+
+		public int IterationCount
+		{
+			get { return iterationCount; }
+		}
+
+		public string Algorithm
+		{
+			get { return "PKCS5S2"; }
+		}
+
+		public string Format
+		{
+			get { return "RAW"; }
+		}
+
+		public byte[] GetEncoded()
+		{
+			return null;
+		}
+
+		internal abstract KeyParameter GetEncoded(string algorithmOid);
+	}
+}
diff --git a/Crypto/src/cms/CMSProcessable.cs b/Crypto/src/cms/CMSProcessable.cs
new file mode 100644
index 000000000..41018d12b
--- /dev/null
+++ b/Crypto/src/cms/CMSProcessable.cs
@@ -0,0 +1,19 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	public interface CmsProcessable
+	{
+		/// <summary>
+		/// Generic routine to copy out the data we want processed.
+		/// </summary>
+		/// <remarks>
+		/// This routine may be called multiple times.
+		/// </remarks>
+		void Write(Stream outStream);
+
+		[Obsolete]
+		object GetContent();
+	}
+}
diff --git a/Crypto/src/cms/CMSProcessableByteArray.cs b/Crypto/src/cms/CMSProcessableByteArray.cs
new file mode 100644
index 000000000..4bee4f8bd
--- /dev/null
+++ b/Crypto/src/cms/CMSProcessableByteArray.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* a holding class for a byte array of data to be processed.
+	*/
+	public class CmsProcessableByteArray
+		: CmsProcessable, CmsReadable
+	{
+		private readonly byte[] bytes;
+
+		public CmsProcessableByteArray(
+			byte[] bytes)
+		{
+			this.bytes = bytes;
+		}
+
+		public Stream GetInputStream()
+		{
+			return new MemoryStream(bytes, false);
+		}
+
+		public virtual void Write(Stream zOut)
+		{
+			zOut.Write(bytes, 0, bytes.Length);
+		}
+
+		/// <returns>A clone of the byte array</returns>
+		public virtual object GetContent()
+		{
+			return bytes.Clone();
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSProcessableFile.cs b/Crypto/src/cms/CMSProcessableFile.cs
new file mode 100644
index 000000000..548dc8efb
--- /dev/null
+++ b/Crypto/src/cms/CMSProcessableFile.cs
@@ -0,0 +1,57 @@
+#if !PORTABLE
+
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* a holding class for a file of data to be processed.
+	*/
+	public class CmsProcessableFile
+		: CmsProcessable, CmsReadable
+	{
+		private const int DefaultBufSize = 32 * 1024;
+
+		private readonly FileInfo	_file;
+		private readonly int		_bufSize;
+
+		public CmsProcessableFile(
+			FileInfo file)
+			: this(file, DefaultBufSize)
+		{
+		}
+
+		public CmsProcessableFile(
+			FileInfo	file,
+			int			bufSize)
+		{
+			_file = file;
+			_bufSize = bufSize;
+		}
+
+		public virtual Stream GetInputStream()
+		{
+			return new FileStream(
+				_file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read, _bufSize);
+		}
+
+		public virtual void Write(
+			Stream zOut)
+		{
+			Stream inStr = GetInputStream();
+			Streams.PipeAll(inStr, zOut);
+			inStr.Close();
+		}
+
+		/// <returns>The file handle</returns>
+		[Obsolete]
+		public virtual object GetContent()
+		{
+			return _file;
+		}
+	}
+}
+#endif
\ No newline at end of file
diff --git a/Crypto/src/cms/CMSProcessableInputStream.cs b/Crypto/src/cms/CMSProcessableInputStream.cs
new file mode 100644
index 000000000..d10d059c6
--- /dev/null
+++ b/Crypto/src/cms/CMSProcessableInputStream.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	public class CmsProcessableInputStream
+		: CmsProcessable, CmsReadable
+	{
+		private Stream input;
+		private bool used = false;
+
+		public CmsProcessableInputStream(
+			Stream input)
+		{
+			this.input = input;
+		}
+
+		public Stream GetInputStream()
+		{
+			CheckSingleUsage();
+
+			return input;
+		}
+
+		public void Write(Stream output)
+		{
+			CheckSingleUsage();
+
+			Streams.PipeAll(input, output);
+			input.Dispose();
+		}
+
+		[Obsolete]
+		public object GetContent()
+		{
+			return GetInputStream();
+		}
+
+		private void CheckSingleUsage()
+		{
+			lock (this)
+			{
+				if (used)
+					throw new InvalidOperationException("CmsProcessableInputStream can only be used once");
+
+				used = true;
+			}
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSReadable.cs b/Crypto/src/cms/CMSReadable.cs
new file mode 100644
index 000000000..9507b920c
--- /dev/null
+++ b/Crypto/src/cms/CMSReadable.cs
@@ -0,0 +1,10 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal interface CmsReadable
+	{
+		Stream GetInputStream();
+	}
+}
diff --git a/Crypto/src/cms/CMSSecureReadable.cs b/Crypto/src/cms/CMSSecureReadable.cs
new file mode 100644
index 000000000..5ceac24fd
--- /dev/null
+++ b/Crypto/src/cms/CMSSecureReadable.cs
@@ -0,0 +1,14 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal interface CmsSecureReadable
+	{
+		AlgorithmIdentifier Algorithm { get; }
+		object CryptoObject { get; }
+		CmsReadable GetReadable(KeyParameter key);
+	}
+}
diff --git a/Crypto/src/cms/CMSSignedData.cs b/Crypto/src/cms/CMSSignedData.cs
new file mode 100644
index 000000000..81c87a426
--- /dev/null
+++ b/Crypto/src/cms/CMSSignedData.cs
@@ -0,0 +1,425 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* general class for handling a pkcs7-signature message.
+	*
+	* A simple example of usage - note, in the example below the validity of
+	* the certificate isn't verified, just the fact that one of the certs
+	* matches the given signer...
+	*
+	* <pre>
+	*  IX509Store              certs = s.GetCertificates();
+	*  SignerInformationStore  signers = s.GetSignerInfos();
+	*
+	*  foreach (SignerInformation signer in signers.GetSigners())
+	*  {
+	*      ArrayList       certList = new ArrayList(certs.GetMatches(signer.SignerID));
+	*      X509Certificate cert = (X509Certificate) certList[0];
+	*
+	*      if (signer.Verify(cert.GetPublicKey()))
+	*      {
+	*          verified++;
+	*      }
+	*  }
+	* </pre>
+	*/
+	public class CmsSignedData
+	{
+		private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
+
+		private readonly CmsProcessable	signedContent;
+		private SignedData				signedData;
+		private ContentInfo				contentInfo;
+		private SignerInformationStore	signerInfoStore;
+		private IX509Store				attrCertStore;
+		private IX509Store				certificateStore;
+		private IX509Store				crlStore;
+		private IDictionary				hashes;
+
+		private CmsSignedData(
+			CmsSignedData c)
+		{
+			this.signedData = c.signedData;
+			this.contentInfo = c.contentInfo;
+			this.signedContent = c.signedContent;
+			this.signerInfoStore = c.signerInfoStore;
+		}
+
+		public CmsSignedData(
+			byte[] sigBlock)
+			: this(CmsUtilities.ReadContentInfo(new MemoryStream(sigBlock, false)))
+		{
+		}
+
+		public CmsSignedData(
+			CmsProcessable	signedContent,
+			byte[]			sigBlock)
+			: this(signedContent, CmsUtilities.ReadContentInfo(new MemoryStream(sigBlock, false)))
+		{
+		}
+
+		/**
+		 * Content with detached signature, digests precomputed
+		 *
+		 * @param hashes a map of precomputed digests for content indexed by name of hash.
+		 * @param sigBlock the signature object.
+		 */
+		public CmsSignedData(
+			IDictionary	hashes,
+			byte[]		sigBlock)
+			: this(hashes, CmsUtilities.ReadContentInfo(sigBlock))
+		{
+		}
+
+		/**
+		* base constructor - content with detached signature.
+		*
+		* @param signedContent the content that was signed.
+		* @param sigData the signature object.
+		*/
+		public CmsSignedData(
+			CmsProcessable	signedContent,
+			Stream			sigData)
+			: this(signedContent, CmsUtilities.ReadContentInfo(sigData))
+		{
+		}
+
+		/**
+		* base constructor - with encapsulated content
+		*/
+		public CmsSignedData(
+			Stream sigData)
+			: this(CmsUtilities.ReadContentInfo(sigData))
+		{
+		}
+
+		public CmsSignedData(
+			CmsProcessable  signedContent,
+			ContentInfo     sigData)
+		{
+			this.signedContent = signedContent;
+			this.contentInfo = sigData;
+			this.signedData = SignedData.GetInstance(contentInfo.Content);
+		}
+
+		public CmsSignedData(
+			IDictionary	hashes,
+			ContentInfo	sigData)
+		{
+			this.hashes = hashes;
+			this.contentInfo = sigData;
+			this.signedData = SignedData.GetInstance(contentInfo.Content);
+		}
+
+		public CmsSignedData(
+			ContentInfo sigData)
+		{
+			this.contentInfo = sigData;
+			this.signedData = SignedData.GetInstance(contentInfo.Content);
+
+			//
+			// this can happen if the signed message is sent simply to send a
+			// certificate chain.
+			//
+			if (signedData.EncapContentInfo.Content != null)
+			{
+				this.signedContent = new CmsProcessableByteArray(
+					((Asn1OctetString)(signedData.EncapContentInfo.Content)).GetOctets());
+			}
+//			else
+//			{
+//				this.signedContent = null;
+//			}
+		}
+
+		/// <summary>Return the version number for this object.</summary>
+		public int Version
+		{
+			get { return signedData.Version.Value.IntValue; }
+		}
+
+		/**
+		* return the collection of signers that are associated with the
+		* signatures for the message.
+		*/
+		public SignerInformationStore GetSignerInfos()
+		{
+			if (signerInfoStore == null)
+			{
+                IList signerInfos = Platform.CreateArrayList();
+				Asn1Set s = signedData.SignerInfos;
+
+				foreach (object obj in s)
+				{
+					SignerInfo info = SignerInfo.GetInstance(obj);
+					DerObjectIdentifier contentType = signedData.EncapContentInfo.ContentType;
+
+					if (hashes == null)
+					{
+						signerInfos.Add(new SignerInformation(info, contentType, signedContent, null));
+					}
+					else
+					{
+						byte[] hash = (byte[]) hashes[info.DigestAlgorithm.ObjectID.Id];
+
+						signerInfos.Add(new SignerInformation(info, contentType, null, new BaseDigestCalculator(hash)));
+					}
+				}
+
+				signerInfoStore = new SignerInformationStore(signerInfos);
+			}
+
+			return signerInfoStore;
+		}
+
+		/**
+		 * return a X509Store containing the attribute certificates, if any, contained
+		 * in this message.
+		 *
+		 * @param type type of store to create
+		 * @return a store of attribute certificates
+		 * @exception NoSuchStoreException if the store type isn't available.
+		 * @exception CmsException if a general exception prevents creation of the X509Store
+		 */
+		public IX509Store GetAttributeCertificates(
+			string type)
+		{
+			if (attrCertStore == null)
+			{
+				attrCertStore = Helper.CreateAttributeStore(type, signedData.Certificates);
+			}
+
+			return attrCertStore;
+		}
+
+		/**
+		 * return a X509Store containing the public key certificates, if any, contained
+		 * in this message.
+		 *
+		 * @param type type of store to create
+		 * @return a store of public key certificates
+		 * @exception NoSuchStoreException if the store type isn't available.
+		 * @exception CmsException if a general exception prevents creation of the X509Store
+		 */
+		public IX509Store GetCertificates(
+			string type)
+		{
+			if (certificateStore == null)
+			{
+				certificateStore = Helper.CreateCertificateStore(type, signedData.Certificates);
+			}
+
+			return certificateStore;
+		}
+
+		/**
+		* return a X509Store containing CRLs, if any, contained
+		* in this message.
+		*
+		* @param type type of store to create
+		* @return a store of CRLs
+		* @exception NoSuchStoreException if the store type isn't available.
+		* @exception CmsException if a general exception prevents creation of the X509Store
+		*/
+		public IX509Store GetCrls(
+			string type)
+		{
+			if (crlStore == null)
+			{
+				crlStore = Helper.CreateCrlStore(type, signedData.CRLs);
+			}
+
+			return crlStore;
+		}
+
+		[Obsolete("Use 'SignedContentType' property instead.")]
+		public string SignedContentTypeOid
+		{
+			get { return signedData.EncapContentInfo.ContentType.Id; }
+		}
+
+		/// <summary>
+		/// Return the <c>DerObjectIdentifier</c> associated with the encapsulated
+		/// content info structure carried in the signed data.
+		/// </summary>
+		public DerObjectIdentifier SignedContentType
+		{
+			get { return signedData.EncapContentInfo.ContentType; }
+		}
+
+		public CmsProcessable SignedContent
+		{
+			get { return signedContent; }
+		}
+
+		/**
+		 * return the ContentInfo
+		 */
+		public ContentInfo ContentInfo
+		{
+			get { return contentInfo; }
+		}
+
+		/**
+		* return the ASN.1 encoded representation of this object.
+		*/
+		public byte[] GetEncoded()
+		{
+			return contentInfo.GetEncoded();
+		}
+
+		/**
+		* Replace the signerinformation store associated with this
+		* CmsSignedData object with the new one passed in. You would
+		* probably only want to do this if you wanted to change the unsigned
+		* attributes associated with a signer, or perhaps delete one.
+		*
+		* @param signedData the signed data object to be used as a base.
+		* @param signerInformationStore the new signer information store to use.
+		* @return a new signed data object.
+		*/
+		public static CmsSignedData ReplaceSigners(
+			CmsSignedData           signedData,
+			SignerInformationStore  signerInformationStore)
+		{
+			//
+			// copy
+			//
+			CmsSignedData cms = new CmsSignedData(signedData);
+
+			//
+			// replace the store
+			//
+			cms.signerInfoStore = signerInformationStore;
+
+			//
+			// replace the signers in the SignedData object
+			//
+			Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
+			Asn1EncodableVector vec = new Asn1EncodableVector();
+
+			foreach (SignerInformation signer in signerInformationStore.GetSigners())
+			{
+				digestAlgs.Add(Helper.FixAlgID(signer.DigestAlgorithmID));
+				vec.Add(signer.ToSignerInfo());
+			}
+
+			Asn1Set digests = new DerSet(digestAlgs);
+			Asn1Set signers = new DerSet(vec);
+			Asn1Sequence sD = (Asn1Sequence)signedData.signedData.ToAsn1Object();
+
+			//
+			// signers are the last item in the sequence.
+			//
+			vec = new Asn1EncodableVector(
+				sD[0], // version
+				digests);
+
+			for (int i = 2; i != sD.Count - 1; i++)
+			{
+				vec.Add(sD[i]);
+			}
+
+			vec.Add(signers);
+
+			cms.signedData = SignedData.GetInstance(new BerSequence(vec));
+
+			//
+			// replace the contentInfo with the new one
+			//
+			cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData);
+
+			return cms;
+		}
+
+		/**
+		* Replace the certificate and CRL information associated with this
+		* CmsSignedData object with the new one passed in.
+		*
+		* @param signedData the signed data object to be used as a base.
+		* @param x509Certs the new certificates to be used.
+		* @param x509Crls the new CRLs to be used.
+		* @return a new signed data object.
+		* @exception CmsException if there is an error processing the stores
+		*/
+		public static CmsSignedData ReplaceCertificatesAndCrls(
+			CmsSignedData	signedData,
+			IX509Store		x509Certs,
+			IX509Store		x509Crls,
+			IX509Store		x509AttrCerts)
+		{
+			if (x509AttrCerts != null)
+				throw Platform.CreateNotImplementedException("Currently can't replace attribute certificates");
+
+			//
+			// copy
+			//
+			CmsSignedData cms = new CmsSignedData(signedData);
+
+			//
+			// replace the certs and crls in the SignedData object
+			//
+			Asn1Set certs = null;
+			try
+			{
+				Asn1Set asn1Set = CmsUtilities.CreateBerSetFromList(
+					CmsUtilities.GetCertificatesFromStore(x509Certs));
+
+				if (asn1Set.Count != 0)
+				{
+					certs = asn1Set;
+				}
+			}
+			catch (X509StoreException e)
+			{
+				throw new CmsException("error getting certificates from store", e);
+			}
+
+			Asn1Set crls = null;
+			try
+			{
+				Asn1Set asn1Set = CmsUtilities.CreateBerSetFromList(
+					CmsUtilities.GetCrlsFromStore(x509Crls));
+
+				if (asn1Set.Count != 0)
+				{
+					crls = asn1Set;
+				}
+			}
+			catch (X509StoreException e)
+			{
+				throw new CmsException("error getting CRLs from store", e);
+			}
+
+			//
+			// replace the CMS structure.
+			//
+			SignedData old = signedData.signedData;
+			cms.signedData = new SignedData(
+				old.DigestAlgorithms,
+				old.EncapContentInfo,
+				certs,
+				crls,
+				old.SignerInfos);
+
+			//
+			// replace the contentInfo with the new one
+			//
+			cms.contentInfo = new ContentInfo(cms.contentInfo.ContentType, cms.signedData);
+
+			return cms;
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSSignedDataGenerator.cs b/Crypto/src/cms/CMSSignedDataGenerator.cs
new file mode 100644
index 000000000..5b68c785d
--- /dev/null
+++ b/Crypto/src/cms/CMSSignedDataGenerator.cs
@@ -0,0 +1,551 @@
+using System;
+using System.Collections;
+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.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+     * general class for generating a pkcs7-signature message.
+     * <p>
+     * A simple example of usage.
+     *
+     * <pre>
+     *      IX509Store certs...
+     *      IX509Store crls...
+     *      CmsSignedDataGenerator gen = new CmsSignedDataGenerator();
+     *
+     *      gen.AddSigner(privKey, cert, CmsSignedGenerator.DigestSha1);
+     *      gen.AddCertificates(certs);
+     *      gen.AddCrls(crls);
+     *
+     *      CmsSignedData data = gen.Generate(content);
+     * </pre>
+	 * </p>
+     */
+    public class CmsSignedDataGenerator
+        : CmsSignedGenerator
+    {
+		private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
+
+		private readonly IList signerInfs = Platform.CreateArrayList();
+
+		private class SignerInf
+        {
+            private readonly CmsSignedGenerator outer;
+
+			private readonly AsymmetricKeyParameter		key;
+			private readonly SignerIdentifier			signerIdentifier;
+			private readonly string						digestOID;
+			private readonly string						encOID;
+			private readonly CmsAttributeTableGenerator	sAttr;
+			private readonly CmsAttributeTableGenerator	unsAttr;
+			private readonly Asn1.Cms.AttributeTable	baseSignedTable;
+
+			internal SignerInf(
+                CmsSignedGenerator			outer,
+	            AsymmetricKeyParameter		key,
+	            SignerIdentifier			signerIdentifier,
+	            string						digestOID,
+	            string						encOID,
+	            CmsAttributeTableGenerator	sAttr,
+	            CmsAttributeTableGenerator	unsAttr,
+	            Asn1.Cms.AttributeTable		baseSignedTable)
+	        {
+                this.outer = outer;
+                this.key = key;
+                this.signerIdentifier = signerIdentifier;
+                this.digestOID = digestOID;
+                this.encOID = encOID;
+	            this.sAttr = sAttr;
+	            this.unsAttr = unsAttr;
+	            this.baseSignedTable = baseSignedTable;
+            }
+
+			internal AlgorithmIdentifier DigestAlgorithmID
+			{
+				get { return new AlgorithmIdentifier(new DerObjectIdentifier(digestOID), DerNull.Instance); }
+			}
+
+			internal CmsAttributeTableGenerator SignedAttributes
+            {
+				get { return sAttr; }
+            }
+
+            internal CmsAttributeTableGenerator UnsignedAttributes
+            {
+				get { return unsAttr; }
+            }
+
+			internal SignerInfo ToSignerInfo(
+                DerObjectIdentifier	contentType,
+                CmsProcessable		content,
+				SecureRandom		random)
+            {
+                AlgorithmIdentifier digAlgId = DigestAlgorithmID;
+				string digestName = Helper.GetDigestAlgName(digestOID);
+				IDigest dig = Helper.GetDigestInstance(digestName);
+
+				string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(encOID);
+				ISigner sig = Helper.GetSignatureInstance(signatureName);
+
+				// TODO Optimise the case where more than one signer with same digest
+				if (content != null)
+                {
+                    content.Write(new DigOutputStream(dig));
+				}
+
+				byte[] hash = DigestUtilities.DoFinal(dig);
+				outer._digests.Add(digestOID, hash.Clone());
+
+				sig.Init(true, new ParametersWithRandom(key, random));
+#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE
+                Stream sigStr = new SigOutputStream(sig);
+#else
+				Stream sigStr = new BufferedStream(new SigOutputStream(sig));
+#endif
+
+				Asn1Set signedAttr = null;
+				if (sAttr != null)
+				{
+					IDictionary parameters = outer.GetBaseParameters(contentType, digAlgId, hash);
+
+//					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);
+                        }
+                    }
+
+					// TODO Validate proposed signed attributes
+
+					signedAttr = outer.GetAttributeSet(signed);
+
+					// sig must be composed from the DER encoding.
+					new DerOutputStream(sigStr).WriteObject(signedAttr);
+				}
+                else if (content != null)
+                {
+					// TODO Use raw signature of the hash value instead
+					content.Write(sigStr);
+                }
+
+                sigStr.Dispose();
+				byte[] sigBytes = sig.GenerateSignature();
+
+				Asn1Set unsignedAttr = null;
+				if (unsAttr != null)
+				{
+					IDictionary baseParameters = outer.GetBaseParameters(contentType, digAlgId, hash);
+					baseParameters[CmsAttributeTableParameter.Signature] = sigBytes.Clone();
+
+//					Asn1.Cms.AttributeTable unsigned = unsAttr.GetAttributes(Collections.unmodifiableMap(baseParameters));
+					Asn1.Cms.AttributeTable unsigned = unsAttr.GetAttributes(baseParameters);
+
+					// TODO Validate proposed unsigned attributes
+
+					unsignedAttr = outer.GetAttributeSet(unsigned);
+				}
+
+				// TODO[RSAPSS] Need the ability to specify non-default parameters
+				Asn1Encodable sigX509Parameters = SignerUtilities.GetDefaultX509Parameters(signatureName);
+				AlgorithmIdentifier encAlgId = CmsSignedGenerator.GetEncAlgorithmIdentifier(
+					new DerObjectIdentifier(encOID), sigX509Parameters);
+				
+                return new SignerInfo(signerIdentifier, digAlgId,
+                    signedAttr, encAlgId, new DerOctetString(sigBytes), unsignedAttr);
+            }
+        }
+
+		public CmsSignedDataGenerator()
+        {
+        }
+
+		/// <summary>Constructor allowing specific source of randomness</summary>
+		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
+		public CmsSignedDataGenerator(
+			SecureRandom rand)
+			: base(rand)
+		{
+		}
+
+		/**
+        * add a signer - no attributes other than the default ones will be
+        * provided here.
+		*
+		* @param key signing key to use
+		* @param cert certificate containing corresponding public key
+		* @param digestOID digest algorithm OID
+        */
+        public void AddSigner(
+            AsymmetricKeyParameter	privateKey,
+            X509Certificate			cert,
+            string					digestOID)
+        {
+        	AddSigner(privateKey, cert, GetEncOid(privateKey, digestOID), digestOID);
+		}
+
+		/**
+		 * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
+		 * provided here.
+		 *
+		 * @param key signing key to use
+		 * @param cert certificate containing corresponding public key
+		 * @param encryptionOID digest encryption algorithm OID
+		 * @param digestOID digest algorithm OID
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter	privateKey,
+			X509Certificate			cert,
+			string					encryptionOID,
+			string					digestOID)
+		{
+			doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID,
+				new DefaultSignedAttributeTableGenerator(), null, null);
+		}
+
+	    /**
+	     * add a signer - no attributes other than the default ones will be
+	     * provided here.
+	     */
+	    public void AddSigner(
+            AsymmetricKeyParameter	privateKey,
+	        byte[]					subjectKeyID,
+            string					digestOID)
+	    {
+			AddSigner(privateKey, subjectKeyID, GetEncOid(privateKey, digestOID), digestOID);
+	    }
+
+		/**
+		 * add a signer, specifying the digest encryption algorithm to use - no attributes other than the default ones will be
+		 * provided here.
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter	privateKey,
+			byte[]					subjectKeyID,
+			string					encryptionOID,
+			string					digestOID)
+		{
+			doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID,
+				new DefaultSignedAttributeTableGenerator(), null, null);
+		}
+
+        /**
+        * add a signer with extra signed/unsigned attributes.
+		*
+		* @param key signing key to use
+		* @param cert certificate containing corresponding public key
+		* @param digestOID digest algorithm OID
+		* @param signedAttr table of attributes to be included in signature
+		* @param unsignedAttr table of attributes to be included as unsigned
+        */
+        public void AddSigner(
+            AsymmetricKeyParameter	privateKey,
+            X509Certificate			cert,
+            string					digestOID,
+            Asn1.Cms.AttributeTable	signedAttr,
+            Asn1.Cms.AttributeTable	unsignedAttr)
+        {
+			AddSigner(privateKey, cert, GetEncOid(privateKey, digestOID), digestOID,
+				signedAttr, unsignedAttr);
+		}
+
+		/**
+		 * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes.
+		 *
+		 * @param key signing key to use
+		 * @param cert certificate containing corresponding public key
+		 * @param encryptionOID digest encryption algorithm OID
+		 * @param digestOID digest algorithm OID
+		 * @param signedAttr table of attributes to be included in signature
+		 * @param unsignedAttr table of attributes to be included as unsigned
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter	privateKey,
+			X509Certificate			cert,
+			string					encryptionOID,
+			string					digestOID,
+			Asn1.Cms.AttributeTable	signedAttr,
+			Asn1.Cms.AttributeTable	unsignedAttr)
+		{
+			doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID,
+				new DefaultSignedAttributeTableGenerator(signedAttr),
+				new SimpleAttributeTableGenerator(unsignedAttr),
+				signedAttr);
+		}
+
+	    /**
+	     * add a signer with extra signed/unsigned attributes.
+		 *
+		 * @param key signing key to use
+		 * @param subjectKeyID subjectKeyID of corresponding public key
+		 * @param digestOID digest algorithm OID
+		 * @param signedAttr table of attributes to be included in signature
+		 * @param unsignedAttr table of attributes to be included as unsigned
+	     */
+		public void AddSigner(
+			AsymmetricKeyParameter	privateKey,
+			byte[]					subjectKeyID,
+			string					digestOID,
+			Asn1.Cms.AttributeTable	signedAttr,
+			Asn1.Cms.AttributeTable	unsignedAttr)
+		{
+			AddSigner(privateKey, subjectKeyID, GetEncOid(privateKey, digestOID), digestOID,
+				signedAttr, unsignedAttr); 
+		}
+
+		/**
+		 * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes.
+		 *
+		 * @param key signing key to use
+		 * @param subjectKeyID subjectKeyID of corresponding public key
+		 * @param encryptionOID digest encryption algorithm OID
+		 * @param digestOID digest algorithm OID
+		 * @param signedAttr table of attributes to be included in signature
+		 * @param unsignedAttr table of attributes to be included as unsigned
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter	privateKey,
+			byte[]					subjectKeyID,
+			string					encryptionOID,
+			string					digestOID,
+			Asn1.Cms.AttributeTable	signedAttr,
+			Asn1.Cms.AttributeTable	unsignedAttr)
+		{
+			doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID,
+				new DefaultSignedAttributeTableGenerator(signedAttr),
+				new SimpleAttributeTableGenerator(unsignedAttr),
+				signedAttr);
+		}
+
+		/**
+		 * add a signer with extra signed/unsigned attributes based on generators.
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter		privateKey,
+			X509Certificate				cert,
+			string						digestOID,
+			CmsAttributeTableGenerator	signedAttrGen,
+			CmsAttributeTableGenerator	unsignedAttrGen)
+		{
+			AddSigner(privateKey, cert, GetEncOid(privateKey, digestOID), digestOID,
+				signedAttrGen, unsignedAttrGen);
+		}
+
+		/**
+		 * add a signer, specifying the digest encryption algorithm, with extra signed/unsigned attributes based on generators.
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter		privateKey,
+			X509Certificate				cert,
+			string						encryptionOID,
+			string						digestOID,
+			CmsAttributeTableGenerator	signedAttrGen,
+			CmsAttributeTableGenerator	unsignedAttrGen)
+		{
+			doAddSigner(privateKey, GetSignerIdentifier(cert), encryptionOID, digestOID, signedAttrGen,
+				unsignedAttrGen, null);
+		}
+
+	    /**
+	     * add a signer with extra signed/unsigned attributes based on generators.
+	     */
+	    public void AddSigner(
+			AsymmetricKeyParameter		privateKey,
+	        byte[]						subjectKeyID,
+	        string						digestOID,
+	        CmsAttributeTableGenerator	signedAttrGen,
+	        CmsAttributeTableGenerator	unsignedAttrGen)
+	    {
+			AddSigner(privateKey, subjectKeyID, GetEncOid(privateKey, digestOID), digestOID,
+				signedAttrGen, unsignedAttrGen);
+	    }
+
+		/**
+		 * add a signer, including digest encryption algorithm, with extra signed/unsigned attributes based on generators.
+		 */
+		public void AddSigner(
+			AsymmetricKeyParameter		privateKey,
+			byte[]						subjectKeyID,
+			string						encryptionOID,
+			string						digestOID,
+			CmsAttributeTableGenerator	signedAttrGen,
+			CmsAttributeTableGenerator	unsignedAttrGen)
+		{
+			doAddSigner(privateKey, GetSignerIdentifier(subjectKeyID), encryptionOID, digestOID,
+				signedAttrGen, unsignedAttrGen, null);
+		}
+
+		private void doAddSigner(
+			AsymmetricKeyParameter		privateKey,
+			SignerIdentifier            signerIdentifier,
+			string                      encryptionOID,
+			string                      digestOID,
+			CmsAttributeTableGenerator  signedAttrGen,
+			CmsAttributeTableGenerator  unsignedAttrGen,
+			Asn1.Cms.AttributeTable		baseSignedTable)
+		{
+			signerInfs.Add(new SignerInf(this, privateKey, signerIdentifier, digestOID, encryptionOID,
+				signedAttrGen, unsignedAttrGen, baseSignedTable));
+		}
+
+		/**
+        * generate a signed object that for a CMS Signed Data object
+        */
+        public CmsSignedData Generate(
+            CmsProcessable content)
+        {
+            return Generate(content, 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. The content type
+        * is set according to the OID represented by the string signedContentType.
+        */
+        public CmsSignedData Generate(
+            string			signedContentType,
+			// FIXME Avoid accessing more than once to support CmsProcessableInputStream
+            CmsProcessable	content,
+            bool			encapsulate)
+        {
+            Asn1EncodableVector digestAlgs = new Asn1EncodableVector();
+            Asn1EncodableVector signerInfos = new Asn1EncodableVector();
+
+			_digests.Clear(); // clear the current preserved digest state
+
+			//
+            // add the precalculated SignerInfo objects.
+            //
+            foreach (SignerInformation signer in _signers)
+            {
+				digestAlgs.Add(Helper.FixAlgID(signer.DigestAlgorithmID));
+
+				// TODO Verify the content type and calculated digest match the precalculated SignerInfo
+				signerInfos.Add(signer.ToSignerInfo());
+            }
+
+			//
+            // add the SignerInfo objects
+            //
+            bool isCounterSignature = (signedContentType == null);
+
+            DerObjectIdentifier contentTypeOid = isCounterSignature
+                ?   null
+				:	new DerObjectIdentifier(signedContentType);
+
+            foreach (SignerInf signer in signerInfs)
+            {
+				try
+                {
+					digestAlgs.Add(signer.DigestAlgorithmID);
+                    signerInfos.Add(signer.ToSignerInfo(contentTypeOid, content, rand));
+				}
+                catch (IOException e)
+                {
+                    throw new CmsException("encoding error.", e);
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new CmsException("key inappropriate for signature.", e);
+                }
+                catch (SignatureException e)
+                {
+                    throw new CmsException("error creating signature.", e);
+                }
+                catch (CertificateEncodingException e)
+                {
+                    throw new CmsException("error creating sid.", e);
+                }
+            }
+
+			Asn1Set certificates = null;
+
+			if (_certs.Count != 0)
+			{
+				certificates = CmsUtilities.CreateBerSetFromList(_certs);
+			}
+
+			Asn1Set certrevlist = null;
+
+			if (_crls.Count != 0)
+			{
+				certrevlist = CmsUtilities.CreateBerSetFromList(_crls);
+			}
+
+			Asn1OctetString octs = null;
+			if (encapsulate)
+            {
+                MemoryStream bOut = new MemoryStream();
+				if (content != null)
+				{
+	                try
+	                {
+	                    content.Write(bOut);
+	                }
+	                catch (IOException e)
+	                {
+	                    throw new CmsException("encapsulation error.", e);
+	                }
+				}
+				octs = new BerOctetString(bOut.ToArray());
+            }
+
+            ContentInfo encInfo = new ContentInfo(contentTypeOid, octs);
+
+            SignedData sd = new SignedData(
+                new DerSet(digestAlgs),
+                encInfo,
+                certificates,
+                certrevlist,
+                new DerSet(signerInfos));
+
+            ContentInfo contentInfo = new ContentInfo(CmsObjectIdentifiers.SignedData, sd);
+
+            return new CmsSignedData(content, contentInfo);
+        }
+
+        /**
+        * 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 CmsSignedData Generate(
+            CmsProcessable	content,
+            bool			encapsulate)
+        {
+            return this.Generate(Data, content, encapsulate);
+        }
+
+		/**
+		* generate a set of one or more SignerInformation objects representing counter signatures on
+		* the passed in SignerInformation object.
+		*
+		* @param signer the signer to be countersigned
+		* @param sigProvider the provider to be used for counter signing.
+		* @return a store containing the signers.
+		*/
+		public SignerInformationStore GenerateCounterSigners(
+			SignerInformation signer)
+		{
+			return this.Generate(null, new CmsProcessableByteArray(signer.GetSignature()), false).GetSignerInfos();
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSSignedDataParser.cs b/Crypto/src/cms/CMSSignedDataParser.cs
new file mode 100644
index 000000000..dd794ee77
--- /dev/null
+++ b/Crypto/src/cms/CMSSignedDataParser.cs
@@ -0,0 +1,455 @@
+using System;
+using System.Collections;
+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.Security;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* Parsing class for an CMS Signed Data object from an input stream.
+	* <p>
+	* Note: that because we are in a streaming mode only one signer can be tried and it is important
+	* that the methods on the parser are called in the appropriate order.
+	* </p>
+	* <p>
+	* A simple example of usage for an encapsulated signature.
+	* </p>
+	* <p>
+	* Two notes: first, in the example below the validity of
+	* the certificate isn't verified, just the fact that one of the certs
+	* matches the given signer, and, second, because we are in a streaming
+	* mode the order of the operations is important.
+	* </p>
+	* <pre>
+	*      CmsSignedDataParser     sp = new CmsSignedDataParser(encapSigData);
+	*
+	*      sp.GetSignedContent().Drain();
+	*
+	*      IX509Store              certs = sp.GetCertificates();
+	*      SignerInformationStore  signers = sp.GetSignerInfos();
+	*
+	*      foreach (SignerInformation signer in signers.GetSigners())
+	*      {
+	*          ArrayList       certList = new ArrayList(certs.GetMatches(signer.SignerID));
+	*          X509Certificate cert = (X509Certificate) certList[0];
+	*
+	*          Console.WriteLine("verify returns: " + signer.Verify(cert));
+	*      }
+	* </pre>
+	*  Note also: this class does not introduce buffering - if you are processing large files you should create
+	*  the parser with:
+	*  <pre>
+	*          CmsSignedDataParser     ep = new CmsSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
+	*  </pre>
+	*  where bufSize is a suitably large buffer size.
+	*/
+	public class CmsSignedDataParser
+		: CmsContentInfoParser
+	{
+		private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
+
+		private SignedDataParser        _signedData;
+		private DerObjectIdentifier		_signedContentType;
+		private CmsTypedStream          _signedContent;
+		private IDictionary				_digests;
+		private ISet					_digestOids;
+
+		private SignerInformationStore  _signerInfoStore;
+		private Asn1Set                 _certSet, _crlSet;
+		private bool					_isCertCrlParsed;
+		private IX509Store				_attributeStore;
+		private IX509Store				_certificateStore;
+		private IX509Store				_crlStore;
+
+		public CmsSignedDataParser(
+			byte[] sigBlock)
+			: this(new MemoryStream(sigBlock, false))
+		{
+		}
+
+		public CmsSignedDataParser(
+			CmsTypedStream	signedContent,
+			byte[]			sigBlock)
+			: this(signedContent, new MemoryStream(sigBlock, false))
+		{
+		}
+
+		/**
+		* base constructor - with encapsulated content
+		*/
+		public CmsSignedDataParser(
+			Stream sigData)
+			: this(null, sigData)
+		{
+		}
+
+		/**
+		* base constructor
+		*
+		* @param signedContent the content that was signed.
+		* @param sigData the signature object.
+		*/
+		public CmsSignedDataParser(
+			CmsTypedStream	signedContent,
+			Stream			sigData)
+			: base(sigData)
+		{
+			try
+			{
+				this._signedContent = signedContent;
+				this._signedData = SignedDataParser.GetInstance(this.contentInfo.GetContent(Asn1Tags.Sequence));
+				this._digests = Platform.CreateHashtable();
+				this._digestOids = new HashSet();
+
+				Asn1SetParser digAlgs = _signedData.GetDigestAlgorithms();
+				IAsn1Convertible o;
+
+				while ((o = digAlgs.ReadObject()) != null)
+				{
+					AlgorithmIdentifier id = AlgorithmIdentifier.GetInstance(o.ToAsn1Object());
+
+					try
+					{
+						string digestOid = id.ObjectID.Id;
+						string digestName = Helper.GetDigestAlgName(digestOid);
+
+						if (!this._digests.Contains(digestName))
+						{
+							this._digests[digestName] = Helper.GetDigestInstance(digestName);
+							this._digestOids.Add(digestOid);
+						}
+					}
+					catch (SecurityUtilityException)
+					{
+						// TODO Should do something other than ignore it
+					}
+				}
+
+				//
+				// If the message is simply a certificate chain message GetContent() may return null.
+				//
+				ContentInfoParser cont = _signedData.GetEncapContentInfo();
+				Asn1OctetStringParser octs = (Asn1OctetStringParser)
+					cont.GetContent(Asn1Tags.OctetString);
+
+				if (octs != null)
+				{
+					CmsTypedStream ctStr = new CmsTypedStream(
+						cont.ContentType.Id, octs.GetOctetStream());
+
+					if (_signedContent == null)
+					{
+						this._signedContent = ctStr;
+					}
+					else
+					{
+						//
+						// content passed in, need to read past empty encapsulated content info object if present
+						//
+						ctStr.Drain();
+					}
+				}
+
+				_signedContentType = _signedContent == null
+					?	cont.ContentType
+					:	new DerObjectIdentifier(_signedContent.ContentType);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("io exception: " + e.Message, e);
+			}
+
+			if (_digests.Count < 1)
+			{
+				throw new CmsException("no digests could be created for message.");
+			}
+		}
+
+		/**
+		 * Return the version number for the SignedData object
+		 *
+		 * @return the version number
+		 */
+		public int Version
+		{
+			get { return _signedData.Version.Value.IntValue; }
+		}
+
+		public ISet DigestOids
+		{
+			get { return new HashSet(_digestOids); }
+		}
+
+		/**
+		* return the collection of signers that are associated with the
+		* signatures for the message.
+		* @throws CmsException
+		*/
+		public SignerInformationStore GetSignerInfos()
+		{
+			if (_signerInfoStore == null)
+			{
+				PopulateCertCrlSets();
+
+                IList signerInfos = Platform.CreateArrayList();
+                IDictionary hashes = Platform.CreateHashtable();
+
+				foreach (object digestKey in _digests.Keys)
+				{
+					hashes[digestKey] = DigestUtilities.DoFinal(
+						(IDigest)_digests[digestKey]);
+				}
+
+				try
+				{
+					Asn1SetParser s = _signedData.GetSignerInfos();
+					IAsn1Convertible o;
+
+					while ((o = s.ReadObject()) != null)
+					{
+						SignerInfo info = SignerInfo.GetInstance(o.ToAsn1Object());
+						string digestName = Helper.GetDigestAlgName(
+							info.DigestAlgorithm.ObjectID.Id);
+
+						byte[] hash = (byte[]) hashes[digestName];
+
+						signerInfos.Add(new SignerInformation(info, _signedContentType, null, new BaseDigestCalculator(hash)));
+					}
+				}
+				catch (IOException e)
+				{
+					throw new CmsException("io exception: " + e.Message, e);
+				}
+
+				_signerInfoStore = new SignerInformationStore(signerInfos);
+			}
+
+			return _signerInfoStore;
+		}
+
+		/**
+		 * return a X509Store containing the attribute certificates, if any, contained
+		 * in this message.
+		 *
+		 * @param type type of store to create
+		 * @return a store of attribute certificates
+		 * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
+		 * @exception CmsException if a general exception prevents creation of the X509Store
+		 */
+		public IX509Store GetAttributeCertificates(
+			string type)
+		{
+			if (_attributeStore == null)
+			{
+				PopulateCertCrlSets();
+
+				_attributeStore = Helper.CreateAttributeStore(type, _certSet);
+			}
+
+			return _attributeStore;
+		}
+
+		/**
+		* return a X509Store containing the public key certificates, if any, contained
+		* in this message.
+		*
+		* @param type type of store to create
+		* @return a store of public key certificates
+		* @exception NoSuchStoreException if the store type isn't available.
+		* @exception CmsException if a general exception prevents creation of the X509Store
+		*/
+		public IX509Store GetCertificates(
+			string type)
+		{
+			if (_certificateStore == null)
+			{
+				PopulateCertCrlSets();
+
+				_certificateStore = Helper.CreateCertificateStore(type, _certSet);
+			}
+
+			return _certificateStore;
+		}
+
+		/**
+		* return a X509Store containing CRLs, if any, contained
+		* in this message.
+		*
+		* @param type type of store to create
+		* @return a store of CRLs
+		* @exception NoSuchStoreException if the store type isn't available.
+		* @exception CmsException if a general exception prevents creation of the X509Store
+		*/
+		public IX509Store GetCrls(
+			string type)
+		{
+			if (_crlStore == null)
+			{
+				PopulateCertCrlSets();
+
+				_crlStore = Helper.CreateCrlStore(type, _crlSet);
+			}
+
+			return _crlStore;
+		}
+
+		private void PopulateCertCrlSets()
+		{
+			if (_isCertCrlParsed)
+				return;
+
+			_isCertCrlParsed = true;
+
+			try
+			{
+				// care! Streaming - Must process the GetCertificates() result before calling GetCrls()
+				_certSet = GetAsn1Set(_signedData.GetCertificates());
+				_crlSet = GetAsn1Set(_signedData.GetCrls());
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("problem parsing cert/crl sets", e);
+			}
+		}
+
+		/// <summary>
+		/// Return the <c>DerObjectIdentifier</c> associated with the encapsulated
+		/// content info structure carried in the signed data.
+		/// </summary>
+		public DerObjectIdentifier SignedContentType
+		{
+			get { return _signedContentType; }
+		}
+
+		public CmsTypedStream GetSignedContent()
+		{
+			if (_signedContent == null)
+			{
+				return null;
+			}
+
+			Stream digStream = _signedContent.ContentStream;
+
+			foreach (IDigest digest in _digests.Values)
+			{
+				digStream = new DigestStream(digStream, digest, null);
+			}
+
+			return new CmsTypedStream(_signedContent.ContentType, digStream);
+		}
+
+		/**
+		 * Replace the signerinformation store associated with the passed
+		 * in message contained in the stream original with the new one passed in.
+		 * You would probably only want to do this if you wanted to change the unsigned
+		 * attributes associated with a signer, or perhaps delete one.
+		 * <p>
+		 * The output stream is returned unclosed.
+		 * </p>
+		 * @param original the signed data stream to be used as a base.
+		 * @param signerInformationStore the new signer information store to use.
+		 * @param out the stream to Write the new signed data object to.
+		 * @return out.
+		 */
+		public static Stream ReplaceSigners(
+			Stream					original,
+			SignerInformationStore	signerInformationStore,
+			Stream					outStr)
+		{
+			// NB: SecureRandom would be ignored since using existing signatures only
+			CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator();
+			CmsSignedDataParser parser = new CmsSignedDataParser(original);
+
+//			gen.AddDigests(parser.DigestOids);
+			gen.AddSigners(signerInformationStore);
+
+			CmsTypedStream signedContent = parser.GetSignedContent();
+			bool encapsulate = (signedContent != null);
+			Stream contentOut = gen.Open(outStr, parser.SignedContentType.Id, encapsulate);
+			if (encapsulate)
+			{
+				Streams.PipeAll(signedContent.ContentStream, contentOut);
+			}
+
+			gen.AddAttributeCertificates(parser.GetAttributeCertificates("Collection"));
+			gen.AddCertificates(parser.GetCertificates("Collection"));
+			gen.AddCrls(parser.GetCrls("Collection"));
+
+//			gen.AddSigners(parser.GetSignerInfos());
+
+            contentOut.Dispose();
+
+			return outStr;
+		}
+
+		/**
+		 * Replace the certificate and CRL information associated with this
+		 * CMSSignedData object with the new one passed in.
+		 * <p>
+		 * The output stream is returned unclosed.
+		 * </p>
+		 * @param original the signed data stream to be used as a base.
+		 * @param certsAndCrls the new certificates and CRLs to be used.
+		 * @param out the stream to Write the new signed data object to.
+		 * @return out.
+		 * @exception CmsException if there is an error processing the CertStore
+		 */
+		public static Stream ReplaceCertificatesAndCrls(
+			Stream			original,
+			IX509Store		x509Certs,
+			IX509Store		x509Crls,
+			IX509Store		x509AttrCerts,
+			Stream			outStr)
+		{
+			// NB: SecureRandom would be ignored since using existing signatures only
+			CmsSignedDataStreamGenerator gen = new CmsSignedDataStreamGenerator();
+			CmsSignedDataParser parser = new CmsSignedDataParser(original);
+
+			gen.AddDigests(parser.DigestOids);
+
+			CmsTypedStream signedContent = parser.GetSignedContent();
+			bool encapsulate = (signedContent != null);
+			Stream contentOut = gen.Open(outStr, parser.SignedContentType.Id, encapsulate);
+			if (encapsulate)
+			{
+				Streams.PipeAll(signedContent.ContentStream, contentOut);
+			}
+
+//			gen.AddAttributeCertificates(parser.GetAttributeCertificates("Collection"));
+//			gen.AddCertificates(parser.GetCertificates("Collection"));
+//			gen.AddCrls(parser.GetCrls("Collection"));
+			if (x509AttrCerts != null)
+				gen.AddAttributeCertificates(x509AttrCerts);
+			if (x509Certs != null)
+				gen.AddCertificates(x509Certs);
+			if (x509Crls != null)
+				gen.AddCrls(x509Crls);
+
+			gen.AddSigners(parser.GetSignerInfos());
+
+            contentOut.Dispose();
+
+			return outStr;
+		}
+
+		private static Asn1Set GetAsn1Set(
+			Asn1SetParser asn1SetParser)
+		{
+			return asn1SetParser == null
+				?	null
+				:	Asn1Set.GetInstance(asn1SetParser.ToAsn1Object());
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSSignedDataStreamGenerator.cs b/Crypto/src/cms/CMSSignedDataStreamGenerator.cs
new file mode 100644
index 000000000..0bed9fcad
--- /dev/null
+++ b/Crypto/src/cms/CMSSignedDataStreamGenerator.cs
@@ -0,0 +1,917 @@
+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");
+
+            	dig = Helper.GetDigestInstance(digestName);
+            	_messageDigests[digestName] = dig;
+            }
+		}
+
+		// TODO Make public?
+		internal void Generate(
+			Stream			outStream,
+			string			eContentType,
+			bool			encapsulate,
+			Stream			dataOutputStream,
+			CmsProcessable	content)
+		{
+			Stream signedOut = Open(outStream, eContentType, encapsulate, dataOutputStream);
+			if (content != null)
+			{
+				content.Write(signedOut);
+			}
+            signedOut.Dispose();
+		}
+
+		// RFC3852, section 5.1:
+		// IF ((certificates is present) AND
+		//    (any certificates with a type of other are present)) OR
+		//    ((crls is present) AND
+		//    (any crls with a type of other are present))
+		// THEN version MUST be 5
+		// ELSE
+		//    IF (certificates is present) AND
+		//       (any version 2 attribute certificates are present)
+		//    THEN version MUST be 4
+		//    ELSE
+		//       IF ((certificates is present) AND
+		//          (any version 1 attribute certificates are present)) OR
+		//          (any SignerInfo structures are version 3) OR
+		//          (encapContentInfo eContentType is other than id-data)
+		//       THEN version MUST be 3
+		//       ELSE version MUST be 1
+		//
+		private DerInteger CalculateVersion(
+			DerObjectIdentifier contentOid)
+		{
+			bool otherCert = false;
+			bool otherCrl = false;
+			bool attrCertV1Found = false;
+			bool attrCertV2Found = false;
+
+			if (_certs != null)
+			{
+				foreach (object obj in _certs)
+				{
+					if (obj is Asn1TaggedObject)
+					{
+						Asn1TaggedObject tagged = (Asn1TaggedObject) obj;
+
+						if (tagged.TagNo == 1)
+						{
+							attrCertV1Found = true;
+						}
+						else if (tagged.TagNo == 2)
+						{
+							attrCertV2Found = true;
+						}
+						else if (tagged.TagNo == 3)
+						{
+							otherCert = true;
+							break;
+						}
+					}
+				}
+			}
+
+			if (otherCert)
+			{
+				return new DerInteger(5);
+			}
+
+			if (_crls != null)
+			{
+				foreach (object obj in _crls)
+				{
+					if (obj is Asn1TaggedObject)
+					{
+						otherCrl = true;
+						break;
+					}
+				}
+			}
+
+			if (otherCrl)
+			{
+				return new DerInteger(5);
+			}
+
+			if (attrCertV2Found)
+			{
+				return new DerInteger(4);
+			}
+
+            if (attrCertV1Found || !CmsObjectIdentifiers.Data.Equals(contentOid) || CheckForVersion3(_signers))
+            {
+                return new DerInteger(3);
+            }
+
+            return new DerInteger(1);
+        }
+
+		private bool CheckForVersion3(
+			IList signerInfos)
+		{
+			foreach (SignerInformation si in signerInfos)
+			{
+				SignerInfo s = SignerInfo.GetInstance(si.ToSignerInfo());
+
+				if (s.Version.Value.IntValue == 3)
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+		private static Stream AttachDigestsToOutputStream(ICollection digests, Stream s)
+		{
+			Stream result = s;
+			foreach (IDigest digest in digests)
+			{
+				result = GetSafeTeeOutputStream(result, new DigOutputStream(digest));
+			}
+			return result;
+		}
+
+		private static Stream GetSafeOutputStream(Stream s)
+		{
+			if (s == null)
+				return new NullOutputStream();
+			return s;
+		}
+
+		private static Stream GetSafeTeeOutputStream(Stream s1, Stream s2)
+		{
+			if (s1 == null)
+				return GetSafeOutputStream(s2);
+			if (s2 == null)
+				return GetSafeOutputStream(s1);
+			return new TeeOutputStream(s1, s2);
+		}
+
+		private class CmsSignedDataOutputStream
+            : BaseOutputStream
+        {
+			private readonly CmsSignedDataStreamGenerator outer;
+
+			private Stream					_out;
+            private DerObjectIdentifier		_contentOID;
+            private BerSequenceGenerator	_sGen;
+            private BerSequenceGenerator	_sigGen;
+            private BerSequenceGenerator	_eiGen;
+
+			public CmsSignedDataOutputStream(
+				CmsSignedDataStreamGenerator	outer,
+				Stream							outStream,
+                string							contentOID,
+                BerSequenceGenerator			sGen,
+                BerSequenceGenerator			sigGen,
+                BerSequenceGenerator			eiGen)
+            {
+				this.outer = outer;
+
+				_out = outStream;
+                _contentOID = new DerObjectIdentifier(contentOID);
+                _sGen = sGen;
+                _sigGen = sigGen;
+                _eiGen = eiGen;
+            }
+
+			public override void WriteByte(
+                byte b)
+            {
+                _out.WriteByte(b);
+            }
+
+			public override void Write(
+                byte[]	bytes,
+                int		off,
+                int		len)
+            {
+                _out.Write(bytes, off, len);
+            }
+
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    _out.Dispose();
+
+                    // TODO Parent context(s) should really be be closed explicitly
+
+                    _eiGen.Close();
+
+                    outer._digests.Clear();    // clear the current preserved digest state
+
+                    if (outer._certs.Count > 0)
+                    {
+                        Asn1Set certs = CmsUtilities.CreateBerSetFromList(outer._certs);
+
+                        WriteToGenerator(_sigGen, new BerTaggedObject(false, 0, certs));
+                    }
+
+                    if (outer._crls.Count > 0)
+                    {
+                        Asn1Set crls = CmsUtilities.CreateBerSetFromList(outer._crls);
+
+                        WriteToGenerator(_sigGen, new BerTaggedObject(false, 1, crls));
+                    }
+
+                    //
+                    // Calculate the digest hashes
+                    //
+                    foreach (DictionaryEntry de in outer._messageDigests)
+                    {
+                        outer._messageHashes.Add(de.Key, DigestUtilities.DoFinal((IDigest)de.Value));
+                    }
+
+                    // TODO If the digest OIDs for precalculated signers weren't mixed in with
+                    // the others, we could fill in outer._digests here, instead of SignerInfoGenerator.Generate
+
+                    //
+                    // collect all the SignerInfo objects
+                    //
+                    Asn1EncodableVector signerInfos = new Asn1EncodableVector();
+
+                    //
+                    // add the generated SignerInfo objects
+                    //
+                    {
+                        foreach (DigestAndSignerInfoGeneratorHolder holder in outer._signerInfs)
+                        {
+                            AlgorithmIdentifier digestAlgorithm = holder.DigestAlgorithm;
+
+                            byte[] calculatedDigest = (byte[])outer._messageHashes[
+                                Helper.GetDigestAlgName(holder.digestOID)];
+                            outer._digests[holder.digestOID] = calculatedDigest.Clone();
+
+                            signerInfos.Add(holder.signerInf.Generate(_contentOID, digestAlgorithm, calculatedDigest));
+                        }
+                    }
+
+                    //
+                    // add the precalculated SignerInfo objects.
+                    //
+                    {
+                        foreach (SignerInformation signer in outer._signers)
+                        {
+                            // TODO Verify the content type and calculated digest match the precalculated SignerInfo
+                            //						if (!signer.ContentType.Equals(_contentOID))
+                            //						{
+                            //							// TODO The precalculated content type did not match - error?
+                            //						}
+                            //
+                            //						byte[] calculatedDigest = (byte[])outer._digests[signer.DigestAlgOid];
+                            //						if (calculatedDigest == null)
+                            //						{
+                            //							// TODO We can't confirm this digest because we didn't calculate it - error?
+                            //						}
+                            //						else
+                            //						{
+                            //							if (!Arrays.AreEqual(signer.GetContentDigest(), calculatedDigest))
+                            //							{
+                            //								// TODO The precalculated digest did not match - error?
+                            //							}
+                            //						}
+
+                            signerInfos.Add(signer.ToSignerInfo());
+                        }
+                    }
+
+                    WriteToGenerator(_sigGen, new DerSet(signerInfos));
+
+                    _sigGen.Close();
+                    _sGen.Close();
+                }
+
+				base.Dispose(disposing);
+			}
+
+			private static void WriteToGenerator(
+				Asn1Generator	ag,
+				Asn1Encodable	ae)
+			{
+				byte[] encoded = ae.GetEncoded();
+				ag.GetRawOutputStream().Write(encoded, 0, encoded.Length);
+			}
+		}
+    }
+}
diff --git a/Crypto/src/cms/CMSSignedGenerator.cs b/Crypto/src/cms/CMSSignedGenerator.cs
new file mode 100644
index 000000000..f272c830e
--- /dev/null
+++ b/Crypto/src/cms/CMSSignedGenerator.cs
@@ -0,0 +1,261 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.CryptoPro;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.TeleTrust;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+    public class CmsSignedGenerator
+    {
+        /**
+        * Default type for the signed data.
+        */
+        public static readonly string Data = CmsObjectIdentifiers.Data.Id;
+
+		public static readonly string DigestSha1 = OiwObjectIdentifiers.IdSha1.Id;
+        public static readonly string DigestSha224 = NistObjectIdentifiers.IdSha224.Id;
+        public static readonly string DigestSha256 = NistObjectIdentifiers.IdSha256.Id;
+        public static readonly string DigestSha384 = NistObjectIdentifiers.IdSha384.Id;
+        public static readonly string DigestSha512 = NistObjectIdentifiers.IdSha512.Id;
+        public static readonly string DigestMD5 = PkcsObjectIdentifiers.MD5.Id;
+        public static readonly string DigestGost3411 = CryptoProObjectIdentifiers.GostR3411.Id;
+		public static readonly string DigestRipeMD128 = TeleTrusTObjectIdentifiers.RipeMD128.Id;
+		public static readonly string DigestRipeMD160 = TeleTrusTObjectIdentifiers.RipeMD160.Id;
+		public static readonly string DigestRipeMD256 = TeleTrusTObjectIdentifiers.RipeMD256.Id;
+
+		public static readonly string EncryptionRsa = PkcsObjectIdentifiers.RsaEncryption.Id;
+        public static readonly string EncryptionDsa = X9ObjectIdentifiers.IdDsaWithSha1.Id;
+        public static readonly string EncryptionECDsa = X9ObjectIdentifiers.ECDsaWithSha1.Id;
+        public static readonly string EncryptionRsaPss = PkcsObjectIdentifiers.IdRsassaPss.Id;
+        public static readonly string EncryptionGost3410 = CryptoProObjectIdentifiers.GostR3410x94.Id;
+        public static readonly string EncryptionECGost3410 = CryptoProObjectIdentifiers.GostR3410x2001.Id;
+
+		private static readonly string EncryptionECDsaWithSha1 = X9ObjectIdentifiers.ECDsaWithSha1.Id;
+		private static readonly string EncryptionECDsaWithSha224 = X9ObjectIdentifiers.ECDsaWithSha224.Id;
+		private static readonly string EncryptionECDsaWithSha256 = X9ObjectIdentifiers.ECDsaWithSha256.Id;
+		private static readonly string EncryptionECDsaWithSha384 = X9ObjectIdentifiers.ECDsaWithSha384.Id;
+		private static readonly string EncryptionECDsaWithSha512 = X9ObjectIdentifiers.ECDsaWithSha512.Id;
+
+		private static readonly ISet noParams = new HashSet();
+		private static readonly IDictionary ecAlgorithms = Platform.CreateHashtable();
+
+		static CmsSignedGenerator()
+		{
+			noParams.Add(EncryptionDsa);
+//			noParams.Add(EncryptionECDsa);
+			noParams.Add(EncryptionECDsaWithSha1);
+			noParams.Add(EncryptionECDsaWithSha224);
+			noParams.Add(EncryptionECDsaWithSha256);
+			noParams.Add(EncryptionECDsaWithSha384);
+			noParams.Add(EncryptionECDsaWithSha512);
+
+			ecAlgorithms.Add(DigestSha1, EncryptionECDsaWithSha1);
+			ecAlgorithms.Add(DigestSha224, EncryptionECDsaWithSha224);
+			ecAlgorithms.Add(DigestSha256, EncryptionECDsaWithSha256);
+			ecAlgorithms.Add(DigestSha384, EncryptionECDsaWithSha384);
+			ecAlgorithms.Add(DigestSha512, EncryptionECDsaWithSha512);
+		}
+
+		internal IList _certs = Platform.CreateArrayList();
+        internal IList _crls = Platform.CreateArrayList();
+		internal IList _signers = Platform.CreateArrayList();
+		internal IDictionary _digests = Platform.CreateHashtable();
+
+		protected readonly SecureRandom rand;
+
+		protected CmsSignedGenerator()
+			: this(new SecureRandom())
+		{
+		}
+
+		/// <summary>Constructor allowing specific source of randomness</summary>
+		/// <param name="rand">Instance of <c>SecureRandom</c> to use.</param>
+		protected CmsSignedGenerator(
+			SecureRandom rand)
+		{
+			this.rand = rand;
+		}
+
+		protected string GetEncOid(
+            AsymmetricKeyParameter	key,
+            string					digestOID)
+        {
+            string encOID = null;
+
+			if (key is RsaKeyParameters)
+			{
+				if (!((RsaKeyParameters) key).IsPrivate)
+					throw new ArgumentException("Expected RSA private key");
+
+				encOID = EncryptionRsa;
+			}
+			else if (key is DsaPrivateKeyParameters)
+			{
+				if (!digestOID.Equals(DigestSha1))
+					throw new ArgumentException("can't mix DSA with anything but SHA1");
+
+				encOID = EncryptionDsa;
+			}
+			else if (key is ECPrivateKeyParameters)
+			{
+				ECPrivateKeyParameters ecPrivKey = (ECPrivateKeyParameters) key;
+				string algName = ecPrivKey.AlgorithmName;
+
+				if (algName == "ECGOST3410")
+				{
+					encOID = EncryptionECGost3410;
+				}
+				else
+				{
+					// TODO Should we insist on algName being one of "EC" or "ECDSA", as Java does?
+					encOID = (string) ecAlgorithms[digestOID];
+
+					if (encOID == null)
+						throw new ArgumentException("can't mix ECDSA with anything but SHA family digests");
+				}
+			}
+			else if (key is Gost3410PrivateKeyParameters)
+			{
+				encOID = EncryptionGost3410;
+			}
+			else
+			{
+				throw new ArgumentException("Unknown algorithm in CmsSignedGenerator.GetEncOid");
+			}
+
+			return encOID;
+        }
+
+		internal static AlgorithmIdentifier GetEncAlgorithmIdentifier(
+			DerObjectIdentifier	encOid,
+			Asn1Encodable		sigX509Parameters)
+		{
+			if (noParams.Contains(encOid.Id))
+			{
+				return new AlgorithmIdentifier(encOid);
+			}
+
+			return new AlgorithmIdentifier(encOid, sigX509Parameters);
+		}
+
+		internal protected virtual IDictionary GetBaseParameters(
+			DerObjectIdentifier	contentType,
+			AlgorithmIdentifier	digAlgId,
+			byte[]				hash)
+		{
+			IDictionary param = Platform.CreateHashtable();
+
+            if (contentType != null)
+            {
+                param[CmsAttributeTableParameter.ContentType] = contentType;
+            }
+
+			param[CmsAttributeTableParameter.DigestAlgorithmIdentifier] = digAlgId;
+            param[CmsAttributeTableParameter.Digest] = hash.Clone();
+
+            return param;
+		}
+
+		internal protected virtual Asn1Set GetAttributeSet(
+            Asn1.Cms.AttributeTable attr)
+        {
+			return attr == null
+				?	null
+				:	new DerSet(attr.ToAsn1EncodableVector());
+        }
+
+		public void AddCertificates(
+			IX509Store certStore)
+		{
+            CollectionUtilities.AddRange(_certs, CmsUtilities.GetCertificatesFromStore(certStore));
+        }
+
+		public void AddCrls(
+			IX509Store crlStore)
+		{
+            CollectionUtilities.AddRange(_crls, CmsUtilities.GetCrlsFromStore(crlStore));
+		}
+
+		/**
+		* Add the attribute certificates contained in the passed in store to the
+		* generator.
+		*
+		* @param store a store of Version 2 attribute certificates
+		* @throws CmsException if an error occurse processing the store.
+		*/
+		public void AddAttributeCertificates(
+			IX509Store store)
+		{
+			try
+			{
+				foreach (IX509AttributeCertificate attrCert in store.GetMatches(null))
+				{
+					_certs.Add(new DerTaggedObject(false, 2,
+						AttributeCertificate.GetInstance(Asn1Object.FromByteArray(attrCert.GetEncoded()))));
+				}
+			}
+			catch (Exception e)
+			{
+				throw new CmsException("error processing attribute certs", e);
+			}
+		}
+
+		/**
+		 * Add a store of precalculated signers to the generator.
+		 *
+		 * @param signerStore store of signers
+		 */
+		public void AddSigners(
+			SignerInformationStore signerStore)
+		{
+			foreach (SignerInformation o in signerStore.GetSigners())
+			{
+				_signers.Add(o);
+				AddSignerCallback(o);
+			}
+		}
+
+		/**
+		 * Return a map of oids and byte arrays representing the digests calculated on the content during
+		 * the last generate.
+		 *
+		 * @return a map of oids (as String objects) and byte[] representing digests.
+		 */
+		public IDictionary GetGeneratedDigests()
+		{
+			return Platform.CreateHashtable(_digests);
+		}
+
+		internal virtual void AddSignerCallback(
+			SignerInformation si)
+		{
+		}
+
+		internal static SignerIdentifier GetSignerIdentifier(X509Certificate cert)
+		{
+			return new SignerIdentifier(CmsUtilities.GetIssuerAndSerialNumber(cert));
+		}
+
+		internal static SignerIdentifier GetSignerIdentifier(byte[] subjectKeyIdentifier)
+		{
+			return new SignerIdentifier(new DerOctetString(subjectKeyIdentifier));    
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSSignedHelper.cs b/Crypto/src/cms/CMSSignedHelper.cs
new file mode 100644
index 000000000..b3406fc06
--- /dev/null
+++ b/Crypto/src/cms/CMSSignedHelper.cs
@@ -0,0 +1,319 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.CryptoPro;
+using Org.BouncyCastle.Asn1.Eac;
+using Org.BouncyCastle.Asn1.Iana;
+using Org.BouncyCastle.Asn1.Misc;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.TeleTrust;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+    internal class CmsSignedHelper
+    {
+        internal static readonly CmsSignedHelper Instance = new CmsSignedHelper();
+
+		private static readonly IDictionary encryptionAlgs = Platform.CreateHashtable();
+        private static readonly IDictionary digestAlgs = Platform.CreateHashtable();
+        private static readonly IDictionary digestAliases = Platform.CreateHashtable();
+
+		private static void AddEntries(DerObjectIdentifier oid, string digest, string encryption)
+		{
+			string alias = oid.Id;
+			digestAlgs.Add(alias, digest);
+			encryptionAlgs.Add(alias, encryption);
+		}
+
+		static CmsSignedHelper()
+		{
+			AddEntries(NistObjectIdentifiers.DsaWithSha224, "SHA224", "DSA");
+			AddEntries(NistObjectIdentifiers.DsaWithSha256, "SHA256", "DSA");
+			AddEntries(NistObjectIdentifiers.DsaWithSha384, "SHA384", "DSA");
+			AddEntries(NistObjectIdentifiers.DsaWithSha512, "SHA512", "DSA");
+			AddEntries(OiwObjectIdentifiers.DsaWithSha1, "SHA1", "DSA");
+			AddEntries(OiwObjectIdentifiers.MD4WithRsa, "MD4", "RSA");
+			AddEntries(OiwObjectIdentifiers.MD4WithRsaEncryption, "MD4", "RSA");
+			AddEntries(OiwObjectIdentifiers.MD5WithRsa, "MD5", "RSA");
+			AddEntries(OiwObjectIdentifiers.Sha1WithRsa, "SHA1", "RSA");
+			AddEntries(PkcsObjectIdentifiers.MD2WithRsaEncryption, "MD2", "RSA");
+			AddEntries(PkcsObjectIdentifiers.MD4WithRsaEncryption, "MD4", "RSA");
+			AddEntries(PkcsObjectIdentifiers.MD5WithRsaEncryption, "MD5", "RSA");
+			AddEntries(PkcsObjectIdentifiers.Sha1WithRsaEncryption, "SHA1", "RSA");
+			AddEntries(PkcsObjectIdentifiers.Sha224WithRsaEncryption, "SHA224", "RSA");
+			AddEntries(PkcsObjectIdentifiers.Sha256WithRsaEncryption, "SHA256", "RSA");
+			AddEntries(PkcsObjectIdentifiers.Sha384WithRsaEncryption, "SHA384", "RSA");
+			AddEntries(PkcsObjectIdentifiers.Sha512WithRsaEncryption, "SHA512", "RSA");
+			AddEntries(X9ObjectIdentifiers.ECDsaWithSha1, "SHA1", "ECDSA");
+			AddEntries(X9ObjectIdentifiers.ECDsaWithSha224, "SHA224", "ECDSA");
+			AddEntries(X9ObjectIdentifiers.ECDsaWithSha256, "SHA256", "ECDSA");
+			AddEntries(X9ObjectIdentifiers.ECDsaWithSha384, "SHA384", "ECDSA");
+			AddEntries(X9ObjectIdentifiers.ECDsaWithSha512, "SHA512", "ECDSA");
+			AddEntries(X9ObjectIdentifiers.IdDsaWithSha1, "SHA1", "DSA");
+			AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_1, "SHA1", "ECDSA");
+			AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_224, "SHA224", "ECDSA");
+			AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_256, "SHA256", "ECDSA");
+			AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_384, "SHA384", "ECDSA");
+			AddEntries(EacObjectIdentifiers.id_TA_ECDSA_SHA_512, "SHA512", "ECDSA");
+			AddEntries(EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, "SHA1", "RSA");
+			AddEntries(EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, "SHA256", "RSA");
+			AddEntries(EacObjectIdentifiers.id_TA_RSA_PSS_SHA_1, "SHA1", "RSAandMGF1");
+			AddEntries(EacObjectIdentifiers.id_TA_RSA_PSS_SHA_256, "SHA256", "RSAandMGF1");
+
+			encryptionAlgs.Add(X9ObjectIdentifiers.IdDsa.Id, "DSA");
+			encryptionAlgs.Add(PkcsObjectIdentifiers.RsaEncryption.Id, "RSA");
+			encryptionAlgs.Add(TeleTrusTObjectIdentifiers.TeleTrusTRsaSignatureAlgorithm, "RSA");
+			encryptionAlgs.Add(X509ObjectIdentifiers.IdEARsa.Id, "RSA");
+			encryptionAlgs.Add(CmsSignedGenerator.EncryptionRsaPss, "RSAandMGF1");
+			encryptionAlgs.Add(CryptoProObjectIdentifiers.GostR3410x94.Id, "GOST3410");
+			encryptionAlgs.Add(CryptoProObjectIdentifiers.GostR3410x2001.Id, "ECGOST3410");
+			encryptionAlgs.Add("1.3.6.1.4.1.5849.1.6.2", "ECGOST3410");
+			encryptionAlgs.Add("1.3.6.1.4.1.5849.1.1.5", "GOST3410");
+
+			digestAlgs.Add(PkcsObjectIdentifiers.MD2.Id, "MD2");
+			digestAlgs.Add(PkcsObjectIdentifiers.MD4.Id, "MD4");
+			digestAlgs.Add(PkcsObjectIdentifiers.MD5.Id, "MD5");
+			digestAlgs.Add(OiwObjectIdentifiers.IdSha1.Id, "SHA1");
+			digestAlgs.Add(NistObjectIdentifiers.IdSha224.Id, "SHA224");
+			digestAlgs.Add(NistObjectIdentifiers.IdSha256.Id, "SHA256");
+			digestAlgs.Add(NistObjectIdentifiers.IdSha384.Id, "SHA384");
+			digestAlgs.Add(NistObjectIdentifiers.IdSha512.Id, "SHA512");
+			digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD128.Id, "RIPEMD128");
+			digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD160.Id, "RIPEMD160");
+			digestAlgs.Add(TeleTrusTObjectIdentifiers.RipeMD256.Id, "RIPEMD256");
+			digestAlgs.Add(CryptoProObjectIdentifiers.GostR3411.Id,  "GOST3411");
+			digestAlgs.Add("1.3.6.1.4.1.5849.1.2.1",  "GOST3411");
+
+			digestAliases.Add("SHA1", new string[] { "SHA-1" });
+			digestAliases.Add("SHA224", new string[] { "SHA-224" });
+			digestAliases.Add("SHA256", new string[] { "SHA-256" });
+			digestAliases.Add("SHA384", new string[] { "SHA-384" });
+			digestAliases.Add("SHA512", new string[] { "SHA-512" });
+		}
+
+		/**
+        * Return the digest algorithm using one of the standard JCA string
+        * representations rather than the algorithm identifier (if possible).
+        */
+        internal string GetDigestAlgName(
+            string digestAlgOid)
+        {
+			string algName = (string)digestAlgs[digestAlgOid];
+
+			if (algName != null)
+			{
+				return algName;
+			}
+
+			return digestAlgOid;
+        }
+
+		internal string[] GetDigestAliases(
+			string algName)
+		{
+			string[] aliases = (string[]) digestAliases[algName];
+
+			return aliases == null ? new String[0] : (string[]) aliases.Clone();
+		}
+
+		/**
+        * Return the digest encryption algorithm using one of the standard
+        * JCA string representations rather than the algorithm identifier (if
+        * possible).
+        */
+        internal string GetEncryptionAlgName(
+            string encryptionAlgOid)
+        {
+			string algName = (string) encryptionAlgs[encryptionAlgOid];
+
+			if (algName != null)
+			{
+				return algName;
+			}
+
+			return encryptionAlgOid;
+        }
+
+		internal IDigest GetDigestInstance(
+			string algorithm)
+		{
+			try
+			{
+				return DigestUtilities.GetDigest(algorithm);
+			}
+			catch (SecurityUtilityException e)
+			{
+				// This is probably superfluous on C#, since no provider infrastructure,
+				// assuming DigestUtilities already knows all the aliases
+				foreach (string alias in GetDigestAliases(algorithm))
+				{
+					try { return DigestUtilities.GetDigest(alias); }
+					catch (SecurityUtilityException) {}
+				}
+				throw e;
+			}
+		}
+
+		internal ISigner GetSignatureInstance(
+			string algorithm)
+		{
+			return SignerUtilities.GetSigner(algorithm);
+		}
+
+		internal IX509Store CreateAttributeStore(
+			string	type,
+			Asn1Set	certSet)
+		{
+			IList certs = Platform.CreateArrayList();
+
+			if (certSet != null)
+			{
+				foreach (Asn1Encodable ae in certSet)
+				{
+					try
+					{
+						Asn1Object obj = ae.ToAsn1Object();
+
+						if (obj is Asn1TaggedObject)
+						{
+							Asn1TaggedObject tagged = (Asn1TaggedObject)obj;
+
+							if (tagged.TagNo == 2)
+							{
+								certs.Add(
+									new X509V2AttributeCertificate(
+										Asn1Sequence.GetInstance(tagged, false).GetEncoded()));
+							}
+						}
+					}
+					catch (Exception ex)
+					{
+						throw new CmsException("can't re-encode attribute certificate!", ex);
+					}
+				}
+			}
+
+			try
+			{
+				return X509StoreFactory.Create(
+					"AttributeCertificate/" + type,
+					new X509CollectionStoreParameters(certs));
+			}
+			catch (ArgumentException e)
+			{
+				throw new CmsException("can't setup the X509Store", e);
+			}
+		}
+
+		internal IX509Store CreateCertificateStore(
+			string	type,
+			Asn1Set	certSet)
+		{
+			IList certs = Platform.CreateArrayList();
+
+			if (certSet != null)
+			{
+				AddCertsFromSet(certs, certSet);
+			}
+
+			try
+			{
+				return X509StoreFactory.Create(
+					"Certificate/" + type,
+					new X509CollectionStoreParameters(certs));
+			}
+			catch (ArgumentException e)
+			{
+				throw new CmsException("can't setup the X509Store", e);
+			}
+		}
+
+		internal IX509Store CreateCrlStore(
+			string	type,
+			Asn1Set	crlSet)
+		{
+			IList crls = Platform.CreateArrayList();
+
+			if (crlSet != null)
+			{
+				AddCrlsFromSet(crls, crlSet);
+			}
+
+			try
+			{
+				return X509StoreFactory.Create(
+					"CRL/" + type,
+					new X509CollectionStoreParameters(crls));
+			}
+			catch (ArgumentException e)
+			{
+				throw new CmsException("can't setup the X509Store", e);
+			}
+		}
+
+		private void AddCertsFromSet(
+			IList	certs,
+			Asn1Set	certSet)
+		{
+			X509CertificateParser cf = new X509CertificateParser();
+
+			foreach (Asn1Encodable ae in certSet)
+			{
+				try
+				{
+					Asn1Object obj = ae.ToAsn1Object();
+
+					if (obj is Asn1Sequence)
+					{
+						// TODO Build certificate directly from sequence?
+						certs.Add(cf.ReadCertificate(obj.GetEncoded()));
+					}
+				}
+				catch (Exception ex)
+				{
+					throw new CmsException("can't re-encode certificate!", ex);
+				}
+			}
+		}
+
+		private void AddCrlsFromSet(
+			IList	crls,
+			Asn1Set	crlSet)
+		{
+			X509CrlParser cf = new X509CrlParser();
+
+			foreach (Asn1Encodable ae in crlSet)
+			{
+				try
+				{
+					// TODO Build CRL directly from ae.ToAsn1Object()?
+					crls.Add(cf.ReadCrl(ae.GetEncoded()));
+				}
+				catch (Exception ex)
+				{
+					throw new CmsException("can't re-encode CRL!", ex);
+				}
+			}
+		}
+
+		internal AlgorithmIdentifier FixAlgID(
+			AlgorithmIdentifier algId)
+		{
+			if (algId.Parameters == null)
+				return new AlgorithmIdentifier(algId.ObjectID, DerNull.Instance);
+
+			return algId;
+		}
+    }
+}
diff --git a/Crypto/src/cms/CMSStreamException.cs b/Crypto/src/cms/CMSStreamException.cs
new file mode 100644
index 000000000..1626fb8b4
--- /dev/null
+++ b/Crypto/src/cms/CMSStreamException.cs
@@ -0,0 +1,26 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+    public class CmsStreamException
+        : IOException
+    {
+		public CmsStreamException()
+		{
+		}
+
+		public CmsStreamException(
+			string name)
+			: base(name)
+        {
+        }
+
+		public CmsStreamException(
+			string		name,
+			Exception	e)
+			: base(name, e)
+        {
+        }
+    }
+}
diff --git a/Crypto/src/cms/CMSTypedStream.cs b/Crypto/src/cms/CMSTypedStream.cs
new file mode 100644
index 000000000..576edf234
--- /dev/null
+++ b/Crypto/src/cms/CMSTypedStream.cs
@@ -0,0 +1,72 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	public class CmsTypedStream
+	{
+		private const int BufferSize = 32 * 1024;
+
+		private readonly string	_oid;
+		private readonly Stream	_in;
+
+		public CmsTypedStream(
+			Stream inStream)
+			: this(PkcsObjectIdentifiers.Data.Id, inStream, BufferSize)
+		{
+		}
+
+		public CmsTypedStream(
+			string oid,
+			Stream inStream)
+			: this(oid, inStream, BufferSize)
+		{
+		}
+
+		public CmsTypedStream(
+			string	oid,
+			Stream	inStream,
+			int		bufSize)
+		{
+			_oid = oid;
+#if NETCF_1_0 || NETCF_2_0 || SILVERLIGHT || PORTABLE
+			_in = new FullReaderStream(inStream);
+#else
+			_in = new FullReaderStream(new BufferedStream(inStream, bufSize));
+#endif
+		}
+
+		public string ContentType
+		{
+			get { return _oid; }
+		}
+
+		public Stream ContentStream
+		{
+			get { return _in; }
+		}
+
+		public void Drain()
+		{
+			Streams.Drain(_in);
+            _in.Dispose();
+		}
+
+		private class FullReaderStream : FilterStream
+		{
+			internal FullReaderStream(Stream input)
+				: base(input)
+			{
+			}
+
+			public override int Read(byte[]	buf, int off, int len)
+			{
+				return Streams.ReadFully(base.s, buf, off, len);
+			}
+		}
+	}
+}
diff --git a/Crypto/src/cms/CMSUtils.cs b/Crypto/src/cms/CMSUtils.cs
new file mode 100644
index 000000000..95d710607
--- /dev/null
+++ b/Crypto/src/cms/CMSUtils.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Security.Certificates;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+    internal class CmsUtilities
+    {
+		// TODO Is there a .NET equivalent to this?
+//		private static readonly Runtime RUNTIME = Runtime.getRuntime();
+
+		internal static int MaximumMemory
+		{
+			get
+			{
+				// TODO Is there a .NET equivalent to this?
+				long maxMem = int.MaxValue;//RUNTIME.maxMemory();
+
+				if (maxMem > int.MaxValue)
+				{
+					return int.MaxValue;
+				}
+
+				return (int)maxMem;
+			}
+		}
+
+		internal static ContentInfo ReadContentInfo(
+			byte[] input)
+		{
+			// enforce limit checking as from a byte array
+			return ReadContentInfo(new Asn1InputStream(input));
+		}
+
+		internal static ContentInfo ReadContentInfo(
+			Stream input)
+		{
+			// enforce some limit checking
+			return ReadContentInfo(new Asn1InputStream(input, MaximumMemory));
+		}
+
+		private static ContentInfo ReadContentInfo(
+			Asn1InputStream aIn)
+		{
+			try
+			{
+				return ContentInfo.GetInstance(aIn.ReadObject());
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("IOException reading content.", e);
+			}
+			catch (InvalidCastException e)
+			{
+				throw new CmsException("Malformed content.", e);
+			}
+			catch (ArgumentException e)
+			{
+				throw new CmsException("Malformed content.", e);
+			}
+		}
+
+		public static byte[] StreamToByteArray(
+            Stream inStream)
+        {
+			return Streams.ReadAll(inStream);
+        }
+
+		public static byte[] StreamToByteArray(
+            Stream	inStream,
+			int		limit)
+        {
+			return Streams.ReadAllLimited(inStream, limit);
+        }
+
+		public static IList GetCertificatesFromStore(
+			IX509Store certStore)
+		{
+			try
+			{
+				IList certs = Platform.CreateArrayList();
+
+				if (certStore != null)
+				{
+					foreach (X509Certificate c in certStore.GetMatches(null))
+					{
+						certs.Add(
+							X509CertificateStructure.GetInstance(
+								Asn1Object.FromByteArray(c.GetEncoded())));
+					}
+				}
+
+				return certs;
+			}
+			catch (CertificateEncodingException e)
+			{
+				throw new CmsException("error encoding certs", e);
+			}
+			catch (Exception e)
+			{
+				throw new CmsException("error processing certs", e);
+			}
+		}
+
+		public static IList GetCrlsFromStore(
+			IX509Store crlStore)
+		{
+			try
+			{
+                IList crls = Platform.CreateArrayList();
+
+				if (crlStore != null)
+				{
+					foreach (X509Crl c in crlStore.GetMatches(null))
+					{
+						crls.Add(
+							CertificateList.GetInstance(
+								Asn1Object.FromByteArray(c.GetEncoded())));
+					}
+				}
+
+				return crls;
+			}
+			catch (CrlException e)
+			{
+				throw new CmsException("error encoding crls", e);
+			}
+			catch (Exception e)
+			{
+				throw new CmsException("error processing crls", e);
+			}
+		}
+
+		public static Asn1Set CreateBerSetFromList(
+			IList berObjects)
+		{
+			Asn1EncodableVector v = new Asn1EncodableVector();
+
+			foreach (Asn1Encodable ae in berObjects)
+			{
+				v.Add(ae);
+			}
+
+			return new BerSet(v);
+		}
+
+		public static Asn1Set CreateDerSetFromList(
+			IList derObjects)
+		{
+			Asn1EncodableVector v = new Asn1EncodableVector();
+
+			foreach (Asn1Encodable ae in derObjects)
+			{
+				v.Add(ae);
+			}
+
+			return new DerSet(v);
+		}
+
+		internal static Stream CreateBerOctetOutputStream(Stream s, int tagNo, bool isExplicit, int bufferSize)
+		{
+			BerOctetStringGenerator octGen = new BerOctetStringGenerator(s, tagNo, isExplicit);
+			return octGen.GetOctetOutputStream(bufferSize);
+		}
+
+		internal static TbsCertificateStructure GetTbsCertificateStructure(X509Certificate cert)
+		{
+			return TbsCertificateStructure.GetInstance(Asn1Object.FromByteArray(cert.GetTbsCertificate()));
+		}
+
+		internal static IssuerAndSerialNumber GetIssuerAndSerialNumber(X509Certificate cert)
+		{
+			TbsCertificateStructure tbsCert = GetTbsCertificateStructure(cert);
+			return new IssuerAndSerialNumber(tbsCert.Issuer, tbsCert.SerialNumber.Value);
+		}
+	}
+}
diff --git a/Crypto/src/cms/CounterSignatureDigestCalculator.cs b/Crypto/src/cms/CounterSignatureDigestCalculator.cs
new file mode 100644
index 000000000..6f8bf65a2
--- /dev/null
+++ b/Crypto/src/cms/CounterSignatureDigestCalculator.cs
@@ -0,0 +1,28 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class CounterSignatureDigestCalculator
+		: IDigestCalculator
+	{
+		private readonly string alg;
+		private readonly byte[] data;
+
+		internal CounterSignatureDigestCalculator(
+			string	alg,
+			byte[]	data)
+		{
+			this.alg = alg;
+			this.data = data;
+		}
+
+		public byte[] GetDigest()
+		{
+			IDigest digest = CmsSignedHelper.Instance.GetDigestInstance(alg);
+			return DigestUtilities.DoFinal(digest, data);
+		}
+	}
+}
diff --git a/Crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs b/Crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs
new file mode 100644
index 000000000..d49b1d9d2
--- /dev/null
+++ b/Crypto/src/cms/DefaultAuthenticatedAttributeTableGenerator.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	 * Default authenticated attributes generator.
+	 */
+	public class DefaultAuthenticatedAttributeTableGenerator
+		: CmsAttributeTableGenerator
+	{
+		private readonly IDictionary table;
+
+		/**
+		 * Initialise to use all defaults
+		 */
+		public DefaultAuthenticatedAttributeTableGenerator()
+		{
+			table = Platform.CreateHashtable();
+		}
+
+		/**
+		 * Initialise with some extra attributes or overrides.
+		 *
+		 * @param attributeTable initial attribute table to use.
+		 */
+		public DefaultAuthenticatedAttributeTableGenerator(
+			AttributeTable attributeTable)
+		{
+			if (attributeTable != null)
+			{
+				table = attributeTable.ToDictionary();
+			}
+			else
+			{
+				table = Platform.CreateHashtable();
+			}
+		}
+
+		/**
+		 * Create a standard attribute table from the passed in parameters - this will
+		 * normally include contentType and messageDigest. If the constructor
+		 * using an AttributeTable was used, entries in it for contentType and
+		 * messageDigest will override the generated ones.
+		 *
+		 * @param parameters source parameters for table generation.
+		 *
+		 * @return a filled in IDictionary of attributes.
+		 */
+		protected virtual IDictionary CreateStandardAttributeTable(
+			IDictionary parameters)
+		{
+            IDictionary std = Platform.CreateHashtable(table);
+
+			if (!std.Contains(CmsAttributes.ContentType))
+            {
+                DerObjectIdentifier contentType = (DerObjectIdentifier)
+                    parameters[CmsAttributeTableParameter.ContentType];
+                Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.ContentType,
+                    new DerSet(contentType));
+                std[attr.AttrType] = attr;
+            }
+
+			if (!std.Contains(CmsAttributes.MessageDigest))
+            {
+                byte[] messageDigest = (byte[])parameters[CmsAttributeTableParameter.Digest];
+                Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.MessageDigest,
+                    new DerSet(new DerOctetString(messageDigest)));
+                std[attr.AttrType] = attr;
+            }
+
+            return std;
+		}
+
+        /**
+		 * @param parameters source parameters
+		 * @return the populated attribute table
+		 */
+		public virtual AttributeTable GetAttributes(
+			IDictionary parameters)
+		{
+            IDictionary table = CreateStandardAttributeTable(parameters);
+			return new AttributeTable(table);
+		}
+	}
+}
diff --git a/Crypto/src/cms/DefaultSignedAttributeTableGenerator.cs b/Crypto/src/cms/DefaultSignedAttributeTableGenerator.cs
new file mode 100644
index 000000000..0e93392d9
--- /dev/null
+++ b/Crypto/src/cms/DefaultSignedAttributeTableGenerator.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	 * Default signed attributes generator.
+	 */
+	public class DefaultSignedAttributeTableGenerator
+		: CmsAttributeTableGenerator
+	{
+		private readonly IDictionary table;
+
+		/**
+		 * Initialise to use all defaults
+		 */
+		public DefaultSignedAttributeTableGenerator()
+		{
+			table = Platform.CreateHashtable();
+		}
+
+		/**
+		 * Initialise with some extra attributes or overrides.
+		 *
+		 * @param attributeTable initial attribute table to use.
+		 */
+		public DefaultSignedAttributeTableGenerator(
+			AttributeTable attributeTable)
+		{
+			if (attributeTable != null)
+			{
+				table = attributeTable.ToDictionary();
+			}
+			else
+			{
+				table = Platform.CreateHashtable();
+			}
+		}
+
+#if (SILVERLIGHT || PORTABLE)
+        /**
+		 * Create a standard attribute table from the passed in parameters - this will
+		 * normally include contentType, signingTime, and messageDigest. If the constructor
+		 * using an AttributeTable was used, entries in it for contentType, signingTime, and
+		 * messageDigest will override the generated ones.
+		 *
+		 * @param parameters source parameters for table generation.
+		 *
+		 * @return a filled in Hashtable of attributes.
+		 */
+		protected virtual IDictionary createStandardAttributeTable(
+			IDictionary parameters)
+		{
+            IDictionary std = Platform.CreateHashtable(table);
+            DoCreateStandardAttributeTable(parameters, std);
+            return std;
+		}
+#else
+        /**
+		 * Create a standard attribute table from the passed in parameters - this will
+		 * normally include contentType, signingTime, and messageDigest. If the constructor
+		 * using an AttributeTable was used, entries in it for contentType, signingTime, and
+		 * messageDigest will override the generated ones.
+		 *
+		 * @param parameters source parameters for table generation.
+		 *
+		 * @return a filled in Hashtable of attributes.
+		 */
+		protected virtual Hashtable createStandardAttributeTable(
+			IDictionary parameters)
+		{
+            Hashtable std = new Hashtable(table);
+            DoCreateStandardAttributeTable(parameters, std);
+			return std;
+		}
+#endif
+
+        private void DoCreateStandardAttributeTable(IDictionary parameters, IDictionary std)
+        {
+            // contentType will be absent if we're trying to generate a counter signature.
+            if (parameters.Contains(CmsAttributeTableParameter.ContentType))
+            {
+                if (!std.Contains(CmsAttributes.ContentType))
+                {
+                    DerObjectIdentifier contentType = (DerObjectIdentifier)
+                        parameters[CmsAttributeTableParameter.ContentType];
+                    Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.ContentType,
+                        new DerSet(contentType));
+                    std[attr.AttrType] = attr;
+                }
+            }
+
+            if (!std.Contains(CmsAttributes.SigningTime))
+            {
+                Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.SigningTime,
+                    new DerSet(new Time(DateTime.UtcNow)));
+                std[attr.AttrType] = attr;
+            }
+
+            if (!std.Contains(CmsAttributes.MessageDigest))
+            {
+                byte[] messageDigest = (byte[])parameters[CmsAttributeTableParameter.Digest];
+                Asn1.Cms.Attribute attr = new Asn1.Cms.Attribute(CmsAttributes.MessageDigest,
+                    new DerSet(new DerOctetString(messageDigest)));
+                std[attr.AttrType] = attr;
+            }
+        }
+
+        /**
+		 * @param parameters source parameters
+		 * @return the populated attribute table
+		 */
+		public virtual AttributeTable GetAttributes(
+			IDictionary parameters)
+		{
+            IDictionary table = createStandardAttributeTable(parameters);
+			return new AttributeTable(table);
+		}
+	}
+}
diff --git a/Crypto/src/cms/DigOutputStream.cs b/Crypto/src/cms/DigOutputStream.cs
new file mode 100644
index 000000000..103b45cac
--- /dev/null
+++ b/Crypto/src/cms/DigOutputStream.cs
@@ -0,0 +1,28 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class DigOutputStream
+		: BaseOutputStream
+	{
+		private readonly IDigest dig;
+
+		internal DigOutputStream(IDigest dig)
+		{
+			this.dig = dig;
+		}
+
+		public override void WriteByte(byte b)
+		{
+			dig.Update(b);
+		}
+
+		public override void Write(byte[] b, int off, int len)
+		{
+			dig.BlockUpdate(b, off, len);
+		}
+	}
+}
diff --git a/Crypto/src/cms/IDigestCalculator.cs b/Crypto/src/cms/IDigestCalculator.cs
new file mode 100644
index 000000000..3661e4023
--- /dev/null
+++ b/Crypto/src/cms/IDigestCalculator.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal interface IDigestCalculator
+	{
+		byte[] GetDigest();
+	}
+}
diff --git a/Crypto/src/cms/KEKRecipientInfoGenerator.cs b/Crypto/src/cms/KEKRecipientInfoGenerator.cs
new file mode 100644
index 000000000..a9bedade6
--- /dev/null
+++ b/Crypto/src/cms/KEKRecipientInfoGenerator.cs
@@ -0,0 +1,137 @@
+using System;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Kisa;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Ntt;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class KekRecipientInfoGenerator : RecipientInfoGenerator
+	{
+		private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance;
+
+		private KeyParameter	keyEncryptionKey;
+		// TODO Can get this from keyEncryptionKey?		
+		private string			keyEncryptionKeyOID;
+		private KekIdentifier	kekIdentifier;
+
+		// Derived
+		private AlgorithmIdentifier keyEncryptionAlgorithm;
+
+		internal KekRecipientInfoGenerator()
+		{
+		}
+
+		internal KekIdentifier KekIdentifier
+		{
+			set { this.kekIdentifier = value; }
+		}
+
+		internal KeyParameter KeyEncryptionKey
+		{
+			set
+			{
+				this.keyEncryptionKey = value;
+				this.keyEncryptionAlgorithm = DetermineKeyEncAlg(keyEncryptionKeyOID, keyEncryptionKey);
+			}
+		}
+
+		internal string KeyEncryptionKeyOID
+		{
+			set { this.keyEncryptionKeyOID = value; }
+		}
+
+		public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random)
+		{
+			byte[] keyBytes = contentEncryptionKey.GetKey();
+
+			IWrapper keyWrapper = Helper.CreateWrapper(keyEncryptionAlgorithm.ObjectID.Id);
+			keyWrapper.Init(true, new ParametersWithRandom(keyEncryptionKey, random));
+        	Asn1OctetString encryptedKey = new DerOctetString(
+				keyWrapper.Wrap(keyBytes, 0, keyBytes.Length));
+
+			return new RecipientInfo(new KekRecipientInfo(kekIdentifier, keyEncryptionAlgorithm, encryptedKey));
+		}
+
+		private static AlgorithmIdentifier DetermineKeyEncAlg(
+			string algorithm, KeyParameter key)
+		{
+			if (algorithm.StartsWith("DES"))
+			{
+				return new AlgorithmIdentifier(
+					PkcsObjectIdentifiers.IdAlgCms3DesWrap,
+					DerNull.Instance);
+			}
+			else if (algorithm.StartsWith("RC2"))
+			{
+				return new AlgorithmIdentifier(
+					PkcsObjectIdentifiers.IdAlgCmsRC2Wrap,
+					new DerInteger(58));
+			}
+			else if (algorithm.StartsWith("AES"))
+			{
+				int length = key.GetKey().Length * 8;
+				DerObjectIdentifier wrapOid;
+
+				if (length == 128)
+				{
+					wrapOid = NistObjectIdentifiers.IdAes128Wrap;
+				}
+				else if (length == 192)
+				{
+					wrapOid = NistObjectIdentifiers.IdAes192Wrap;
+				}
+				else if (length == 256)
+				{
+					wrapOid = NistObjectIdentifiers.IdAes256Wrap;
+				}
+				else
+				{
+					throw new ArgumentException("illegal keysize in AES");
+				}
+
+				return new AlgorithmIdentifier(wrapOid);  // parameters absent
+			}
+			else if (algorithm.StartsWith("SEED"))
+			{
+				// parameters absent
+				return new AlgorithmIdentifier(KisaObjectIdentifiers.IdNpkiAppCmsSeedWrap);
+			}
+			else if (algorithm.StartsWith("CAMELLIA"))
+			{
+				int length = key.GetKey().Length * 8;
+				DerObjectIdentifier wrapOid;
+
+				if (length == 128)
+				{
+					wrapOid = NttObjectIdentifiers.IdCamellia128Wrap;
+				}
+				else if (length == 192)
+				{
+					wrapOid = NttObjectIdentifiers.IdCamellia192Wrap;
+				}
+				else if (length == 256)
+				{
+					wrapOid = NttObjectIdentifiers.IdCamellia256Wrap;
+				}
+				else
+				{
+					throw new ArgumentException("illegal keysize in Camellia");
+				}
+
+				return new AlgorithmIdentifier(wrapOid); // parameters must be absent
+			}
+			else
+			{
+				throw new ArgumentException("unknown algorithm");
+			}
+		}
+	}
+}
diff --git a/Crypto/src/cms/KEKRecipientInformation.cs b/Crypto/src/cms/KEKRecipientInformation.cs
new file mode 100644
index 000000000..f960197d6
--- /dev/null
+++ b/Crypto/src/cms/KEKRecipientInformation.cs
@@ -0,0 +1,62 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * the RecipientInfo class for a recipient who has been sent a message
+    * encrypted using a secret key known to the other side.
+    */
+    public class KekRecipientInformation
+        : RecipientInformation
+    {
+        private KekRecipientInfo info;
+
+		internal KekRecipientInformation(
+			KekRecipientInfo	info,
+			CmsSecureReadable	secureReadable)
+			: base(info.KeyEncryptionAlgorithm, secureReadable)
+		{
+            this.info = info;
+            this.rid = new RecipientID();
+
+			KekIdentifier kekId = info.KekID;
+
+			rid.KeyIdentifier = kekId.KeyIdentifier.GetOctets();
+        }
+
+		/**
+        * decrypt the content and return an input stream.
+        */
+        public override CmsTypedStream GetContentStream(
+            ICipherParameters key)
+        {
+			try
+			{
+				byte[] encryptedKey = info.EncryptedKey.GetOctets();
+				IWrapper keyWrapper = WrapperUtilities.GetWrapper(keyEncAlg.ObjectID.Id);
+
+				keyWrapper.Init(false, key);
+
+				KeyParameter sKey = ParameterUtilities.CreateKeyParameter(
+					GetContentAlgorithmName(), keyWrapper.Unwrap(encryptedKey, 0, encryptedKey.Length));
+
+				return GetContentFromSessionKey(sKey);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+        }
+    }
+}
diff --git a/Crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs b/Crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs
new file mode 100644
index 000000000..4fafb7c6e
--- /dev/null
+++ b/Crypto/src/cms/KeyAgreeRecipientInfoGenerator.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Cms.Ecc;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class KeyAgreeRecipientInfoGenerator : RecipientInfoGenerator
+	{
+		private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance;
+
+		private DerObjectIdentifier			keyAgreementOID;
+		private DerObjectIdentifier			keyEncryptionOID;
+		private IList					    recipientCerts;
+		private AsymmetricCipherKeyPair		senderKeyPair;
+
+		internal KeyAgreeRecipientInfoGenerator()
+		{
+		}
+
+		internal DerObjectIdentifier KeyAgreementOID
+		{
+			set { this.keyAgreementOID = value; }
+		}
+
+		internal DerObjectIdentifier KeyEncryptionOID
+		{
+			set { this.keyEncryptionOID = value; }
+		}
+
+		internal ICollection RecipientCerts
+		{
+			set { this.recipientCerts = Platform.CreateArrayList(value); }
+		}
+
+		internal AsymmetricCipherKeyPair SenderKeyPair
+		{
+			set { this.senderKeyPair = value; }
+		}
+
+		public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random)
+		{
+			byte[] keyBytes = contentEncryptionKey.GetKey();
+
+			AsymmetricKeyParameter senderPublicKey = senderKeyPair.Public;
+			ICipherParameters senderPrivateParams = senderKeyPair.Private;
+
+
+			OriginatorIdentifierOrKey originator;
+			try
+			{
+				originator = new OriginatorIdentifierOrKey(
+					CreateOriginatorPublicKey(senderPublicKey));
+			}
+			catch (IOException e)
+			{
+				throw new InvalidKeyException("cannot extract originator public key: " + e);
+			}
+
+
+			Asn1OctetString ukm = null;
+			if (keyAgreementOID.Id.Equals(CmsEnvelopedGenerator.ECMqvSha1Kdf))
+			{
+				try
+				{
+					IAsymmetricCipherKeyPairGenerator ephemKPG =
+						GeneratorUtilities.GetKeyPairGenerator(keyAgreementOID);
+					ephemKPG.Init(
+						((ECPublicKeyParameters)senderPublicKey).CreateKeyGenerationParameters(random));
+
+					AsymmetricCipherKeyPair ephemKP = ephemKPG.GenerateKeyPair();
+
+					ukm = new DerOctetString(
+						new MQVuserKeyingMaterial(
+							CreateOriginatorPublicKey(ephemKP.Public), null));
+
+					senderPrivateParams = new MqvPrivateParameters(
+						(ECPrivateKeyParameters)senderPrivateParams,
+						(ECPrivateKeyParameters)ephemKP.Private,
+						(ECPublicKeyParameters)ephemKP.Public);
+				}
+				catch (IOException e)
+				{
+					throw new InvalidKeyException("cannot extract MQV ephemeral public key: " + e);
+				}
+				catch (SecurityUtilityException e)
+				{
+					throw new InvalidKeyException("cannot determine MQV ephemeral key pair parameters from public key: " + e);
+				}
+			}
+
+
+			DerSequence paramSeq = new DerSequence(
+				keyEncryptionOID,
+				DerNull.Instance);
+			AlgorithmIdentifier keyEncAlg = new AlgorithmIdentifier(keyAgreementOID, paramSeq);
+
+
+			Asn1EncodableVector recipientEncryptedKeys = new Asn1EncodableVector();
+			foreach (X509Certificate recipientCert in recipientCerts)
+			{
+				TbsCertificateStructure tbsCert;
+				try
+				{
+					tbsCert = TbsCertificateStructure.GetInstance(
+						Asn1Object.FromByteArray(recipientCert.GetTbsCertificate()));
+				}
+				catch (Exception)
+				{
+					throw new ArgumentException("can't extract TBS structure from certificate");
+				}
+
+				// TODO Should there be a SubjectKeyIdentifier-based alternative?
+				IssuerAndSerialNumber issuerSerial = new IssuerAndSerialNumber(
+					tbsCert.Issuer, tbsCert.SerialNumber.Value);
+				KeyAgreeRecipientIdentifier karid = new KeyAgreeRecipientIdentifier(issuerSerial);
+
+				ICipherParameters recipientPublicParams = recipientCert.GetPublicKey();
+				if (keyAgreementOID.Id.Equals(CmsEnvelopedGenerator.ECMqvSha1Kdf))
+				{
+					recipientPublicParams = new MqvPublicParameters(
+						(ECPublicKeyParameters)recipientPublicParams,
+						(ECPublicKeyParameters)recipientPublicParams);
+				}
+
+				// Use key agreement to choose a wrap key for this recipient
+				IBasicAgreement keyAgreement = AgreementUtilities.GetBasicAgreementWithKdf(
+					keyAgreementOID, keyEncryptionOID.Id);
+				keyAgreement.Init(new ParametersWithRandom(senderPrivateParams, random));
+				BigInteger agreedValue = keyAgreement.CalculateAgreement(recipientPublicParams);
+
+				int keyEncryptionKeySize = GeneratorUtilities.GetDefaultKeySize(keyEncryptionOID) / 8;
+				byte[] keyEncryptionKeyBytes = X9IntegerConverter.IntegerToBytes(agreedValue, keyEncryptionKeySize);
+				KeyParameter keyEncryptionKey = ParameterUtilities.CreateKeyParameter(
+					keyEncryptionOID, keyEncryptionKeyBytes);
+
+				// Wrap the content encryption key with the agreement key
+				IWrapper keyWrapper = Helper.CreateWrapper(keyEncryptionOID.Id);
+				keyWrapper.Init(true, new ParametersWithRandom(keyEncryptionKey, random));
+				byte[] encryptedKeyBytes = keyWrapper.Wrap(keyBytes, 0, keyBytes.Length);
+
+	        	Asn1OctetString encryptedKey = new DerOctetString(encryptedKeyBytes);
+
+				recipientEncryptedKeys.Add(new RecipientEncryptedKey(karid, encryptedKey));
+			}
+
+			return new RecipientInfo(new KeyAgreeRecipientInfo(originator, ukm, keyEncAlg,
+				new DerSequence(recipientEncryptedKeys)));
+		}
+
+		private static OriginatorPublicKey CreateOriginatorPublicKey(
+			AsymmetricKeyParameter publicKey)
+		{
+			SubjectPublicKeyInfo spki = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(publicKey);
+			return new OriginatorPublicKey(
+				new AlgorithmIdentifier(spki.AlgorithmID.ObjectID, DerNull.Instance),
+				spki.PublicKeyData.GetBytes());
+		}
+	}
+}
diff --git a/Crypto/src/cms/KeyAgreeRecipientInformation.cs b/Crypto/src/cms/KeyAgreeRecipientInformation.cs
new file mode 100644
index 000000000..38a94b0a4
--- /dev/null
+++ b/Crypto/src/cms/KeyAgreeRecipientInformation.cs
@@ -0,0 +1,226 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Cms.Ecc;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Utilities;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Pkcs;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* the RecipientInfo class for a recipient who has been sent a message
+	* encrypted using key agreement.
+	*/
+	public class KeyAgreeRecipientInformation
+		: RecipientInformation
+	{
+		private KeyAgreeRecipientInfo info;
+		private Asn1OctetString       encryptedKey;
+
+		internal static void ReadRecipientInfo(IList infos, KeyAgreeRecipientInfo info,
+			CmsSecureReadable secureReadable)
+		{
+			try
+			{
+				foreach (Asn1Encodable rek in info.RecipientEncryptedKeys)
+				{
+					RecipientEncryptedKey id = RecipientEncryptedKey.GetInstance(rek.ToAsn1Object());
+
+					RecipientID rid = new RecipientID();
+
+					Asn1.Cms.KeyAgreeRecipientIdentifier karid = id.Identifier;
+
+					Asn1.Cms.IssuerAndSerialNumber iAndSN = karid.IssuerAndSerialNumber;
+					if (iAndSN != null)
+					{
+						rid.Issuer = iAndSN.Name;
+						rid.SerialNumber = iAndSN.SerialNumber.Value;
+					}
+					else
+					{
+						Asn1.Cms.RecipientKeyIdentifier rKeyID = karid.RKeyID;
+
+						// Note: 'date' and 'other' fields of RecipientKeyIdentifier appear to be only informational 
+
+						rid.SubjectKeyIdentifier = rKeyID.SubjectKeyIdentifier.GetOctets();
+					}
+
+					infos.Add(new KeyAgreeRecipientInformation(info, rid, id.EncryptedKey,
+						secureReadable));
+				}
+			}
+			catch (IOException e)
+			{
+				throw new ArgumentException("invalid rid in KeyAgreeRecipientInformation", e);
+			}
+		}
+
+		internal KeyAgreeRecipientInformation(
+			KeyAgreeRecipientInfo	info,
+			RecipientID				rid,
+			Asn1OctetString			encryptedKey,
+			CmsSecureReadable		secureReadable)
+			: base(info.KeyEncryptionAlgorithm, secureReadable)
+		{
+			this.info = info;
+			this.rid = rid;
+			this.encryptedKey = encryptedKey;
+		}
+
+		private AsymmetricKeyParameter GetSenderPublicKey(
+			AsymmetricKeyParameter		receiverPrivateKey,
+			OriginatorIdentifierOrKey	originator)
+		{
+			OriginatorPublicKey opk = originator.OriginatorPublicKey;
+			if (opk != null)
+			{
+				return GetPublicKeyFromOriginatorPublicKey(receiverPrivateKey, opk);
+			}
+			
+			OriginatorID origID = new OriginatorID();
+			
+			Asn1.Cms.IssuerAndSerialNumber iAndSN = originator.IssuerAndSerialNumber;
+			if (iAndSN != null)
+			{
+				origID.Issuer = iAndSN.Name;
+				origID.SerialNumber = iAndSN.SerialNumber.Value;
+			}
+			else
+			{
+				SubjectKeyIdentifier ski = originator.SubjectKeyIdentifier;
+
+				origID.SubjectKeyIdentifier = ski.GetKeyIdentifier();
+			}
+
+			return GetPublicKeyFromOriginatorID(origID);
+		}
+
+		private AsymmetricKeyParameter GetPublicKeyFromOriginatorPublicKey(
+			AsymmetricKeyParameter	receiverPrivateKey,
+			OriginatorPublicKey		originatorPublicKey)
+		{
+			PrivateKeyInfo privInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(receiverPrivateKey);
+			SubjectPublicKeyInfo pubInfo = new SubjectPublicKeyInfo(
+				privInfo.AlgorithmID,
+				originatorPublicKey.PublicKey.GetBytes());
+			return PublicKeyFactory.CreateKey(pubInfo);
+		}
+
+		private AsymmetricKeyParameter GetPublicKeyFromOriginatorID(
+			OriginatorID origID)
+		{
+			// TODO Support all alternatives for OriginatorIdentifierOrKey
+			// see RFC 3852 6.2.2
+			throw new CmsException("No support for 'originator' as IssuerAndSerialNumber or SubjectKeyIdentifier");
+		}
+
+		private KeyParameter CalculateAgreedWrapKey(
+			string					wrapAlg,
+			AsymmetricKeyParameter	senderPublicKey,
+			AsymmetricKeyParameter	receiverPrivateKey)
+		{
+			DerObjectIdentifier agreeAlgID = keyEncAlg.ObjectID;
+
+			ICipherParameters senderPublicParams = senderPublicKey;
+			ICipherParameters receiverPrivateParams = receiverPrivateKey;
+
+			if (agreeAlgID.Id.Equals(CmsEnvelopedGenerator.ECMqvSha1Kdf))
+			{
+				byte[] ukmEncoding = info.UserKeyingMaterial.GetOctets();
+				MQVuserKeyingMaterial ukm = MQVuserKeyingMaterial.GetInstance(
+					Asn1Object.FromByteArray(ukmEncoding));
+
+				AsymmetricKeyParameter ephemeralKey = GetPublicKeyFromOriginatorPublicKey(
+					receiverPrivateKey, ukm.EphemeralPublicKey);
+
+				senderPublicParams = new MqvPublicParameters(
+					(ECPublicKeyParameters)senderPublicParams,
+					(ECPublicKeyParameters)ephemeralKey);
+				receiverPrivateParams = new MqvPrivateParameters(
+					(ECPrivateKeyParameters)receiverPrivateParams,
+					(ECPrivateKeyParameters)receiverPrivateParams);
+			}
+
+			IBasicAgreement agreement = AgreementUtilities.GetBasicAgreementWithKdf(
+				agreeAlgID, wrapAlg);
+			agreement.Init(receiverPrivateParams);
+			BigInteger agreedValue = agreement.CalculateAgreement(senderPublicParams);
+
+			int wrapKeySize = GeneratorUtilities.GetDefaultKeySize(wrapAlg) / 8;
+			byte[] wrapKeyBytes = X9IntegerConverter.IntegerToBytes(agreedValue, wrapKeySize);
+			return ParameterUtilities.CreateKeyParameter(wrapAlg, wrapKeyBytes);
+		}
+
+		private KeyParameter UnwrapSessionKey(
+			string			wrapAlg,
+			KeyParameter	agreedKey)
+		{
+			byte[] encKeyOctets = encryptedKey.GetOctets();
+
+			IWrapper keyCipher = WrapperUtilities.GetWrapper(wrapAlg);
+			keyCipher.Init(false, agreedKey);
+			byte[] sKeyBytes = keyCipher.Unwrap(encKeyOctets, 0, encKeyOctets.Length);
+			return ParameterUtilities.CreateKeyParameter(GetContentAlgorithmName(), sKeyBytes);
+		}
+
+		internal KeyParameter GetSessionKey(
+			AsymmetricKeyParameter receiverPrivateKey)
+		{
+			try
+			{
+				string wrapAlg = DerObjectIdentifier.GetInstance(
+					Asn1Sequence.GetInstance(keyEncAlg.Parameters)[0]).Id;
+
+				AsymmetricKeyParameter senderPublicKey = GetSenderPublicKey(
+					receiverPrivateKey, info.Originator);
+
+				KeyParameter agreedWrapKey = CalculateAgreedWrapKey(wrapAlg,
+					senderPublicKey, receiverPrivateKey);
+
+				return UnwrapSessionKey(wrapAlg, agreedWrapKey);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+			catch (Exception e)
+			{
+				throw new CmsException("originator key invalid.", e);
+			}
+		}
+
+		/**
+		* decrypt the content and return an input stream.
+		*/
+		public override CmsTypedStream GetContentStream(
+			ICipherParameters key)
+		{
+			if (!(key is AsymmetricKeyParameter))
+				throw new ArgumentException("KeyAgreement requires asymmetric key", "key");
+
+			AsymmetricKeyParameter receiverPrivateKey = (AsymmetricKeyParameter) key;
+
+			if (!receiverPrivateKey.IsPrivate)
+				throw new ArgumentException("Expected private key", "key");
+
+			KeyParameter sKey = GetSessionKey(receiverPrivateKey);
+
+			return GetContentFromSessionKey(sKey);
+		}
+	}
+}
diff --git a/Crypto/src/cms/KeyTransRecipientInfoGenerator.cs b/Crypto/src/cms/KeyTransRecipientInfoGenerator.cs
new file mode 100644
index 000000000..0992e6da6
--- /dev/null
+++ b/Crypto/src/cms/KeyTransRecipientInfoGenerator.cs
@@ -0,0 +1,87 @@
+using System;
+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.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class KeyTransRecipientInfoGenerator : RecipientInfoGenerator
+	{
+		private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance;
+
+		private TbsCertificateStructure	recipientTbsCert;
+		private AsymmetricKeyParameter	recipientPublicKey;
+		private Asn1OctetString			subjectKeyIdentifier;
+
+		// Derived fields
+		private SubjectPublicKeyInfo info;
+
+		internal KeyTransRecipientInfoGenerator()
+		{
+		}
+
+		internal X509Certificate RecipientCert
+		{
+			set
+			{
+				this.recipientTbsCert = CmsUtilities.GetTbsCertificateStructure(value);
+				this.recipientPublicKey = value.GetPublicKey();
+				this.info = recipientTbsCert.SubjectPublicKeyInfo;
+			}
+		}
+		
+		internal AsymmetricKeyParameter RecipientPublicKey
+		{
+			set
+			{
+				this.recipientPublicKey = value;
+
+				try
+				{
+					info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(
+						recipientPublicKey);
+				}
+				catch (IOException)
+				{
+					throw new ArgumentException("can't extract key algorithm from this key");
+				}
+			}
+		}
+		
+		internal Asn1OctetString SubjectKeyIdentifier
+		{
+			set { this.subjectKeyIdentifier = value; }
+		}
+
+		public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random)
+		{
+			byte[] keyBytes = contentEncryptionKey.GetKey();
+			AlgorithmIdentifier keyEncryptionAlgorithm = info.AlgorithmID;
+
+			IWrapper keyWrapper = Helper.CreateWrapper(keyEncryptionAlgorithm.ObjectID.Id);
+			keyWrapper.Init(true, new ParametersWithRandom(recipientPublicKey, random));
+			byte[] encryptedKeyBytes = keyWrapper.Wrap(keyBytes, 0, keyBytes.Length);
+
+			RecipientIdentifier recipId;
+			if (recipientTbsCert != null)
+			{
+				IssuerAndSerialNumber issuerAndSerial = new IssuerAndSerialNumber(
+					recipientTbsCert.Issuer, recipientTbsCert.SerialNumber.Value);
+				recipId = new RecipientIdentifier(issuerAndSerial);
+			}
+			else
+			{
+				recipId = new RecipientIdentifier(subjectKeyIdentifier);
+			}
+
+			return new RecipientInfo(new KeyTransRecipientInfo(recipId, keyEncryptionAlgorithm,
+				new DerOctetString(encryptedKeyBytes)));
+		}
+	}
+}
diff --git a/Crypto/src/cms/KeyTransRecipientInformation.cs b/Crypto/src/cms/KeyTransRecipientInformation.cs
new file mode 100644
index 000000000..24121cb2c
--- /dev/null
+++ b/Crypto/src/cms/KeyTransRecipientInformation.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Asn1Pkcs = Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * the KeyTransRecipientInformation class for a recipient who has been sent a secret
+    * key encrypted using their public key that needs to be used to
+    * extract the message.
+    */
+    public class KeyTransRecipientInformation
+        : RecipientInformation
+    {
+        private KeyTransRecipientInfo info;
+
+		internal KeyTransRecipientInformation(
+			KeyTransRecipientInfo	info,
+			CmsSecureReadable		secureReadable)
+			: base(info.KeyEncryptionAlgorithm, secureReadable)
+		{
+            this.info = info;
+            this.rid = new RecipientID();
+
+			RecipientIdentifier r = info.RecipientIdentifier;
+
+			try
+            {
+                if (r.IsTagged)
+                {
+                    Asn1OctetString octs = Asn1OctetString.GetInstance(r.ID);
+
+					rid.SubjectKeyIdentifier = octs.GetOctets();
+                }
+                else
+                {
+                    IssuerAndSerialNumber iAnds = IssuerAndSerialNumber.GetInstance(r.ID);
+
+					rid.Issuer = iAnds.Name;
+                    rid.SerialNumber = iAnds.SerialNumber.Value;
+                }
+            }
+            catch (IOException)
+            {
+                throw new ArgumentException("invalid rid in KeyTransRecipientInformation");
+            }
+        }
+
+		private string GetExchangeEncryptionAlgorithmName(
+			DerObjectIdentifier oid)
+		{
+			if (Asn1Pkcs.PkcsObjectIdentifiers.RsaEncryption.Equals(oid))
+			{
+				return "RSA//PKCS1Padding";
+			}
+
+			return oid.Id;
+		}
+
+		internal KeyParameter UnwrapKey(ICipherParameters key)
+		{
+			byte[] encryptedKey = info.EncryptedKey.GetOctets();
+			string keyExchangeAlgorithm = GetExchangeEncryptionAlgorithmName(keyEncAlg.ObjectID);
+
+			try
+			{
+				IWrapper keyWrapper = WrapperUtilities.GetWrapper(keyExchangeAlgorithm);
+				keyWrapper.Init(false, key);
+
+				// FIXME Support for MAC algorithm parameters similar to cipher parameters
+				return ParameterUtilities.CreateKeyParameter(
+					GetContentAlgorithmName(), keyWrapper.Unwrap(encryptedKey, 0, encryptedKey.Length));
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+//			catch (IllegalBlockSizeException e)
+			catch (DataLengthException e)
+			{
+				throw new CmsException("illegal blocksize in message.", e);
+			}
+//			catch (BadPaddingException e)
+			catch (InvalidCipherTextException e)
+			{
+				throw new CmsException("bad padding in message.", e);
+			}
+		}
+		
+		/**
+        * decrypt the content and return it as a byte array.
+        */
+        public override CmsTypedStream GetContentStream(
+            ICipherParameters key)
+        {
+			KeyParameter sKey = UnwrapKey(key);
+
+			return GetContentFromSessionKey(sKey);
+		}
+    }
+}
diff --git a/Crypto/src/cms/MacOutputStream.cs b/Crypto/src/cms/MacOutputStream.cs
new file mode 100644
index 000000000..8891dbc2c
--- /dev/null
+++ b/Crypto/src/cms/MacOutputStream.cs
@@ -0,0 +1,28 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class MacOutputStream
+		: BaseOutputStream
+	{
+		private readonly IMac mac;
+
+		internal MacOutputStream(IMac mac)
+		{
+			this.mac = mac;
+		}
+
+		public override void Write(byte[] b, int off, int len)
+		{
+			mac.BlockUpdate(b, off, len);
+		}
+
+		public override void WriteByte(byte b)
+		{
+			mac.Update(b);
+		}
+	}
+}
diff --git a/Crypto/src/cms/NullOutputStream.cs b/Crypto/src/cms/NullOutputStream.cs
new file mode 100644
index 000000000..ed937bdeb
--- /dev/null
+++ b/Crypto/src/cms/NullOutputStream.cs
@@ -0,0 +1,20 @@
+using System;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class NullOutputStream
+		: BaseOutputStream
+	{
+		public override void WriteByte(byte b)
+		{
+			// do nothing
+		}
+
+		public override void Write(byte[] buffer, int offset, int count)
+		{
+			// do nothing
+		}
+	}
+}
diff --git a/Crypto/src/cms/OriginatorId.cs b/Crypto/src/cms/OriginatorId.cs
new file mode 100644
index 000000000..5a3b7374d
--- /dev/null
+++ b/Crypto/src/cms/OriginatorId.cs
@@ -0,0 +1,51 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * a basic index for an originator.
+    */
+    public class OriginatorID
+        : X509CertStoreSelector
+    {
+        public override int GetHashCode()
+        {
+            int code = Arrays.GetHashCode(this.SubjectKeyIdentifier);
+
+			BigInteger serialNumber = this.SerialNumber;
+			if (serialNumber != null)
+            {
+                code ^= serialNumber.GetHashCode();
+            }
+
+			X509Name issuer = this.Issuer;
+            if (issuer != null)
+            {
+                code ^= issuer.GetHashCode();
+            }
+
+			return code;
+        }
+
+        public override bool Equals(
+            object obj)
+        {
+			if (obj == this)
+				return false;
+
+			OriginatorID id = obj as OriginatorID;
+
+			if (id == null)
+				return false;
+
+			return Arrays.AreEqual(SubjectKeyIdentifier, id.SubjectKeyIdentifier)
+				&& Platform.Equals(SerialNumber, id.SerialNumber)
+				&& IssuersMatch(Issuer, id.Issuer);
+        }
+    }
+}
diff --git a/Crypto/src/cms/PKCS5Scheme2PBEKey.cs b/Crypto/src/cms/PKCS5Scheme2PBEKey.cs
new file mode 100644
index 000000000..08b8518a1
--- /dev/null
+++ b/Crypto/src/cms/PKCS5Scheme2PBEKey.cs
@@ -0,0 +1,64 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Cms
+{
+	/// <summary>
+	/// PKCS5 scheme-2 - password converted to bytes assuming ASCII.
+	/// </summary>
+	public class Pkcs5Scheme2PbeKey
+		: CmsPbeKey
+	{
+		[Obsolete("Use version taking 'char[]' instead")]
+		public Pkcs5Scheme2PbeKey(
+			string	password,
+			byte[]	salt,
+			int		iterationCount)
+			: this(password.ToCharArray(), salt, iterationCount)
+		{
+		}
+
+		[Obsolete("Use version taking 'char[]' instead")]
+		public Pkcs5Scheme2PbeKey(
+			string				password,
+			AlgorithmIdentifier keyDerivationAlgorithm)
+			: this(password.ToCharArray(), keyDerivationAlgorithm)
+		{
+		}
+		
+		public Pkcs5Scheme2PbeKey(
+			char[]	password,
+			byte[]	salt,
+			int		iterationCount)
+			: base(password, salt, iterationCount)
+		{
+		}
+
+		public Pkcs5Scheme2PbeKey(
+			char[]				password,
+			AlgorithmIdentifier keyDerivationAlgorithm)
+			: base(password, keyDerivationAlgorithm)
+		{
+		}
+
+		internal override KeyParameter GetEncoded(
+			string algorithmOid)
+		{
+			Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
+
+			gen.Init(
+				PbeParametersGenerator.Pkcs5PasswordToBytes(password),
+				salt,
+				iterationCount);
+
+			return (KeyParameter) gen.GenerateDerivedParameters(
+				algorithmOid,
+				CmsEnvelopedHelper.Instance.GetKeySize(algorithmOid));
+		}
+	}
+}
diff --git a/Crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs b/Crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
new file mode 100644
index 000000000..7aecc2978
--- /dev/null
+++ b/Crypto/src/cms/PKCS5Scheme2UTF8PBEKey.cs
@@ -0,0 +1,64 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	 * PKCS5 scheme-2 - password converted to bytes using UTF-8.
+	 */
+	public class Pkcs5Scheme2Utf8PbeKey
+		: CmsPbeKey
+	{
+		[Obsolete("Use version taking 'char[]' instead")]
+		public Pkcs5Scheme2Utf8PbeKey(
+			string	password,
+			byte[]	salt,
+			int		iterationCount)
+			: this(password.ToCharArray(), salt, iterationCount)
+		{
+		}
+
+		[Obsolete("Use version taking 'char[]' instead")]
+		public Pkcs5Scheme2Utf8PbeKey(
+			string				password,
+			AlgorithmIdentifier keyDerivationAlgorithm)
+			: this(password.ToCharArray(), keyDerivationAlgorithm)
+		{
+		}
+
+		public Pkcs5Scheme2Utf8PbeKey(
+			char[]	password,
+			byte[]	salt,
+			int		iterationCount)
+			: base(password, salt, iterationCount)
+		{
+		}
+
+		public Pkcs5Scheme2Utf8PbeKey(
+			char[]				password,
+			AlgorithmIdentifier keyDerivationAlgorithm)
+			: base(password, keyDerivationAlgorithm)
+		{
+		}
+
+		internal override KeyParameter GetEncoded(
+			string algorithmOid)
+		{
+			Pkcs5S2ParametersGenerator gen = new Pkcs5S2ParametersGenerator();
+
+			gen.Init(
+				PbeParametersGenerator.Pkcs5PasswordToUtf8Bytes(password),
+				salt,
+				iterationCount);
+
+			return (KeyParameter) gen.GenerateDerivedParameters(
+				algorithmOid,
+				CmsEnvelopedHelper.Instance.GetKeySize(algorithmOid));
+		}
+	}
+}
diff --git a/Crypto/src/cms/PasswordRecipientInfoGenerator.cs b/Crypto/src/cms/PasswordRecipientInfoGenerator.cs
new file mode 100644
index 000000000..0a0b27b53
--- /dev/null
+++ b/Crypto/src/cms/PasswordRecipientInfoGenerator.cs
@@ -0,0 +1,69 @@
+using System;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class PasswordRecipientInfoGenerator : RecipientInfoGenerator
+	{
+		private static readonly CmsEnvelopedHelper Helper = CmsEnvelopedHelper.Instance;
+
+		private AlgorithmIdentifier	keyDerivationAlgorithm;
+		private KeyParameter		keyEncryptionKey;
+		// TODO Can get this from keyEncryptionKey?		
+		private string				keyEncryptionKeyOID;
+
+		internal PasswordRecipientInfoGenerator()
+		{
+		}
+
+		internal AlgorithmIdentifier KeyDerivationAlgorithm
+		{
+			set { this.keyDerivationAlgorithm = value; }
+		}
+
+		internal KeyParameter KeyEncryptionKey
+		{
+			set { this.keyEncryptionKey = value; }
+		}
+
+		internal string KeyEncryptionKeyOID
+		{
+			set { this.keyEncryptionKeyOID = value; }
+		}
+
+		public RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random)
+		{
+			byte[] keyBytes = contentEncryptionKey.GetKey();
+
+			string rfc3211WrapperName = Helper.GetRfc3211WrapperName(keyEncryptionKeyOID);
+			IWrapper keyWrapper = Helper.CreateWrapper(rfc3211WrapperName);
+
+			// Note: In Java build, the IV is automatically generated in JCE layer
+			int ivLength = rfc3211WrapperName.StartsWith("DESEDE") ? 8 : 16;
+			byte[] iv = new byte[ivLength];
+			random.NextBytes(iv);
+
+			ICipherParameters parameters = new ParametersWithIV(keyEncryptionKey, iv);
+			keyWrapper.Init(true, new ParametersWithRandom(parameters, random));
+        	Asn1OctetString encryptedKey = new DerOctetString(
+				keyWrapper.Wrap(keyBytes, 0, keyBytes.Length));
+
+			DerSequence seq = new DerSequence(
+				new DerObjectIdentifier(keyEncryptionKeyOID),
+				new DerOctetString(iv));
+
+			AlgorithmIdentifier keyEncryptionAlgorithm = new AlgorithmIdentifier(
+				PkcsObjectIdentifiers.IdAlgPwriKek, seq);
+
+			return new RecipientInfo(new PasswordRecipientInfo(
+				keyDerivationAlgorithm, keyEncryptionAlgorithm, encryptedKey));
+		}
+	}
+}
diff --git a/Crypto/src/cms/PasswordRecipientInformation.cs b/Crypto/src/cms/PasswordRecipientInformation.cs
new file mode 100644
index 000000000..f629caba6
--- /dev/null
+++ b/Crypto/src/cms/PasswordRecipientInformation.cs
@@ -0,0 +1,79 @@
+using System;
+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.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	 * the RecipientInfo class for a recipient who has been sent a message
+	 * encrypted using a password.
+	 */
+	public class PasswordRecipientInformation
+		: RecipientInformation
+	{
+		private readonly PasswordRecipientInfo	info;
+
+		internal PasswordRecipientInformation(
+			PasswordRecipientInfo	info,
+			CmsSecureReadable		secureReadable)
+			: base(info.KeyEncryptionAlgorithm, secureReadable)
+		{
+			this.info = info;
+			this.rid = new RecipientID();
+		}
+
+		/**
+		 * return the object identifier for the key derivation algorithm, or null
+		 * if there is none present.
+		 *
+		 * @return OID for key derivation algorithm, if present.
+		 */
+		public virtual AlgorithmIdentifier KeyDerivationAlgorithm
+		{
+			get { return info.KeyDerivationAlgorithm; }
+		}
+
+		/**
+		 * decrypt the content and return an input stream.
+		 */
+		public override CmsTypedStream GetContentStream(
+			ICipherParameters key)
+		{
+			try
+			{
+				AlgorithmIdentifier kekAlg = AlgorithmIdentifier.GetInstance(info.KeyEncryptionAlgorithm);
+				Asn1Sequence        kekAlgParams = (Asn1Sequence)kekAlg.Parameters;
+				byte[]              encryptedKey = info.EncryptedKey.GetOctets();
+				string              kekAlgName = DerObjectIdentifier.GetInstance(kekAlgParams[0]).Id;
+				string				cName = CmsEnvelopedHelper.Instance.GetRfc3211WrapperName(kekAlgName);
+				IWrapper			keyWrapper = WrapperUtilities.GetWrapper(cName);
+
+				byte[] iv = Asn1OctetString.GetInstance(kekAlgParams[1]).GetOctets();
+
+				ICipherParameters parameters = ((CmsPbeKey)key).GetEncoded(kekAlgName);
+				parameters = new ParametersWithIV(parameters, iv);
+
+				keyWrapper.Init(false, parameters);
+
+				KeyParameter sKey = ParameterUtilities.CreateKeyParameter(
+					GetContentAlgorithmName(), keyWrapper.Unwrap(encryptedKey, 0, encryptedKey.Length));
+
+				return GetContentFromSessionKey(sKey);
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw new CmsException("couldn't create cipher.", e);
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key invalid in message.", e);
+			}
+		}
+	}
+}
diff --git a/Crypto/src/cms/RecipientId.cs b/Crypto/src/cms/RecipientId.cs
new file mode 100644
index 000000000..9b6eb093b
--- /dev/null
+++ b/Crypto/src/cms/RecipientId.cs
@@ -0,0 +1,58 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+    public class RecipientID
+        : X509CertStoreSelector
+    {
+        private byte[] keyIdentifier;
+
+		public byte[] KeyIdentifier
+		{
+			get { return Arrays.Clone(keyIdentifier); }
+			set { keyIdentifier = Arrays.Clone(value); }
+		}
+
+		public override int GetHashCode()
+        {
+            int code = Arrays.GetHashCode(keyIdentifier)
+				^ Arrays.GetHashCode(this.SubjectKeyIdentifier);
+
+			BigInteger serialNumber = this.SerialNumber;
+			if (serialNumber != null)
+            {
+                code ^= serialNumber.GetHashCode();
+            }
+
+			X509Name issuer = this.Issuer;
+            if (issuer != null)
+            {
+                code ^= issuer.GetHashCode();
+            }
+
+            return code;
+        }
+
+        public override bool Equals(
+            object obj)
+        {
+			if (obj == this)
+				return true;
+
+			RecipientID id = obj as RecipientID;
+
+			if (id == null)
+				return false;
+
+			return Arrays.AreEqual(keyIdentifier, id.keyIdentifier)
+				&& Arrays.AreEqual(SubjectKeyIdentifier, id.SubjectKeyIdentifier)
+				&& Platform.Equals(SerialNumber, id.SerialNumber)
+				&& IssuersMatch(Issuer, id.Issuer);
+        }
+    }
+}
diff --git a/Crypto/src/cms/RecipientInfoGenerator.cs b/Crypto/src/cms/RecipientInfoGenerator.cs
new file mode 100644
index 000000000..c41db6122
--- /dev/null
+++ b/Crypto/src/cms/RecipientInfoGenerator.cs
@@ -0,0 +1,26 @@
+using System;
+
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+	interface RecipientInfoGenerator
+	{
+		/// <summary>
+		/// Generate a RecipientInfo object for the given key.
+		/// </summary>
+		/// <param name="contentEncryptionKey">
+		/// A <see cref="KeyParameter"/>
+		/// </param>
+		/// <param name="random">
+		/// A <see cref="SecureRandom"/>
+		/// </param>
+		/// <returns>
+		/// A <see cref="RecipientInfo"/>
+		/// </returns>
+		/// <exception cref="GeneralSecurityException"></exception>
+		RecipientInfo Generate(KeyParameter contentEncryptionKey, SecureRandom random);
+	}
+}
diff --git a/Crypto/src/cms/RecipientInformation.cs b/Crypto/src/cms/RecipientInformation.cs
new file mode 100644
index 000000000..8b0316be4
--- /dev/null
+++ b/Crypto/src/cms/RecipientInformation.cs
@@ -0,0 +1,126 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Pkcs;
+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.Utilities;
+using Org.BouncyCastle.Utilities.Collections;
+
+namespace Org.BouncyCastle.Cms
+{
+    public abstract class RecipientInformation
+    {
+		internal RecipientID			rid = new RecipientID();
+		internal AlgorithmIdentifier	keyEncAlg;
+		internal CmsSecureReadable		secureReadable;
+		
+		private byte[] resultMac;
+
+		internal RecipientInformation(
+			AlgorithmIdentifier	keyEncAlg,
+			CmsSecureReadable	secureReadable)
+		{
+			this.keyEncAlg = keyEncAlg;
+			this.secureReadable = secureReadable;
+		}
+
+		internal string GetContentAlgorithmName()
+		{
+			AlgorithmIdentifier algorithm = secureReadable.Algorithm;
+//			return CmsEnvelopedHelper.Instance.GetSymmetricCipherName(algorithm.ObjectID.Id);
+			return algorithm.ObjectID.Id;
+		}
+
+		public RecipientID RecipientID
+        {
+			get { return rid; }
+        }
+
+		public AlgorithmIdentifier KeyEncryptionAlgorithmID
+		{
+			get { return keyEncAlg; }
+		}
+
+		/**
+        * return the object identifier for the key encryption algorithm.
+        * 
+		* @return OID for key encryption algorithm.
+        */
+        public string KeyEncryptionAlgOid
+        {
+			get { return keyEncAlg.ObjectID.Id; }
+        }
+
+		/**
+        * return the ASN.1 encoded key encryption algorithm parameters, or null if
+        * there aren't any.
+        * 
+		* @return ASN.1 encoding of key encryption algorithm parameters.
+        */
+		public Asn1Object KeyEncryptionAlgParams
+		{
+			get
+			{
+				Asn1Encodable ae = keyEncAlg.Parameters;
+
+				return ae == null ? null : ae.ToAsn1Object();
+			}
+		}
+
+		internal CmsTypedStream GetContentFromSessionKey(
+			KeyParameter sKey)
+		{
+			CmsReadable readable = secureReadable.GetReadable(sKey); 
+
+			try
+			{
+				return new CmsTypedStream(readable.GetInputStream());
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("error getting .", e);
+			}
+		}
+
+		public byte[] GetContent(
+            ICipherParameters key)
+        {
+            try
+            {
+				return CmsUtilities.StreamToByteArray(GetContentStream(key).ContentStream);
+            }
+            catch (IOException e)
+            {
+                throw new Exception("unable to parse internal stream: " + e);
+            }
+        }
+
+		/**
+		* Return the MAC calculated for the content stream. Note: this call is only meaningful once all
+		* the content has been read.
+		*
+		* @return  byte array containing the mac.
+		*/
+		public byte[] GetMac()
+		{
+			if (resultMac == null)
+			{
+				object cryptoObject = secureReadable.CryptoObject;
+				if (cryptoObject is IMac)
+				{
+					resultMac = MacUtilities.DoFinal((IMac)cryptoObject);
+				}
+			}
+
+			return Arrays.Clone(resultMac);
+		}
+		
+		public abstract CmsTypedStream GetContentStream(ICipherParameters key);
+	}
+}
diff --git a/Crypto/src/cms/RecipientInformationStore.cs b/Crypto/src/cms/RecipientInformationStore.cs
new file mode 100644
index 000000000..33b472f9d
--- /dev/null
+++ b/Crypto/src/cms/RecipientInformationStore.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+	public class RecipientInformationStore
+	{
+		private readonly IList all; //ArrayList[RecipientInformation]
+		private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[RecipientID, ArrayList[RecipientInformation]]
+
+		public RecipientInformationStore(
+			ICollection recipientInfos)
+		{
+			foreach (RecipientInformation recipientInformation in recipientInfos)
+			{
+				RecipientID rid = recipientInformation.RecipientID;
+                IList list = (IList)table[rid];
+
+				if (list == null)
+				{
+					table[rid] = list = Platform.CreateArrayList(1);
+				}
+
+				list.Add(recipientInformation);
+			}
+
+            this.all = Platform.CreateArrayList(recipientInfos);
+		}
+
+		public RecipientInformation this[RecipientID selector]
+		{
+			get { return GetFirstRecipient(selector); }
+		}
+
+		/**
+		* Return the first RecipientInformation object that matches the
+		* passed in selector. Null if there are no matches.
+		*
+		* @param selector to identify a recipient
+		* @return a single RecipientInformation object. Null if none matches.
+		*/
+		public RecipientInformation GetFirstRecipient(
+			RecipientID selector)
+		{
+			IList list = (IList) table[selector];
+
+			return list == null ? null : (RecipientInformation) list[0];
+		}
+
+		/**
+		* Return the number of recipients in the collection.
+		*
+		* @return number of recipients identified.
+		*/
+		public int Count
+		{
+			get { return all.Count; }
+		}
+
+		/**
+		* Return all recipients in the collection
+		*
+		* @return a collection of recipients.
+		*/
+		public ICollection GetRecipients()
+		{
+			return Platform.CreateArrayList(all);
+		}
+
+		/**
+		* Return possible empty collection with recipients matching the passed in RecipientID
+		*
+		* @param selector a recipient id to select against.
+		* @return a collection of RecipientInformation objects.
+		*/
+		public ICollection GetRecipients(
+			RecipientID selector)
+		{
+            IList list = (IList)table[selector];
+
+            return list == null ? Platform.CreateArrayList() : Platform.CreateArrayList(list);
+		}
+	}
+}
diff --git a/Crypto/src/cms/SigOutputStream.cs b/Crypto/src/cms/SigOutputStream.cs
new file mode 100644
index 000000000..a807fa7fc
--- /dev/null
+++ b/Crypto/src/cms/SigOutputStream.cs
@@ -0,0 +1,43 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal class SigOutputStream
+		: BaseOutputStream
+	{
+		private readonly ISigner sig;
+
+		internal SigOutputStream(ISigner sig)
+		{
+			this.sig = sig;
+		}
+
+		public override void WriteByte(byte b)
+		{
+			try
+			{
+				sig.Update(b);
+			}
+			catch (SignatureException e)
+			{
+				throw new CmsStreamException("signature problem: " + e);
+			}
+		}
+
+		public override void Write(byte[] b, int off, int len)
+		{
+			try
+			{
+				sig.BlockUpdate(b, off, len);
+			}
+			catch (SignatureException e)
+			{
+				throw new CmsStreamException("signature problem: " + e);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/Crypto/src/cms/SignerId.cs b/Crypto/src/cms/SignerId.cs
new file mode 100644
index 000000000..baac9369b
--- /dev/null
+++ b/Crypto/src/cms/SignerId.cs
@@ -0,0 +1,51 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509.Store;
+
+namespace Org.BouncyCastle.Cms
+{
+    /**
+    * a basic index for a signer.
+    */
+    public class SignerID
+        : X509CertStoreSelector
+    {
+        public override int GetHashCode()
+        {
+            int code = Arrays.GetHashCode(this.SubjectKeyIdentifier);
+
+			BigInteger serialNumber = this.SerialNumber;
+			if (serialNumber != null)
+            {
+                code ^= serialNumber.GetHashCode();
+            }
+
+			X509Name issuer = this.Issuer;
+            if (issuer != null)
+            {
+                code ^= issuer.GetHashCode();
+            }
+
+			return code;
+        }
+
+        public override bool Equals(
+            object obj)
+        {
+			if (obj == this)
+				return false;
+
+			SignerID id = obj as SignerID;
+
+			if (id == null)
+				return false;
+
+			return Arrays.AreEqual(SubjectKeyIdentifier, id.SubjectKeyIdentifier)
+				&& Platform.Equals(SerialNumber, id.SerialNumber)
+				&& IssuersMatch(Issuer, id.Issuer);
+        }
+    }
+}
diff --git a/Crypto/src/cms/SignerInfoGenerator.cs b/Crypto/src/cms/SignerInfoGenerator.cs
new file mode 100644
index 000000000..f78cf2c01
--- /dev/null
+++ b/Crypto/src/cms/SignerInfoGenerator.cs
@@ -0,0 +1,14 @@
+using System;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Cms;
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	internal interface SignerInfoGenerator
+	{
+		SignerInfo Generate(DerObjectIdentifier contentType, AlgorithmIdentifier digestAlgorithm,
+        	byte[] calculatedDigest);
+	}
+}
diff --git a/Crypto/src/cms/SignerInformation.cs b/Crypto/src/cms/SignerInformation.cs
new file mode 100644
index 000000000..20af29a50
--- /dev/null
+++ b/Crypto/src/cms/SignerInformation.cs
@@ -0,0 +1,758 @@
+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.Engines;
+using Org.BouncyCastle.Crypto.Signers;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.X509;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	* an expanded SignerInfo block from a CMS Signed message
+	*/
+	public class SignerInformation
+	{
+		private static readonly CmsSignedHelper Helper = CmsSignedHelper.Instance;
+
+		private SignerID			sid;
+		private SignerInfo			info;
+		private AlgorithmIdentifier	digestAlgorithm;
+		private AlgorithmIdentifier	encryptionAlgorithm;
+		private readonly Asn1Set	signedAttributeSet;
+		private readonly Asn1Set	unsignedAttributeSet;
+		private CmsProcessable		content;
+		private byte[]				signature;
+		private DerObjectIdentifier	contentType;
+		private IDigestCalculator	digestCalculator;
+		private byte[]				resultDigest;
+
+		// Derived
+		private Asn1.Cms.AttributeTable	signedAttributeTable;
+		private Asn1.Cms.AttributeTable	unsignedAttributeTable;
+		private readonly bool isCounterSignature;
+
+		internal SignerInformation(
+			SignerInfo			info,
+			DerObjectIdentifier	contentType,
+			CmsProcessable		content,
+			IDigestCalculator	digestCalculator)
+		{
+			this.info = info;
+			this.sid = new SignerID();
+			this.contentType = contentType;
+			this.isCounterSignature = contentType == null;
+
+			try
+			{
+				SignerIdentifier s = info.SignerID;
+
+				if (s.IsTagged)
+				{
+					Asn1OctetString octs = Asn1OctetString.GetInstance(s.ID);
+
+					sid.SubjectKeyIdentifier = octs.GetEncoded();
+				}
+				else
+				{
+					Asn1.Cms.IssuerAndSerialNumber iAnds =
+						Asn1.Cms.IssuerAndSerialNumber.GetInstance(s.ID);
+
+					sid.Issuer = iAnds.Name;
+					sid.SerialNumber = iAnds.SerialNumber.Value;
+				}
+			}
+			catch (IOException)
+			{
+				throw new ArgumentException("invalid sid in SignerInfo");
+			}
+
+			this.digestAlgorithm = info.DigestAlgorithm;
+			this.signedAttributeSet = info.AuthenticatedAttributes;
+			this.unsignedAttributeSet = info.UnauthenticatedAttributes;
+			this.encryptionAlgorithm = info.DigestEncryptionAlgorithm;
+			this.signature = info.EncryptedDigest.GetOctets();
+
+			this.content = content;
+			this.digestCalculator = digestCalculator;
+		}
+
+		public bool IsCounterSignature
+		{
+			get { return isCounterSignature; }
+		}
+
+		public DerObjectIdentifier ContentType
+		{
+			get { return contentType; }
+		}
+
+		public SignerID SignerID
+		{
+			get { return sid; }
+		}
+
+		/**
+		* return the version number for this objects underlying SignerInfo structure.
+		*/
+		public int Version
+		{
+			get { return info.Version.Value.IntValue; }
+		}
+
+		public AlgorithmIdentifier DigestAlgorithmID
+		{
+			get { return digestAlgorithm; }
+		}
+
+		/**
+		* return the object identifier for the signature.
+		*/
+		public string DigestAlgOid
+		{
+			get { return digestAlgorithm.ObjectID.Id; }
+		}
+
+		/**
+		* return the signature parameters, or null if there aren't any.
+		*/
+		public Asn1Object DigestAlgParams
+		{
+			get
+			{
+				Asn1Encodable ae = digestAlgorithm.Parameters;
+
+				return ae == null ? null : ae.ToAsn1Object();
+			}
+		}
+
+		/**
+		 * return the content digest that was calculated during verification.
+		 */
+		public byte[] GetContentDigest()
+		{
+			if (resultDigest == null)
+			{
+				throw new InvalidOperationException("method can only be called after verify.");
+			}
+
+			return (byte[])resultDigest.Clone();
+		}
+
+		public AlgorithmIdentifier EncryptionAlgorithmID
+		{
+			get { return encryptionAlgorithm; }
+		}
+
+		/**
+		* return the object identifier for the signature.
+		*/
+		public string EncryptionAlgOid
+		{
+			get { return encryptionAlgorithm.ObjectID.Id; }
+		}
+
+		/**
+		* return the signature/encryption algorithm parameters, or null if
+		* there aren't any.
+		*/
+		public Asn1Object EncryptionAlgParams
+		{
+			get
+			{
+				Asn1Encodable ae = encryptionAlgorithm.Parameters;
+
+				return ae == null ? null : ae.ToAsn1Object();
+			}
+		}
+
+		/**
+		* return a table of the signed attributes - indexed by
+		* the OID of the attribute.
+		*/
+		public Asn1.Cms.AttributeTable SignedAttributes
+		{
+			get
+			{
+				if (signedAttributeSet != null && signedAttributeTable == null)
+				{
+					signedAttributeTable = new Asn1.Cms.AttributeTable(signedAttributeSet);
+				}
+				return signedAttributeTable;
+			}
+		}
+
+		/**
+		* return a table of the unsigned attributes indexed by
+		* the OID of the attribute.
+		*/
+		public Asn1.Cms.AttributeTable UnsignedAttributes
+		{
+			get
+			{
+				if (unsignedAttributeSet != null && unsignedAttributeTable == null)
+				{
+					unsignedAttributeTable = new Asn1.Cms.AttributeTable(unsignedAttributeSet);
+				}
+				return unsignedAttributeTable;
+			}
+		}
+
+		/**
+		* return the encoded signature
+		*/
+		public byte[] GetSignature()
+		{
+			return (byte[]) signature.Clone();
+		}
+
+		/**
+		* 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.
+			*/
+			Asn1.Cms.AttributeTable unsignedAttributeTable = UnsignedAttributes;
+			if (unsignedAttributeTable == null)
+			{
+                return new SignerInformationStore(Platform.CreateArrayList(0));
+			}
+
+            IList counterSignatures = Platform.CreateArrayList();
+
+			/*
+			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);
+
+			foreach (Asn1.Cms.Attribute counterSignatureAttribute in allCSAttrs)
+			{
+				/*
+				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.AttrValues;
+				if (values.Count < 1)
+				{
+					// TODO Throw an appropriate exception?
+				}
+
+				foreach (Asn1Encodable asn1Obj in values)
+				{
+					/*
+					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(asn1Obj.ToAsn1Object());
+
+					string digestName = CmsSignedHelper.Instance.GetDigestAlgName(si.DigestAlgorithm.ObjectID.Id);
+
+					counterSignatures.Add(new SignerInformation(si, null, null, new CounterSignatureDigestCalculator(digestName, GetSignature())));
+				}
+			}
+
+			return new SignerInformationStore(counterSignatures);
+		}
+
+		/**
+		* return the DER encoding of the signed attributes.
+		* @throws IOException if an encoding error occurs.
+		*/
+		public byte[] GetEncodedSignedAttributes()
+		{
+			return signedAttributeSet == null
+				?	null
+				:	signedAttributeSet.GetEncoded(Asn1Encodable.Der);
+		}
+
+		private bool DoVerify(
+			AsymmetricKeyParameter	key)
+		{
+			string digestName = Helper.GetDigestAlgName(this.DigestAlgOid);
+			IDigest digest = Helper.GetDigestInstance(digestName);
+
+			DerObjectIdentifier sigAlgOid = this.encryptionAlgorithm.ObjectID;
+			Asn1Encodable sigParams = this.encryptionAlgorithm.Parameters;
+			ISigner sig;
+
+			if (sigAlgOid.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdRsassaPss))
+			{
+				// RFC 4056 2.2
+				// When the id-RSASSA-PSS algorithm identifier is used for a signature,
+				// the AlgorithmIdentifier parameters field MUST contain RSASSA-PSS-params.
+				if (sigParams == null)
+					throw new CmsException("RSASSA-PSS signature must specify algorithm parameters");
+
+				try
+				{
+					// TODO Provide abstract configuration mechanism
+					// (via alternate SignerUtilities.GetSigner method taking ASN.1 params)
+
+					Asn1.Pkcs.RsassaPssParameters pss = Asn1.Pkcs.RsassaPssParameters.GetInstance(
+						sigParams.ToAsn1Object());
+
+					if (!pss.HashAlgorithm.ObjectID.Equals(this.digestAlgorithm.ObjectID))
+						throw new CmsException("RSASSA-PSS signature parameters specified incorrect hash algorithm");
+					if (!pss.MaskGenAlgorithm.ObjectID.Equals(Asn1.Pkcs.PkcsObjectIdentifiers.IdMgf1))
+						throw new CmsException("RSASSA-PSS signature parameters specified unknown MGF");
+
+					IDigest pssDigest = DigestUtilities.GetDigest(pss.HashAlgorithm.ObjectID);
+					int saltLength = pss.SaltLength.Value.IntValue;
+					byte trailerField = (byte) pss.TrailerField.Value.IntValue;
+
+					// RFC 4055 3.1
+					// The value MUST be 1, which represents the trailer field with hexadecimal value 0xBC
+					if (trailerField != 1)
+						throw new CmsException("RSASSA-PSS signature parameters must have trailerField of 1");
+
+					sig = new PssSigner(new RsaBlindedEngine(), pssDigest, saltLength);
+				}
+				catch (Exception e)
+				{
+					throw new CmsException("failed to set RSASSA-PSS signature parameters", e);
+				}
+			}
+			else
+			{
+				// TODO Probably too strong a check at the moment
+//				if (sigParams != null)
+//					throw new CmsException("unrecognised signature parameters provided");
+
+				string signatureName = digestName + "with" + Helper.GetEncryptionAlgName(this.EncryptionAlgOid);
+
+				sig = Helper.GetSignatureInstance(signatureName);
+			}
+
+			try
+			{
+				if (digestCalculator != null)
+				{
+					resultDigest = digestCalculator.GetDigest();
+				}
+				else
+				{
+					if (content != null)
+					{
+						content.Write(new DigOutputStream(digest));
+					}
+					else if (signedAttributeSet == null)
+					{
+						// 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 = DigestUtilities.DoFinal(digest);
+				}
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("can't process mime object to create signature.", e);
+			}
+
+			// RFC 3852 11.1 Check the content-type attribute is correct
+			{
+				Asn1Object 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 is DerObjectIdentifier))
+						throw new CmsException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
+
+					DerObjectIdentifier signedContentType = (DerObjectIdentifier)validContentType;
+
+					if (!signedContentType.Equals(contentType))
+						throw new CmsException("content-type attribute value does not match eContentType");
+				}
+			}
+
+			// RFC 3852 11.2 Check the message-digest attribute is correct
+			{
+				Asn1Object 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 is Asn1OctetString))
+					{
+						throw new CmsException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
+					}
+
+					Asn1OctetString signedMessageDigest = (Asn1OctetString)validMessageDigest;
+
+					if (!Arrays.AreEqual(resultDigest, signedMessageDigest.GetOctets()))
+						throw new CmsException("message-digest attribute value does not match calculated value");
+				}
+			}
+
+			// RFC 3852 11.4 Validate countersignature attribute(s)
+			{
+            	Asn1.Cms.AttributeTable signedAttrTable = this.SignedAttributes;
+            	if (signedAttrTable != null
+                	&& signedAttrTable.GetAll(CmsAttributes.CounterSignature).Count > 0)
+            	{
+                	throw new CmsException("A countersignature attribute MUST NOT be a signed attribute");
+            	}
+
+            	Asn1.Cms.AttributeTable unsignedAttrTable = this.UnsignedAttributes;
+            	if (unsignedAttrTable != null)
+            	{
+					foreach (Asn1.Cms.Attribute csAttr in unsignedAttrTable.GetAll(CmsAttributes.CounterSignature))
+	                {
+                    	if (csAttr.AttrValues.Count < 1)
+	                        throw new CmsException("A countersignature attribute MUST contain at least one AttributeValue");
+
+						// Note: We don't recursively validate the countersignature value
+    	            }
+	            }
+			}
+
+			try
+			{
+				sig.Init(false, key);
+
+				if (signedAttributeSet == null)
+				{
+					if (digestCalculator != null)
+					{
+						// need to decrypt signature and check message bytes
+						return VerifyDigest(resultDigest, key, this.GetSignature());
+					}
+					else if (content != null)
+					{
+						// TODO Use raw signature of the hash value instead
+						content.Write(new SigOutputStream(sig));
+					}
+				}
+				else
+				{
+					byte[] tmp = this.GetEncodedSignedAttributes();
+					sig.BlockUpdate(tmp, 0, tmp.Length);
+				}
+
+				return sig.VerifySignature(this.GetSignature());
+			}
+			catch (InvalidKeyException e)
+			{
+				throw new CmsException("key not appropriate to signature in message.", e);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("can't process mime object to create signature.", e);
+			}
+			catch (SignatureException e)
+			{
+				throw new CmsException("invalid signature format in message: " + e.Message, e);
+			}
+		}
+
+		private bool IsNull(
+			Asn1Encodable o)
+		{
+			return (o is Asn1Null) || (o == null);
+		}
+
+		private DigestInfo DerDecode(
+			byte[] encoding)
+		{
+			if (encoding[0] != (int)(Asn1Tags.Constructed | Asn1Tags.Sequence))
+			{
+				throw new IOException("not a digest info object");
+			}
+
+			DigestInfo digInfo = DigestInfo.GetInstance(Asn1Object.FromByteArray(encoding));
+
+			// length check to avoid Bleichenbacher vulnerability
+
+			if (digInfo.GetEncoded().Length != encoding.Length)
+			{
+				throw new CmsException("malformed RSA signature");
+			}
+
+			return digInfo;
+		}
+
+		private bool VerifyDigest(
+			byte[]					digest,
+			AsymmetricKeyParameter	key,
+			byte[]					signature)
+		{
+			string algorithm = Helper.GetEncryptionAlgName(this.EncryptionAlgOid);
+
+			try
+			{
+				if (algorithm.Equals("RSA"))
+				{
+					IBufferedCipher c = CmsEnvelopedHelper.Instance.CreateAsymmetricCipher("RSA/ECB/PKCS1Padding");
+
+					c.Init(false, key);
+
+					byte[] decrypt = c.DoFinal(signature);
+
+					DigestInfo digInfo = DerDecode(decrypt);
+
+					if (!digInfo.AlgorithmID.ObjectID.Equals(digestAlgorithm.ObjectID))
+					{
+						return false;
+					}
+
+					if (!IsNull(digInfo.AlgorithmID.Parameters))
+					{
+						return false;
+					}
+
+					byte[] sigHash = digInfo.GetDigest();
+
+					return Arrays.ConstantTimeAreEqual(digest, sigHash);
+				}
+				else if (algorithm.Equals("DSA"))
+				{
+					ISigner sig = SignerUtilities.GetSigner("NONEwithDSA");
+
+					sig.Init(false, key);
+
+					sig.BlockUpdate(digest, 0, digest.Length);
+
+					return sig.VerifySignature(signature);
+				}
+				else
+				{
+					throw new CmsException("algorithm: " + algorithm + " not supported in base signatures.");
+				}
+			}
+			catch (SecurityUtilityException e)
+			{
+				throw e;
+			}
+			catch (GeneralSecurityException e)
+			{
+				throw new CmsException("Exception processing signature: " + e, e);
+			}
+			catch (IOException e)
+			{
+				throw new CmsException("Exception decoding signature: " + e, e);
+			}
+		}
+
+		/**
+		* verify that the given public key successfully handles and confirms the
+		* signature associated with this signer.
+		*/
+		public bool Verify(
+			AsymmetricKeyParameter pubKey)
+		{
+			if (pubKey.IsPrivate)
+				throw new ArgumentException("Expected public key", "pubKey");
+
+			// Optional, but still need to validate if present
+			GetSigningTime();
+
+			return DoVerify(pubKey);
+		}
+
+		/**
+		* verify that the given certificate successfully handles and confirms
+		* the signature associated with this signer and, if a signingTime
+		* attribute is available, that the certificate was valid at the time the
+		* signature was generated.
+		*/
+		public bool Verify(
+			X509Certificate cert)
+		{
+			Asn1.Cms.Time signingTime = GetSigningTime();
+			if (signingTime != null)
+			{
+				cert.CheckValidity(signingTime.Date);
+			}
+
+			return DoVerify(cert.GetPublicKey());
+		}
+
+		/**
+		* Return the base ASN.1 CMS structure that this object contains.
+		*
+		* @return an object containing a CMS SignerInfo structure.
+		*/
+		public SignerInfo ToSignerInfo()
+		{
+			return info;
+		}
+
+		private Asn1Object GetSingleValuedSignedAttribute(
+			DerObjectIdentifier attrOID, string printableName)
+		{
+
+			Asn1.Cms.AttributeTable unsignedAttrTable = this.UnsignedAttributes;
+			if (unsignedAttrTable != null
+				&& unsignedAttrTable.GetAll(attrOID).Count > 0)
+			{
+				throw new CmsException("The " + printableName
+					+ " attribute MUST NOT be an unsigned attribute");
+			}
+
+			Asn1.Cms.AttributeTable signedAttrTable = this.SignedAttributes;
+			if (signedAttrTable == null)
+			{
+				return null;
+			}
+
+			Asn1EncodableVector v = signedAttrTable.GetAll(attrOID);
+			switch (v.Count)
+			{
+				case 0:
+					return null;
+				case 1:
+					Asn1.Cms.Attribute t = (Asn1.Cms.Attribute) v[0];
+					Asn1Set attrValues = t.AttrValues;
+
+					if (attrValues.Count != 1)
+						throw new CmsException("A " + printableName
+							+ " attribute MUST have a single attribute value");
+
+					return attrValues[0].ToAsn1Object();
+				default:
+					throw new CmsException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
+						+ printableName + " attribute");
+			}
+		}
+
+		private Asn1.Cms.Time GetSigningTime()
+		{
+			Asn1Object validSigningTime = GetSingleValuedSignedAttribute(
+				CmsAttributes.SigningTime, "signing-time");
+
+			if (validSigningTime == null)
+				return null;
+
+			try
+			{
+				return Asn1.Cms.Time.GetInstance(validSigningTime);
+			}
+			catch (ArgumentException)
+			{
+				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,
+			Asn1.Cms.AttributeTable	unsignedAttributes)
+		{
+			SignerInfo sInfo = signerInformation.info;
+			Asn1Set unsignedAttr = null;
+
+			if (unsignedAttributes != null)
+			{
+				unsignedAttr = new DerSet(unsignedAttributes.ToAsn1EncodableVector());
+			}
+
+			return new SignerInformation(
+				new SignerInfo(
+					sInfo.SignerID,
+					sInfo.DigestAlgorithm,
+					sInfo.AuthenticatedAttributes,
+					sInfo.DigestEncryptionAlgorithm,
+					sInfo.EncryptedDigest,
+					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;
+			Asn1.Cms.AttributeTable unsignedAttr = signerInformation.UnsignedAttributes;
+			Asn1EncodableVector v;
+
+			if (unsignedAttr != null)
+			{
+				v = unsignedAttr.ToAsn1EncodableVector();
+			}
+			else
+			{
+				v = new Asn1EncodableVector();
+			}
+
+			Asn1EncodableVector sigs = new Asn1EncodableVector();
+
+			foreach (SignerInformation sigInf in counterSigners.GetSigners())
+			{
+				sigs.Add(sigInf.ToSignerInfo());
+			}
+
+			v.Add(new Asn1.Cms.Attribute(CmsAttributes.CounterSignature, new DerSet(sigs)));
+
+			return new SignerInformation(
+				new SignerInfo(
+					sInfo.SignerID,
+					sInfo.DigestAlgorithm,
+					sInfo.AuthenticatedAttributes,
+					sInfo.DigestEncryptionAlgorithm,
+					sInfo.EncryptedDigest,
+					new DerSet(v)),
+				signerInformation.contentType,
+				signerInformation.content,
+				null);
+		}
+	}
+}
diff --git a/Crypto/src/cms/SignerInformationStore.cs b/Crypto/src/cms/SignerInformationStore.cs
new file mode 100644
index 000000000..bd613843d
--- /dev/null
+++ b/Crypto/src/cms/SignerInformationStore.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Cms
+{
+    public class SignerInformationStore
+    {
+		private readonly IList all; //ArrayList[SignerInformation]
+		private readonly IDictionary table = Platform.CreateHashtable(); // Hashtable[SignerID, ArrayList[SignerInformation]]
+
+		public SignerInformationStore(
+            ICollection signerInfos)
+        {
+            foreach (SignerInformation signer in signerInfos)
+            {
+                SignerID sid = signer.SignerID;
+                IList list = (IList)table[sid];
+
+				if (list == null)
+				{
+					table[sid] = list = Platform.CreateArrayList(1);
+				}
+
+				list.Add(signer);
+            }
+
+            this.all = Platform.CreateArrayList(signerInfos);
+        }
+
+        /**
+        * Return the first SignerInformation object that matches the
+        * passed in selector. Null if there are no matches.
+        *
+        * @param selector to identify a signer
+        * @return a single SignerInformation object. Null if none matches.
+        */
+        public SignerInformation GetFirstSigner(
+            SignerID selector)
+        {
+			IList list = (IList) table[selector];
+
+			return list == null ? null : (SignerInformation) list[0];
+        }
+
+		/// <summary>The number of signers in the collection.</summary>
+		public int Count
+        {
+			get { return all.Count; }
+        }
+
+		/// <returns>An ICollection of all signers in the collection</returns>
+        public ICollection GetSigners()
+        {
+            return Platform.CreateArrayList(all);
+        }
+
+		/**
+        * Return possible empty collection with signers matching the passed in SignerID
+        *
+        * @param selector a signer id to select against.
+        * @return a collection of SignerInformation objects.
+        */
+        public ICollection GetSigners(
+            SignerID selector)
+        {
+			IList list = (IList) table[selector];
+
+            return list == null ? Platform.CreateArrayList() : Platform.CreateArrayList(list);
+        }
+    }
+}
diff --git a/Crypto/src/cms/SimpleAttributeTableGenerator.cs b/Crypto/src/cms/SimpleAttributeTableGenerator.cs
new file mode 100644
index 000000000..b3df21c29
--- /dev/null
+++ b/Crypto/src/cms/SimpleAttributeTableGenerator.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Asn1.Cms;
+
+namespace Org.BouncyCastle.Cms
+{
+	/**
+	 * Basic generator that just returns a preconstructed attribute table
+	 */
+	public class SimpleAttributeTableGenerator
+		: CmsAttributeTableGenerator
+	{
+		private readonly AttributeTable attributes;
+
+		public SimpleAttributeTableGenerator(
+			AttributeTable attributes)
+		{
+			this.attributes = attributes;
+		}
+
+		public virtual AttributeTable GetAttributes(
+			IDictionary parameters)
+		{
+			return attributes;
+		}
+	}
+}