Generar sello con la e.firma del SAT usando archivos .key .cer

El código que pondré en esta entrada es una forma que encontré para sellar cadenas utilizando la llave privada (archivo .key) que otorga el SAT en México así como un método para verificar la autenticidad del sello.

Lo primero es crear la clase con la que se abre la llave privada.

class KeyFile
    {


        public RSACryptoServiceProvider OpenKeyFile(String filename, string pPassword)
        {
            RSACryptoServiceProvider rsa = null;
            byte[] keyblob = GetFileBytes(filename);
            if (keyblob == null)
                return null;

            rsa = DecodePrivateKeyInfo(keyblob, pPassword);	//PKCS #8 encrypted
            if (rsa != null)
            {
                return rsa;
            }
            return null;
        }


        private static byte[] GetFileBytes(String filename)
        {
            if (!File.Exists(filename))
                return null;
            Stream stream = new FileStream(filename, FileMode.Open);
            int datalen = (int)stream.Length;
            byte[] filebytes = new byte[datalen];
            stream.Seek(0, SeekOrigin.Begin);
            stream.Read(filebytes, 0, datalen);
            stream.Close();
            return filebytes;
        }
        public static 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  ------
            MemoryStream mem = new MemoryStream(privkey);
            BinaryReader binr = new 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);

                Console.WriteLine("showing components ..");

                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                RSAParameters RSAparams = new 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(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, SeekOrigin.Current);
            //last ReadByte wasn't a removed zero, so back up a byte
            return count;
        }

        public static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
        {
            // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
            // this byte[] includes the sequence byte and terminal encoded null 
            byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
            byte[] seq = new byte[15];
            // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
            MemoryStream mem = new MemoryStream(pkcs8);
            int lenstream = (int)mem.Length;
            BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
            byte bt = 0;
            ushort twobytes = 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;


                bt = binr.ReadByte();
                if (bt != 0x02)
                    return null;

                twobytes = binr.ReadUInt16();

                if (twobytes != 0x0001)
                    return null;

                seq = binr.ReadBytes(15);		//read the Sequence OID
                if (!CompareBytearrays(seq, SeqOID))	//make sure Sequence for OID is correct
                    return null;

                bt = binr.ReadByte();
                if (bt != 0x04)	//expect an Octet string 
                    return null;

                bt = binr.ReadByte();		//read next byte, or next 2 bytes is  0x81 or 0x82; otherwise bt is the byte count
                if (bt == 0x81)
                    binr.ReadByte();
                else
                    if (bt == 0x82)
                    binr.ReadUInt16();
                //------ at this stage, the remaining sequence should be the RSA private key

                byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
                RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
                return rsacsp;
            }

            catch (Exception)
            {
                return null;
            }

            finally { binr.Close(); }
        }


        public static RSACryptoServiceProvider
                  DecodePrivateKeyInfo(byte[] encpkcs8, string pPassword)
        {
            // encoded OID sequence for  PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
            // this byte[] includes the sequence byte and terminal encoded null 
            byte[] OIDpkcs5PBES2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D };
            byte[] OIDpkcs5PBKDF2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C };
            byte[] OIDdesEDE3CBC = { 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07 };
            byte[] seqdes = new byte[10];
            byte[] seq = new byte[11];
            byte[] salt;
            byte[] IV;
            byte[] encryptedpkcs8;
            byte[] pkcs8;

            int saltsize, ivsize, encblobsize;
            int iterations;

            // ---------  Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob  ------
            MemoryStream mem = new MemoryStream(encpkcs8);
            int lenstream = (int)mem.Length;
            BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
            byte bt = 0;
            ushort twobytes = 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();	//inner sequence
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();


                seq = binr.ReadBytes(11);		//read the Sequence OID
                if (!CompareBytearrays(seq, OIDpkcs5PBES2))	//is it a OIDpkcs5PBES2 ?
                    return null;

                twobytes = binr.ReadUInt16();	//inner sequence for pswd salt
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();

                twobytes = binr.ReadUInt16();	//inner sequence for pswd salt
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();

                seq = binr.ReadBytes(11);		//read the Sequence OID
                if (!CompareBytearrays(seq, OIDpkcs5PBKDF2))	//is it a OIDpkcs5PBKDF2 ?
                    return null;

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();

                bt = binr.ReadByte();
                if (bt != 0x04)		//expect octet string for salt
                    return null;
                saltsize = binr.ReadByte();
                salt = binr.ReadBytes(saltsize);

                bt = binr.ReadByte();
                if (bt != 0x02) 	//expect an integer for PBKF2 interation count
                    return null;

                int itbytes = binr.ReadByte();	//PBKD2 iterations should fit in 2 bytes.
                if (itbytes == 1)
                    iterations = binr.ReadByte();
                else if (itbytes == 2)
                    iterations = 256 * binr.ReadByte() + binr.ReadByte();
                else
                    return null;

                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130)
                    binr.ReadByte();
                else if (twobytes == 0x8230)
                    binr.ReadInt16();


                seqdes = binr.ReadBytes(10);		//read the Sequence OID
                if (!CompareBytearrays(seqdes, OIDdesEDE3CBC))	//is it a OIDdes-EDE3-CBC ?
                    return null;

                bt = binr.ReadByte();
                if (bt != 0x04)		//expect octet string for IV
                    return null;
                ivsize = binr.ReadByte();	// IV byte size should fit in one byte (24 expected for 3DES)
                IV = binr.ReadBytes(ivsize);

                bt = binr.ReadByte();
                if (bt != 0x04)		// expect octet string for encrypted PKCS8 data
                    return null;


                bt = binr.ReadByte();

                if (bt == 0x81)
                    encblobsize = binr.ReadByte();	// data size in next byte
                else if (bt == 0x82)
                    encblobsize = 256 * binr.ReadByte() + binr.ReadByte();
                else
                    encblobsize = bt;		// we already have the data size


                encryptedpkcs8 = binr.ReadBytes(encblobsize);
                SecureString secpswd = new SecureString();
                foreach (char c in pPassword)
                    secpswd.AppendChar(c);

                pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations);
                if (pkcs8 == null)	// probably a bad pswd entered.
                    return null;

                RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8);
                return rsa;
            }

            catch (Exception)
            {
                return null;
            }

            finally { binr.Close(); }


        }


        public static byte[] DecryptPBDK2(byte[] edata, byte[] salt,
                  byte[] IV, SecureString secpswd, int iterations)
        {
            CryptoStream decrypt = null;

            IntPtr unmanagedPswd = IntPtr.Zero;
            byte[] psbytes = new byte[secpswd.Length];
            unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
            Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
            Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);

            try
            {
                Rfc2898DeriveBytes kd = new Rfc2898DeriveBytes(psbytes, salt, iterations);
                TripleDES decAlg = TripleDES.Create();
                decAlg.Key = kd.GetBytes(24);
                decAlg.IV = IV;
                MemoryStream memstr = new MemoryStream();
                decrypt = new CryptoStream(memstr, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
                decrypt.Write(edata, 0, edata.Length);
                decrypt.Flush();
                decrypt.Close();	// this is REQUIRED.
                byte[] cleartext = memstr.ToArray();
                return cleartext;
            }
            catch (Exception e)
            {
                Console.WriteLine("Problem decrypting: {0}", e.Message);
                return null;
            }
        }


        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;
        }


    }

