context: using System;
  using System.Collections;
  using System.Diagnostics;
  using System.IO;
  
  using Nequeo.Cryptography.Key.Asn1;
  using Nequeo.Cryptography.Key.Asn1.Cms;
  using Nequeo.Cryptography.Key.Asn1.X509;
  using Nequeo.Cryptography.Key.Crypto;
  using Nequeo.Cryptography.Key.Crypto.Engines;
  using Nequeo.Cryptography.Key.Crypto.Signers;
  using Nequeo.Cryptography.Key.Security;
  using Nequeo.Cryptography.Key.Utilities;
  using Nequeo.Cryptography.Key.X509;
  
  namespace Nequeo.Cryptography.Key.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;
  
gt: 					byte trailerField = (byte) pss.TrailerField.Value.IntValue;