lib/powershell-pulp-main.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
 
namespace PowershellPulp {
   
  // From: https://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back?msg=4339068
  public enum PemStringType {
    Certificate,
    RsaPrivateKey
  }
 
  public class Helpers {
    // This helper function parses an integer size from the reader using the
    // ASN.1 format
    public static int DecodeIntegerSize(System.IO.BinaryReader rd) {
      byte byteValue;
      int count;
 
      byteValue = rd.ReadByte();
      if (byteValue != 0x02) // Indicates an ASN.1 integer value follows
        return 0;
      byteValue = rd.ReadByte();
      if (byteValue == 0x81) {
        count = rd.ReadByte(); // Data size is the following byte
      }
      else if (byteValue == 0x82) {
        byte hi = rd.ReadByte(); // Data size in next 2 bytes
        byte lo = rd.ReadByte();
        count = BitConverter.ToUInt16(new[] { lo, hi }, 0);
      }
      else {
        count = byteValue; // We already have the data size
      }
      // Remove high order zeros in data
      while (rd.ReadByte() == 0x00) {
        count -= 1;
      }
      rd.BaseStream.Seek(-1, System.IO.SeekOrigin.Current);
      return count;
    }
 
    public static byte[] GetBytesFromPEM(string pemString, PemStringType type) {
      string header; string footer;
 
      switch (type) {
        case PemStringType.Certificate:
          header = "-----BEGIN CERTIFICATE-----";
          footer = "-----END CERTIFICATE-----";
          break;
        case PemStringType.RsaPrivateKey:
          header = "-----BEGIN RSA PRIVATE KEY-----";
          footer = "-----END RSA PRIVATE KEY-----";
          break;
        default:
          return null;
      }
 
      int start = pemString.IndexOf(header) + header.Length;
      int end = pemString.IndexOf(footer, start) - start;
      return Convert.FromBase64String(pemString.Substring(start, end));
    }
 
 
    public static byte[] AlignBytes(byte[] inputBytes, int alignSize) {
      int inputBytesSize = inputBytes.Length;
 
      if ((alignSize != -1) && (inputBytesSize < alignSize)) {
        byte[] buf = new byte[alignSize];
        for (int i = 0; i < inputBytesSize; ++i) {
            buf[i + (alignSize - inputBytesSize)] = inputBytes[i];
        }
        return buf;
      }
      else {
        return inputBytes; // Already aligned, or doesn't need alignment
      }
    }
  }
 
  internal class RSAParameterTraits {
    public RSAParameterTraits(int modulusLengthInBits) {
      /* The modulus length is supposed to be one of the common lengths, which
         is the commonly referred to strength of the key, like 1024 bit,
         2048 bit, etc. It might be a few bits off though, since if the modulus
         has leading zeros it could show up as 1016 bits or something like
         that. */
      int assumedLength = -1;
      double logbase = Math.Log(modulusLengthInBits, 2);
 
      if (logbase == (int)logbase) {
        // It's already an even power of 2
        assumedLength = modulusLengthInBits;
      }
      else {
        // It's not an even power of 2, so round it up to the nearest
        // power of 2.
        assumedLength = (int)(logbase + 1.0);
        assumedLength = (int)(Math.Pow(2, assumedLength));
        // Can this really happen in the field? I've never seen it, so if it
        // happens you should verify that this really does the 'right' thing!
        System.Diagnostics.Debug.Assert(false);
      }
 
      switch (assumedLength) {
        case 1024:
          this.size_Mod = 0x80;
          this.size_Exp = -1;
          this.size_D = 0x80;
          this.size_P = 0x40;
          this.size_Q = 0x40;
          this.size_DP = 0x40;
          this.size_DQ = 0x40;
          this.size_InvQ = 0x40;
          break;
        case 2048:
          this.size_Mod = 0x100;
          this.size_Exp = -1;
          this.size_D = 0x100;
          this.size_P = 0x80;
          this.size_Q = 0x80;
          this.size_DP = 0x80;
          this.size_DQ = 0x80;
          this.size_InvQ = 0x80;
          break;
        case 4096:
          this.size_Mod = 0x200;
          this.size_Exp = -1;
          this.size_D = 0x200;
          this.size_P = 0x100;
          this.size_Q = 0x100;
          this.size_DP = 0x100;
          this.size_DQ = 0x100;
          this.size_InvQ = 0x100;
          break;
        default:
          System.Diagnostics.Debug.Assert(false); // Unknown key size?
          break;
      }
    }
 