Lo siguiente es crear la clase con la que se harán los procesos de sellado y verificado


    class Sello
    {
       
        public  byte[] FirmarCadena(string cadenaOriginal, string pKeyFile, string pPassword)
        {


            KeyFile kf = new KeyFile();

            RSACryptoServiceProvider rsa = kf.OpenKeyFile(pKeyFile, pPassword);
            if (rsa != null)
            {
                
                return rsa.SignData(Encoding.UTF8.GetBytes(cadenaOriginal), "SHA256");
                
            }

            return null;

           
        }

        public bool ValidarFirma(string publicCert, byte[] sello, string cadenaOriginal)
        {

            X509Certificate2 _ClavePublica = new X509Certificate2(publicCert);

            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(_ClavePublica.PublicKey.Key.ToXmlString(false));

            return rsa.VerifyData(Encoding.UTF8.GetBytes(cadenaOriginal), "SHA256", sello);

        }
       
    }

Ya por último así hacer uso de las clases:

class Program
    {
        static void Main(string[] args)
        {


            string pathCertificadoPublico = @"C:\fiel\ARCHIVO.cer";
            string pathLlavePrivada = @"C:\fiel\Claveprivada_FIEL.key";

            string cadenaOriginal = "esta es la cadena original";
            Sello sello = new Sello();

            var cadenaFirmada = sello.FirmarCadena(cadenaOriginal, pathLlavePrivada, "miContraseñaSegura");

            Console.WriteLine("cadena original:");
            Console.WriteLine(cadenaOriginal);
            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("cadena firmada:");
            Console.WriteLine(Convert.ToBase64String(cadenaFirmada));
            Console.WriteLine();
            Console.WriteLine();

            var resi = sello.ValidarFirma(pathCertificadoPublico, cadenaFirmada, cadenaOriginal);

            Console.WriteLine("la firma es:");
            Console.WriteLine(resi.ToString());
            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("MOSTRAR DATOS DEL CERTIFICADO PUBLICO");
            Console.WriteLine();
            /* MOSTRAR DATOS DEL CERTIFICADO PUBLICO*/

            string sPathCer = pathCertificadoPublico;
            byte[] yCert = File.ReadAllBytes(sPathCer);
            X509Certificate2 oCSD = new X509Certificate2(yCert);

            /*Vigencia del CSD*/
            string sInicio = oCSD.GetEffectiveDateString();
            string sFinal = oCSD.GetExpirationDateString();
            Console.WriteLine("Fecha de inicio del certificado: " + sInicio);
            Console.WriteLine("Fecha de expiración del certificado: " + sFinal);
            Console.WriteLine();

            /*No. de Serie del Certificado*/
            string sNoSerie = Encoding.ASCII.GetString(oCSD.GetSerialNumber().Reverse().ToArray());
            Console.WriteLine("Número de Serie: " + sNoSerie);
            Console.WriteLine();

            /*Issuer*/
            string sIssuer = oCSD.Issuer;
            Console.WriteLine("Issue: " + sIssuer);
            Console.WriteLine();

            /*El certificado incluye la llave privada?*/
            bool bExisteKey = oCSD.HasPrivateKey;
            Console.WriteLine("Contiene llave privada? - " + bExisteKey.ToString());
            Console.WriteLine();

            /*Subject*/
            string sSubject = oCSD.Subject;
            Console.WriteLine("Subject: " + sSubject);
            Console.WriteLine();

            /*Thubprint*/
            string sThumprint = oCSD.Thumbprint;
            Console.WriteLine("Thumbprint: " + sThumprint);
            Console.WriteLine();

            /*Certificado en base 64*/
            string sCertRaw = Convert.ToBase64String(oCSD.Export(X509ContentType.Cert));
            Console.WriteLine("Certificado: " + sCertRaw);
            Console.WriteLine();

        }
    }

