context: //
  // PKCS8.cs: PKCS #8 - Private-Key Information Syntax Standard
  //	ftp://ftp.rsasecurity.com/pub/pkcs/doc/pkcs-8.doc
  //
  // Author:
  //	Sebastien Pouliot <sebastien@ximian.com>
  //
  // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
  // Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections;
  using System.Security.Cryptography;
  using System.Text;
  
  using CryptoNet.Security.X509;
  
  namespace CryptoNet.Security.Cryptography {
  
  
      internal sealed class PKCS8
      {
  
          internal enum KeyInfo
          {
              PrivateKey,
              EncryptedPrivateKey,
              Unknown
          }
  
          private PKCS8()
          {
          }
  
          static public KeyInfo GetType(byte[] data)
          {
              if (data == null)
                  throw new ArgumentNullException("data");
  
              KeyInfo ki = KeyInfo.Unknown;
              try
              {
                  ASN1 top = new ASN1(data);
                  if ((top.Tag == 0x30) && (top.Count > 0))
                  {
                      ASN1 firstLevel = top[0];
                      switch (firstLevel.Tag)
                      {
                          case 0x02:
                              ki = KeyInfo.PrivateKey;
                              break;
                          case 0x30:
                              ki = KeyInfo.EncryptedPrivateKey;
                              break;
                      }
                  }
              }
              catch
              {
                  throw new CryptographicException("invalid ASN.1 data");
              }
              return ki;
          }
  
          /*
           * PrivateKeyInfo ::= SEQUENCE {
           *	version Version,
           *	privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
           *	privateKey PrivateKey,
           *	attributes [0] IMPLICIT Attributes OPTIONAL 
           * }
           * 
           * Version ::= INTEGER
           * 
           * PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
           * 
           * PrivateKey ::= OCTET STRING
           * 
           * Attributes ::= SET OF Attribute
           */
          internal class PrivateKeyInfo
          {
  
              private int _version;
              private string _algorithm;
              private byte[] _key;
              private ArrayList _list;
  
              public PrivateKeyInfo()
              {
                  _version = 0;
                  _list = new ArrayList();
              }
  
              public PrivateKeyInfo(byte[] data)
                  : this()
              {
                  Decode(data);
              }
  
              // properties
  
              public string Algorithm
              {
                  get { return _algorithm; }
                  set { _algorithm = value; }
              }
  
              public ArrayList Attributes
              {
                  get { return _list; }
              }
  
              public byte[] PrivateKey
              {
                  get
                  {
                      if (_key == null)
                          return null;
                      return (byte[])_key.Clone();
                  }
                  set
                  {
                      if (value == null)
                          throw new ArgumentNullException("PrivateKey");
                      _key = (byte[])value.Clone();
                  }
              }
  
              public int Version
              {
                  get { return _version; }
                  set
                  {
                      if (value < 0)
                          throw new ArgumentOutOfRangeException("negative version");
                      _version = value;
                  }
              }
  
              // methods
  
              private void Decode(byte[] data)
              {
                  ASN1 privateKeyInfo = new ASN1(data);
                  if (privateKeyInfo.Tag != 0x30)
                      throw new CryptographicException("invalid PrivateKeyInfo");
  
                  ASN1 version = privateKeyInfo[0];
                  if (version.Tag != 0x02)
                      throw new CryptographicException("invalid version");
                  _version = version.Value[0];
  
                  ASN1 privateKeyAlgorithm = privateKeyInfo[1];
                  if (privateKeyAlgorithm.Tag != 0x30)
                      throw new CryptographicException("invalid algorithm");
  
                  ASN1 algorithm = privateKeyAlgorithm[0];
                  if (algorithm.Tag != 0x06)
                      throw new CryptographicException("missing algorithm OID");
                  _algorithm = ASN1Convert.ToOid(algorithm);
  
                  ASN1 privateKey = privateKeyInfo[2];
                  _key = privateKey.Value;
  
                  // attributes [0] IMPLICIT Attributes OPTIONAL
                  if (privateKeyInfo.Count > 3)
                  {
                      ASN1 attributes = privateKeyInfo[3];
                      for (int i = 0; i < attributes.Count; i++)
                      {
                          _list.Add(attributes[i]);
                      }
                  }
              }
  
              public byte[] GetBytes()
              {
                  ASN1 privateKeyAlgorithm = new ASN1(0x30);
                  privateKeyAlgorithm.Add(ASN1Convert.FromOid(_algorithm));
                  privateKeyAlgorithm.Add(new ASN1(0x05)); // ASN.1 NULL
  
                  ASN1 pki = new ASN1(0x30);
                  pki.Add(new ASN1(0x02, new byte[1] { (byte)_version }));
                  pki.Add(privateKeyAlgorithm);
                  pki.Add(new ASN1(0x04, _key));
  
                  if (_list.Count > 0)
                  {
                      ASN1 attributes = new ASN1(0xA0);
                      foreach (ASN1 attribute in _list)
                      {
                          attributes.Add(attribute);
                      }
                      pki.Add(attributes);
                  }
  
                  return pki.GetBytes();
              }
  
              // static methods
  
              static private byte[] RemoveLeadingZero(byte[] bigInt)
              {
                  int start = 0;
                  int length = bigInt.Length;
                  if (bigInt[0] == 0x00)
                  {
                      start = 1;
                      length--;
                  }
                  byte[] bi = new byte[length];
                  Buffer.BlockCopy(bigInt, start, bi, 0, length);
                  return bi;
              }
  
              static private byte[] Normalize(byte[] bigInt, int length)
              {
                  if (bigInt.Length == length)
                      return bigInt;
                  else if (bigInt.Length > length)
                      return RemoveLeadingZero(bigInt);
                  else
                  {
                      // pad with 0
                      byte[] bi = new byte[length];
                      Buffer.BlockCopy(bigInt, 0, bi, (length - bigInt.Length), bigInt.Length);
                      return bi;
                  }
              }
  
              /*
               * RSAPrivateKey ::= SEQUENCE {
               *	version           Version, 
               *	modulus           INTEGER,  -- n
               *	publicExponent    INTEGER,  -- e
               *	privateExponent   INTEGER,  -- d
               *	prime1            INTEGER,  -- p
               *	prime2            INTEGER,  -- q
               *	exponent1         INTEGER,  -- d mod (p-1)
               *	exponent2         INTEGER,  -- d mod (q-1) 
               *	coefficient       INTEGER,  -- (inverse of q) mod p
               *	otherPrimeInfos   OtherPrimeInfos OPTIONAL 
               * }
               */
              static public RSA DecodeRSA(byte[] keypair)
              {
                  ASN1 privateKey = new ASN1(keypair);
                  if (privateKey.Tag != 0x30)
                      throw new CryptographicException("invalid private key format");
  
                  ASN1 version = privateKey[0];
                  if (version.Tag != 0x02)
                      throw new CryptographicException("missing version");
  
                  if (privateKey.Count < 9)
                      throw new CryptographicException("not enough key parameters");
  
                  RSAParameters param = new RSAParameters();
                  // note: MUST remove leading 0 - else MS wont import the key
                  param.Modulus = RemoveLeadingZero(privateKey[1].Value);
                  int keysize = param.Modulus.Length;
                  int keysize2 = (keysize >> 1); // half-size
                  // size must be normalized - else MS wont import the key
                  param.D = Normalize(privateKey[3].Value, keysize);
                  param.DP = Normalize(privateKey[6].Value, keysize2);
                  param.DQ = Normalize(privateKey[7].Value, keysize2);
                  param.Exponent = RemoveLeadingZero(privateKey[2].Value);
                  param.InverseQ = Normalize(privateKey[8].Value, keysize2);
                  param.P = Normalize(privateKey[4].Value, keysize2);
                  param.Q = Normalize(privateKey[5].Value, keysize2);
  
                  RSA rsa = null;
                  try
                  {
                      rsa = RSA.Create();
                      rsa.ImportParameters(param);
                  }
                  catch (CryptographicException)
                  {
                      // this may cause problem when this code is run under
                      // the SYSTEM identity on Windows (e.g. ASP.NET). See
                      // http://bugzilla.ximian.com/show_bug.cgi?id=77559
                      CspParameters csp = new CspParameters();
                      csp.Flags = CspProviderFlags.UseMachineKeyStore;
                      rsa = new RSACryptoServiceProvider(csp);
                      rsa.ImportParameters(param);
                  }
                  return rsa;
              }
  
              /*
               * RSAPrivateKey ::= SEQUENCE {
               *	version           Version, 
               *	modulus           INTEGER,  -- n
               *	publicExponent    INTEGER,  -- e
               *	privateExponent   INTEGER,  -- d
               *	prime1            INTEGER,  -- p
               *	prime2            INTEGER,  -- q
               *	exponent1         INTEGER,  -- d mod (p-1)
               *	exponent2         INTEGER,  -- d mod (q-1) 
               *	coefficient       INTEGER,  -- (inverse of q) mod p
               *	otherPrimeInfos   OtherPrimeInfos OPTIONAL 
               * }
               */
              static public byte[] Encode(RSA rsa)
              {
                  RSAParameters param = rsa.ExportParameters(true);
  
                  ASN1 rsaPrivateKey = new ASN1(0x30);
                  rsaPrivateKey.Add(new ASN1(0x02, new byte[1] { 0x00 }));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.Modulus));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.Exponent));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.D));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.P));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.Q));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.DP));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.DQ));
                  rsaPrivateKey.Add(ASN1Convert.FromUnsignedBigInteger(param.InverseQ));
  
                  return rsaPrivateKey.GetBytes();
              }
  
              // DSA only encode it's X private key inside an ASN.1 INTEGER (Hint: Tag == 0x02)
              // which isn't enough for rebuilding the keypair. The other parameters
              // can be found (98% of the time) in the X.509 certificate associated
              // with the private key or (2% of the time) the parameters are in it's
              // issuer X.509 certificate (not supported in the .NET framework).
              static public DSA DecodeDSA(byte[] privateKey, DSAParameters dsaParameters)
              {
                  ASN1 pvk = new ASN1(privateKey);
                  if (pvk.Tag != 0x02)
                      throw new CryptographicException("invalid private key format");
  
                  // X is ALWAYS 20 bytes (no matter if the key length is 512 or 1024 bits)
                  dsaParameters.X = Normalize(pvk.Value, 20);
                  DSA dsa = DSA.Create();
                  dsa.ImportParameters(dsaParameters);
                  return dsa;
              }
  
              static public byte[] Encode(DSA dsa)
              {
                  DSAParameters param = dsa.ExportParameters(true);
                  return ASN1Convert.FromUnsignedBigInteger(param.X).GetBytes();
              }
  
              static public byte[] Encode(AsymmetricAlgorithm aa)
              {
                  if (aa is RSA)
                      return Encode((RSA)aa);
                  else if (aa is DSA)
                      return Encode((DSA)aa);
                  else
                      throw new CryptographicException("Unknown asymmetric algorithm {0}", aa.ToString());
              }
          }
  
          /*
           * EncryptedPrivateKeyInfo ::= SEQUENCE {
           *	encryptionAlgorithm EncryptionAlgorithmIdentifier,
           *	encryptedData EncryptedData 
           * }
           * 
           * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
           * 
           * EncryptedData ::= OCTET STRING
           * 
           * --
           *  AlgorithmIdentifier  ::= SEQUENCE {
           *	algorithm  OBJECT IDENTIFIER,
           *	parameters ANY DEFINED BY algorithm OPTIONAL
           * }
           * 
           * -- from PKCS#5
           * PBEParameter ::= SEQUENCE {
           *	salt OCTET STRING SIZE(8),
           *	iterationCount INTEGER 
           * }
           */
          internal class EncryptedPrivateKeyInfo
          {
  
              private string _algorithm;
              private byte[] _salt;
              private int _iterations;
              private byte[] _data;
  
              public EncryptedPrivateKeyInfo() { }
  
              public EncryptedPrivateKeyInfo(byte[] data)
                  : this()
              {
                  Decode(data);
              }
  
              // properties
  
              public string Algorithm
              {
                  get { return _algorithm; }
                  set { _algorithm = value; }
              }
  
              public byte[] EncryptedData
              {
                  get { return (_data == null) ? null : (byte[])_data.Clone(); }
  
gt:                 set { _data = (value == null) ? null : (byte[])value.Clone(); }