    public int size_Mod = -1;
    public int size_Exp = -1;
    public int size_D = -1;
    public int size_P = -1;
    public int size_Q = -1;
    public int size_DP = -1;
    public int size_DQ = -1;
    public int size_InvQ = -1;
  }
 
  public class Crypto {
    /* This helper function parses an RSA private key using the ASN.1 format
       - takes a Byte array containing PEM string of private key
       - returns an instance of RSACryptoServiceProvider representing the
         requested private key
       - null if method fails on retriving the key. */
    public static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] privateKeyBytes) {
      MemoryStream ms = new MemoryStream(privateKeyBytes);
      BinaryReader rd = new BinaryReader(ms);
 
      try {
        byte byteValue;
        ushort shortValue;
 
        shortValue = rd.ReadUInt16();
 
        switch (shortValue) {
          case 0x8130:
            // If true, data is little endian since the proper logical seq is
            // 0x30 0x81
            rd.ReadByte(); // Advance 1 byte
            break;
          case 0x8230:
            rd.ReadInt16(); // Advance 2 bytes
            break;
          default:
            Debug.Assert(false); // Improper ASN.1 format
            return null;
          }
 
        shortValue = rd.ReadUInt16();
        if (shortValue != 0x0102) { // Version number
          Debug.Assert(false); // Improper ASN.1 format, unexpected version number
          return null;
        }
 
        byteValue = rd.ReadByte();
        if (byteValue != 0x00) {
          Debug.Assert(false); // Improper ASN.1 format
          return null;
        }
 
        /* The data following the version will be the ASN.1 data itself, which
           in our case
           are a sequence of integers.
 
           In order to solve a problem with instancing RSACryptoServiceProvider
           via default constructor on .net 4.0 this is a hack */
        CspParameters parms = new CspParameters();
        parms.Flags = CspProviderFlags.NoFlags;
        parms.KeyContainerName = Guid.NewGuid().ToString().ToUpperInvariant();
        parms.ProviderType = ((Environment.OSVersion.Version.Major > 5) || ((Environment.OSVersion.Version.Major == 5) && (Environment.OSVersion.Version.Minor >= 1))) ? 0x18 : 1;
 
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(parms);
        RSAParameters rsAparams = new RSAParameters();
 
        rsAparams.Modulus = rd.ReadBytes(Helpers.DecodeIntegerSize(rd));
 
        /* Argh, this is a pain. From emperical testing it appears to be that
           RSAParameters doesn't like byte buffers that have their leading zeros
           removed. The RFC doesn't address this area that I can see, so it's
           hard to say that this is a bug, but it sure would be helpful if it
           allowed that. So, there's some extra code here that knows what the
           sizes of the various components are supposed to be. Using these sizes
           we can ensure the buffer sizes are exactly what the RSAParameters
           expect. Thanks, Microsoft. */
        RSAParameterTraits traits = new RSAParameterTraits(rsAparams.Modulus.Length * 8);
 
        rsAparams.Modulus = Helpers.AlignBytes(rsAparams.Modulus, traits.size_Mod);
        rsAparams.Exponent = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_Exp);
        rsAparams.D = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_D);
        rsAparams.P = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_P);
        rsAparams.Q = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_Q);
        rsAparams.DP = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_DP);
        rsAparams.DQ = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_DQ);
        rsAparams.InverseQ = Helpers.AlignBytes(rd.ReadBytes(Helpers.DecodeIntegerSize(rd)), traits.size_InvQ);
 
        rsa.ImportParameters(rsAparams);
        return rsa;
      }
      catch (Exception) {
        Debug.Assert(false);
        return null;
      }
      finally {
        rd.Close();
      }
    }
  }
 
  // From https://github.com/csob/paymentgateway/blob/master/Integration%20Examples/eAPI%20v1.6/dotNET/CsobGTWDemo/CsobGatewayClientExample/Security/Crypto.cs
  public class Crypto2 {
    public static System.Security.Cryptography.RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey) {
      byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
 
      // --------- Set up stream to decode the asn.1 encoded RSA private key ------
      System.IO.MemoryStream mem = new System.IO.MemoryStream(privkey);
      System.IO.BinaryReader binr = new System.IO.BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
      byte bt = 0;
      ushort twobytes = 0;
      int elems = 0;
      try {
        twobytes = binr.ReadUInt16();
        if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
          binr.ReadByte(); //advance 1 byte
        else if (twobytes == 0x8230)
          binr.ReadInt16(); //advance 2 bytes
        else
          return null;
 
        twobytes = binr.ReadUInt16();
        if (twobytes != 0x0102) //version number
          return null;
        bt = binr.ReadByte();
        if (bt != 0x00)
          return null;
 
        //------ all private key components are Integer sequences ----
        elems = GetIntegerSize(binr);
        MODULUS = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        E = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        D = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        P = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        Q = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        DP = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        DQ = binr.ReadBytes(elems);
 
        elems = GetIntegerSize(binr);
        IQ = binr.ReadBytes(elems);
 
        // System.Security.Cryptography.RSA.Create();
 
        // ------- create RSACryptoServiceProvider instance and initialize with public key -----
        System.Security.Cryptography.RSACryptoServiceProvider RSA = new System.Security.Cryptography.RSACryptoServiceProvider();
        System.Security.Cryptography.RSAParameters RSAparams = new System.Security.Cryptography.RSAParameters();
        RSAparams.Modulus = MODULUS;
        RSAparams.Exponent = E;
        RSAparams.D = D;
        RSAparams.P = P;
        RSAparams.Q = Q;
        RSAparams.DP = DP;
        RSAparams.DQ = DQ;
        RSAparams.InverseQ = IQ;
        RSA.ImportParameters(RSAparams);
        return RSA;
      }
      catch (Exception) {
        return null;
      }
      finally {
        binr.Close();
      }
    }
 
    private static int GetIntegerSize(System.IO.BinaryReader binr) {
      byte bt = 0;
      byte lowbyte = 0x00;
      byte highbyte = 0x00;
      int count = 0;
      bt = binr.ReadByte();
      if (bt != 0x02) //expect integer
        return 0;
      bt = binr.ReadByte();
 
      if (bt == 0x81)
        count = binr.ReadByte(); // data size in next byte
      else if (bt == 0x82) {
        highbyte = binr.ReadByte(); // data size in next 2 bytes
        lowbyte = binr.ReadByte();
        byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
        count = BitConverter.ToInt32(modint, 0);
      }
      else {
        count = bt; // we already have the data size
      }
 
      while (binr.ReadByte() == 0x00) {
        //remove high order zeros in data
        count -= 1;
      }
      binr.BaseStream.Seek(-1, System.IO.SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
      return count;
    }
 
    private static bool CompareBytearrays(byte[] a, byte[] b) {
      if (a.Length != b.Length)
        return false;
      int i = 0;
      foreach (byte c in a) {
        if (c != b[i])
          return false;
        i++;
      }
      return true;
    }
  }
 
  public class Certificate {
    public Certificate() { }
 
    public Certificate(string cert, string key, string password) {
      this.PublicCertificate = cert;
      this.PrivateKey = key;
      this.Password = password;
    }
 
    #region Fields
    private string _publicCertificate;
    private string _privateKey;
    private string _password;
    #endregion
 
    #region Properties
    public string PublicCertificate {
      get { return _publicCertificate; }
      set { _publicCertificate = value; }
    }
 
    public string PrivateKey {
      get { return _privateKey; }
      set { _privateKey = value; }
    }
 
    public string Password {
      get { return _password; }
      set { _password = value; }
    }
    #endregion
 
    public X509Certificate2 GetCertificateFromPEMstring(bool certOnly) {
      if (certOnly)
        return GetCertificateFromPEMstring(this.PublicCertificate);
      else
        return GetCertificateFromPEMstring(this.PublicCertificate, this.PrivateKey, this.Password);
    }
 
    public static X509Certificate2 GetCertificateFromPEMstring(string publicCert) {
      return new X509Certificate2(Encoding.UTF8.GetBytes(publicCert));
    }
 
    public static X509Certificate2 GetCertificateFromPEMstring(string publicCert, string privateKey, string password) {
      byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
      byte[] keyBuffer = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);
 
      X509Certificate2 certificate = new X509Certificate2(certBuffer, password);
 
      RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
      certificate.PrivateKey = prov;
 
      return certificate;
    }
 
  }
 
}