Gran parte del código fue tomado de aquí

Snippets

Así se le llama a la funcionalidad que tiene Visual Studio para ahorrar tiempo al momento de codificar alguna sentencia o bloque de código, el uso es muy sencillo, al escribir algun código aparecen algunas sugerencias, picas el botón TAB dos veces y listo.

Aquí vemos un ejemplo con «for», al darle dos veces TAB veremos que un bloque de código con el bucle for ha sido escrito y esta listo para que asignes las variables.

Una de las novedades es que se pueden escribir snippets personalizadas aunque nunca he sentido la necesidad de hacerlo.

Cambiar URL de redmine a la raíz de servidor

Me encuentro probando la aplicación Redmine en un Windows Server 2012 R2, su instalación fue muy sencilla al puro estilo Windows, es decir, siguiente, siguiente, siguiente, finalizar.  Cuando el proceso termina podemos abrir el navegador y por dirección IP o nombre del servidor podemos accesar a su página de bienvenida.

Sigue leyendo Cambiar URL de redmine a la raíz de servidor

Problema con Oracle VM Manager

Recientemente tuvimos un problema con la consola de administración Oracle VM Manager,  resulta que cuando queríamos firmarnos se quedaba pensando y nada pasaba, evidentemente algo andaba mal por lo que tuve que abordar el tema a la brevedad ya que esta consola es importante para las gestiones diarias.

Sigue leyendo Problema con Oracle VM Manager

Agregando página de Blog a WordPress

Estoy con la iniciativa de poner más actividad en este blog por lo que he actualizado el wordpress y cambiado el tema a uno llamado «Appointment», tiene la particularidad de que pone una página de inicio estática, por lo que conlleva a que la página con la lista de entradas este en otro lugar, para ello y con mi poca experiencia en WordPress me di a la tarea de explorar cuales eran las alternativas para llegar a esta configuración,  Sigue leyendo Agregando página de Blog a WordPress

Curso de Yii-Framework en Código Facilito

Yii

Yii Framework es un poderoso framework escrito en PHP y desde hace algunos años he desarrollado en él algunas aplicaciones para consumo personal, no me ha tocado usarlo para algo mas grande.

Gracias a los amigos de Código Facilito que subieron un curso muy bueno sobre este framework logré enteder su funcionamiento de forma rápida y sencilla por eso recomiendo a todos los que quieran incursionar en este Framework le den un vistazo al curso que esta públicado en youtube o bien entrar aquí para tenerlo en forma de listado cada uno de sus capitulos.

Espero les guste tanto como a mi.

Saludos