context: using System;
  using System.Collections;
  using System.IO;
  
  using Org.BouncyCastle.Crypto;
  using Org.BouncyCastle.Crypto.Parameters;
  using Org.BouncyCastle.Security;
  
  namespace Org.BouncyCastle.Bcpg.OpenPgp
  {
  	/// <remarks>General class to handle a PGP secret key object.</remarks>
      public class PgpSecretKey
      {
          private readonly SecretKeyPacket	secret;
          private readonly PgpPublicKey		pub;
  
  		internal PgpSecretKey(
  			SecretKeyPacket	secret,
  			PgpPublicKey	pub)
  		{
  			this.secret = secret;
  			this.pub = pub;
  		}
  
  		internal PgpSecretKey(
  			PgpPrivateKey				privKey,
  			PgpPublicKey				pubKey,
  			SymmetricKeyAlgorithmTag	encAlgorithm,
  			char[]						passPhrase,
  			bool						useSha1,
  			SecureRandom				rand)
  			: this(privKey, pubKey, encAlgorithm, passPhrase, useSha1, rand, false)
  		{
  		}
  
  		internal PgpSecretKey(
  			PgpPrivateKey				privKey,
  			PgpPublicKey				pubKey,
  			SymmetricKeyAlgorithmTag	encAlgorithm,
              char[]						passPhrase,
  			bool						useSha1,
  			SecureRandom				rand,
  			bool						isMasterKey)
          {
  			BcpgObject secKey;
  
  			this.pub = pubKey;
  
  			switch (pubKey.Algorithm)
              {
  				case PublicKeyAlgorithmTag.RsaEncrypt:
  				case PublicKeyAlgorithmTag.RsaSign:
  				case PublicKeyAlgorithmTag.RsaGeneral:
  					RsaPrivateCrtKeyParameters rsK = (RsaPrivateCrtKeyParameters) privKey.Key;
  					secKey = new RsaSecretBcpgKey(rsK.Exponent, rsK.P, rsK.Q);
  					break;
  				case PublicKeyAlgorithmTag.Dsa:
  					DsaPrivateKeyParameters dsK = (DsaPrivateKeyParameters) privKey.Key;
  					secKey = new DsaSecretBcpgKey(dsK.X);
  					break;
  				case PublicKeyAlgorithmTag.ElGamalEncrypt:
  				case PublicKeyAlgorithmTag.ElGamalGeneral:
  					ElGamalPrivateKeyParameters esK = (ElGamalPrivateKeyParameters) privKey.Key;
  					secKey = new ElGamalSecretBcpgKey(esK.X);
  					break;
  				default:
  					throw new PgpException("unknown key class");
              }
  
  			try
              {
                  MemoryStream bOut = new MemoryStream();
                  BcpgOutputStream pOut = new BcpgOutputStream(bOut);
  
  				pOut.WriteObject(secKey);
  
  				byte[] keyData = bOut.ToArray();
  				byte[] checksumBytes = Checksum(useSha1, keyData, keyData.Length);
  
  				pOut.Write(checksumBytes);
  
  				byte[] bOutData = bOut.ToArray();
  
  				if (encAlgorithm == SymmetricKeyAlgorithmTag.Null)
  				{
  					if (isMasterKey)
  					{
  						this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
  					}
  					else
  					{
  						this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, null, null, bOutData);
  					}
  				}
  				else
                  {
  					S2k s2k;
  					byte[] iv;
  					byte[] encData = EncryptKeyData(bOutData, encAlgorithm, passPhrase, rand, out s2k, out iv);
  
  					int s2kUsage = useSha1
  						?	SecretKeyPacket.UsageSha1
  						:	SecretKeyPacket.UsageChecksum;
  
  					if (isMasterKey)
  					{
  						this.secret = new SecretKeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
  					}
  					else
  					{
  						this.secret = new SecretSubkeyPacket(pub.publicPk, encAlgorithm, s2kUsage, s2k, iv, encData);
  					}
  				}
              }
              catch (PgpException e)
              {
                  throw e;
              }
              catch (Exception e)
              {
                  throw new PgpException("Exception encrypting key", e);
              }
          }
  
  		public PgpSecretKey(
              int							certificationLevel,
              PgpKeyPair					keyPair,
              string						id,
              SymmetricKeyAlgorithmTag	encAlgorithm,
              char[]						passPhrase,
              PgpSignatureSubpacketVector	hashedPackets,
              PgpSignatureSubpacketVector	unhashedPackets,
              SecureRandom				rand)
  			: this(certificationLevel, keyPair, id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand)
  		{
  		}
  
  		public PgpSecretKey(
  			int							certificationLevel,
  			PgpKeyPair					keyPair,
  			string						id,
  			SymmetricKeyAlgorithmTag	encAlgorithm,
  			char[]						passPhrase,
  			bool						useSha1,
  			PgpSignatureSubpacketVector	hashedPackets,
  			PgpSignatureSubpacketVector	unhashedPackets,
  			SecureRandom				rand)
  			: this(keyPair.PrivateKey, certifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true)
  		{
  		}
  
  		private static PgpPublicKey certifiedPublicKey(
  			int							certificationLevel,
  			PgpKeyPair					keyPair,
  			string						id,
  			PgpSignatureSubpacketVector	hashedPackets,
  			PgpSignatureSubpacketVector	unhashedPackets)
  		{
  			PgpSignatureGenerator sGen;
  			try
  			{
  				sGen = new PgpSignatureGenerator(keyPair.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
  			}
  			catch (Exception e)
  			{
  				throw new PgpException("Creating signature generator: " + e.Message, e);
  			}
  
  			//
  			// Generate the certification
  			//
  			sGen.InitSign(certificationLevel, keyPair.PrivateKey);
  
  			sGen.SetHashedSubpackets(hashedPackets);
  			sGen.SetUnhashedSubpackets(unhashedPackets);
  
  			try
              {
  				PgpSignature certification = sGen.GenerateCertification(id, keyPair.PublicKey);
                  return PgpPublicKey.AddCertification(keyPair.PublicKey, id, certification);
              }
              catch (Exception e)
              {
  				throw new PgpException("Exception doing certification: " + e.Message, e);
  			}
          }
  
  		public PgpSecretKey(
              int							certificationLevel,
              PublicKeyAlgorithmTag		algorithm,
              AsymmetricKeyParameter		pubKey,
              AsymmetricKeyParameter		privKey,
              DateTime					time,
              string						id,
              SymmetricKeyAlgorithmTag	encAlgorithm,
              char[]						passPhrase,
              PgpSignatureSubpacketVector	hashedPackets,
              PgpSignatureSubpacketVector	unhashedPackets,
              SecureRandom				rand)
              : this(certificationLevel,
                  new PgpKeyPair(algorithm, pubKey, privKey, time),
                  id, encAlgorithm, passPhrase, hashedPackets, unhashedPackets, rand)
          {
          }
  
  		public PgpSecretKey(
  			int							certificationLevel,
  			PublicKeyAlgorithmTag		algorithm,
  			AsymmetricKeyParameter		pubKey,
  			AsymmetricKeyParameter		privKey,
  			DateTime					time,
  			string						id,
  			SymmetricKeyAlgorithmTag	encAlgorithm,
  			char[]						passPhrase,
  			bool						useSha1,
  			PgpSignatureSubpacketVector	hashedPackets,
  			PgpSignatureSubpacketVector	unhashedPackets,
  			SecureRandom				rand)
  			: this(certificationLevel, new PgpKeyPair(algorithm, pubKey, privKey, time), id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)
  		{
  		}
  
  		/// <summary>
  		/// Check if this key has an algorithm type that makes it suitable to use for signing.
  		/// </summary>
  		/// <remarks>
  		/// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
  		/// determining the preferred use of the key.
  		/// </remarks>
  		/// <returns>
  		/// <c>true</c> if this key algorithm is suitable for use with signing.
  		/// </returns>
  		public bool IsSigningKey
          {
  			get
  			{
  				switch (pub.Algorithm)
  				{
  					case PublicKeyAlgorithmTag.RsaGeneral:
  					case PublicKeyAlgorithmTag.RsaSign:
  					case PublicKeyAlgorithmTag.Dsa:
  					case PublicKeyAlgorithmTag.ECDsa:
  					case PublicKeyAlgorithmTag.ElGamalGeneral:
  						return true;
  					default:
  						return false;
  				}
  			}
          }
  
  		/// <summary>True, if this is a master key.</summary>
          public bool IsMasterKey
  		{
  			get { return pub.IsMasterKey; }
          }
  
  		/// <summary>The algorithm the key is encrypted with.</summary>
          public SymmetricKeyAlgorithmTag KeyEncryptionAlgorithm
          {
  			get { return secret.EncAlgorithm; }
          }
  
  		/// <summary>The key ID of the public key associated with this key.</summary>
          public long KeyId
          {
              get { return pub.KeyId; }
          }
  
  		/// <summary>The public key associated with this key.</summary>
          public PgpPublicKey PublicKey
          {
  			get { return pub; }
          }
  
  		/// <summary>Allows enumeration of any user IDs associated with the key.</summary>
  		/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
          public IEnumerable UserIds
          {
  			get { return pub.GetUserIds(); }
          }
  
  		/// <summary>Allows enumeration of any user attribute vectors associated with the key.</summary>
  		/// <returns>An <c>IEnumerable</c> of <c>string</c> objects.</returns>
          public IEnumerable UserAttributes
          {
  			get { return pub.GetUserAttributes(); }
          }
  
  		private byte[] ExtractKeyData(
              char[] passPhrase)
          {
              SymmetricKeyAlgorithmTag alg = secret.EncAlgorithm;
  			byte[] encData = secret.GetSecretKeyData();
  
  			if (alg == SymmetricKeyAlgorithmTag.Null)
  				return encData;
  
  			byte[] data;
  			IBufferedCipher c = null;
  			try
  			{
  				string cName = PgpUtilities.GetSymmetricCipherName(alg);
  				c = CipherUtilities.GetCipher(cName + "/CFB/NoPadding");
  			}
  			catch (Exception e)
  			{
  				throw new PgpException("Exception creating cipher", e);
  			}
  
  			// TODO Factor this block out as 'encryptData'
  			try
              {
  				KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase);
  				byte[] iv = secret.GetIV();
  
  				if (secret.PublicKeyPacket.Version == 4)
                  {
  					c.Init(false, new ParametersWithIV(key, iv));
  
  					data = c.DoFinal(encData);
  
  
gt: 					bool useSha1 = secret.S2kUsage == SecretKeyPacket.UsageSha1;