none
How to use a X509 certificate (not a PFX!) to verify a signature?

    Question

  • I have signed a file with my private key and want to verify the file in my app using the public X509 certificate.

    -----BEGIN CERTIFICATE-----
    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
    -----END CERTIFICATE----- 


    Thats what I do:

    certBuffer = CryptographicBuffer.convertStringToBinary(cert, Windows.Security.Cryptography.BinaryStringEncoding.utf8);
    keyProvider = AsymmetricKeyAlgorithmProvider.openAlgorithm(AsymmetricAlgorithmNames.rsaSignPkcs1Sha1);

    key = keyProvider.importPublicKey(certBuffer, CryptographicPublicKeyBlobType.x509SubjectPublicKeyInfo);

    the last call fails with "ASN1 bad tag value met." Which is surprising, as google reveals that means the key provider cannot find the private key in the cert, which is INTENTIONAL in this case cause I do not want to deploy the private key in the cert (then it would be a PFX anyway). I just want to use asymetric file signing. 

    Is that possible or not?

    Friday, March 08, 2013 4:03 PM

Answers

  • Hi Phil,

    I wrote a general solution to the problem.  The code finds the X509PublicKey given a base64 encoded certificate.

    public CryptographicKey GetCryptographicPublicKeyFromCert(string strCert)
    {
        int length;
        CryptographicKey CryptKey = null;

        byte[] bCert = Convert.FromBase64String(strCert);

        // Assume Cert contains RSA public key
        // Find matching OID in the certificate and return public key
        byte[] rsaOID = EncodeOID("1.2.840.113549.1.1.1");
        int index = FindX509PubKeyIndex(bCert, rsaOID, out length);

        // Found X509PublicKey in certificate so copy it.
        if (index > -1)
        {
            byte[] X509PublicKey = new byte[length];
            Array.Copy(bCert, index, X509PublicKey, 0, length);

            AsymmetricKeyAlgorithmProvider AlgProvider = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);
            CryptKey = AlgProvider.ImportPublicKey(CryptographicBuffer.CreateFromByteArray(X509PublicKey));
        }

        return CryptKey;
    }

    static int FindX509PubKeyIndex(byte[] Reference, byte[] value, out int length)
    {
        int index = -1;
        bool found;
        length = 0;

        for (int n = 0; n < Reference.Length; n++)
        {
            if ((Reference[n] == value[0]) && (n + value.Length < Reference.Length))
            {
                index = n;
                found = true;

                for (int m = 1; m < value.Length; m++) {
                    if (Reference[n+m] != value[m]) {
                        found = false;
                        break;
                    }
                }

                if (found) break;
                else index = -1;
            }
        }
               
        if (index > -1)
        {
            // Find outer Sequence
            while (index > 0 && Reference[index] != 0x30) index--;
            index--;
            while (index > 0 && Reference[index] != 0x30) index--;               
        }

        if (index > -1)
        {
            // Find the length of encoded Public Key
            if ((Reference[index + 1] & 0x80) == 0x80)
            {
                int numBytes = Reference[index + 1] & 0x7F;
                for (int m = 0; m < numBytes; m++)
                {
                    length += (Reference[index + 2 + m] << ((numBytes - 1 - m) * 8));
                }

                length += 4;
            }
            else
            {
                length = Reference[index + 1] + 2;
            }
        }

        return index;
    }

    static public byte[] EncodeOID(string szOID)
    {
        int[] OIDNums;
        byte[] pbEncodedTemp = new byte[64];
        byte[] pbEncoded = null;
        int n, index, num, count;

        OIDNums = ParseOID(szOID);

        pbEncodedTemp[0] = 6;

        pbEncodedTemp[2] = Convert.ToByte(OIDNums[0] * 40 + OIDNums[1]);
        count = 1;

        for (n = 2, index = 3; n < OIDNums.Length; n++)
        {
            num = OIDNums[n];

            if (num >= 16384)
            {
                pbEncodedTemp[index++] = Convert.ToByte(num / 16384 | 0x80);
                num = num % 16384;

                count++;
            }

            if (num >= 128)
            {
                pbEncodedTemp[index++] = Convert.ToByte(num / 128 | 0x80);
                num = num % 128;

                count++;
            }


            pbEncodedTemp[index++] = Convert.ToByte(num);
            count++;
        }

        pbEncodedTemp[1] = Convert.ToByte(count);

        pbEncoded = new byte[count + 2];
        Array.Copy(pbEncodedTemp, 0, pbEncoded, 0, count + 2);

        return pbEncoded;
    }

    static public int[] ParseOID(string szOID)
    {
        int nlast, n = 0;
        bool fFinished = false;
        int count = 0;
        int[] dwNums = null;

        do
        {
            nlast = n;
            n = szOID.IndexOf(".", nlast);
            if (n == -1) fFinished = true;
            count++;
            n++;
        } while (fFinished == false);

        dwNums = new int[count];

        count = 0;
        fFinished = false;

        do
        {
            nlast = n;
            n = szOID.IndexOf(".", nlast);
            if (n != -1)
            {
                dwNums[count] = Convert.ToInt32(szOID.Substring(nlast, n - nlast), 10);
            }
            else
            {
                fFinished = true;
                dwNums[count] = Convert.ToInt32(szOID.Substring(nlast, szOID.Length - nlast), 10);
            }

            n++;
            count++;
        } while (fFinished == false);

        return dwNums;
    }


    Carlos Lopez - Microsoft Escalation Engineer

    Monday, March 11, 2013 7:34 PM
    Moderator

All replies

  • The problem is happening due to the algorithm mismatch rather than a problem with the function. The Certificate you uses has the algorithm as sha-1, but you are using AsymmetricAlgorithmNames.rsaSignPkcs1Sha1. If you look at the sample mentioned here: http://msdn.microsoft.com/en-us/library/windows/apps/windows.security.cryptography.core.asymmetrickeyalgorithmprovider.aspx you will notice that the function SampleAsymmetricEncryptSessionKey uses the same algorithm to create the keypair and notice that the function only uses the public key, and does not use the private key at all.

    If you used a certificate with the supported algorithm, then you shouldn't have a problem.

    Thanks,

    Prashant.

    Saturday, March 09, 2013 12:39 AM
    Moderator
  • Thanks, Prashant. I am using the public key now and it works. But which algo should I use for the cert?
    Monday, March 11, 2013 10:11 AM
  • Hi Phil,

    I wrote a general solution to the problem.  The code finds the X509PublicKey given a base64 encoded certificate.

    public CryptographicKey GetCryptographicPublicKeyFromCert(string strCert)
    {
        int length;
        CryptographicKey CryptKey = null;

        byte[] bCert = Convert.FromBase64String(strCert);

        // Assume Cert contains RSA public key
        // Find matching OID in the certificate and return public key
        byte[] rsaOID = EncodeOID("1.2.840.113549.1.1.1");
        int index = FindX509PubKeyIndex(bCert, rsaOID, out length);

        // Found X509PublicKey in certificate so copy it.
        if (index > -1)
        {
            byte[] X509PublicKey = new byte[length];
            Array.Copy(bCert, index, X509PublicKey, 0, length);

            AsymmetricKeyAlgorithmProvider AlgProvider = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaPkcs1);
            CryptKey = AlgProvider.ImportPublicKey(CryptographicBuffer.CreateFromByteArray(X509PublicKey));
        }

        return CryptKey;
    }

    static int FindX509PubKeyIndex(byte[] Reference, byte[] value, out int length)
    {
        int index = -1;
        bool found;
        length = 0;

        for (int n = 0; n < Reference.Length; n++)
        {
            if ((Reference[n] == value[0]) && (n + value.Length < Reference.Length))
            {
                index = n;
                found = true;

                for (int m = 1; m < value.Length; m++) {
                    if (Reference[n+m] != value[m]) {
                        found = false;
                        break;
                    }
                }

                if (found) break;
                else index = -1;
            }
        }
               
        if (index > -1)
        {
            // Find outer Sequence
            while (index > 0 && Reference[index] != 0x30) index--;
            index--;
            while (index > 0 && Reference[index] != 0x30) index--;               
        }

        if (index > -1)
        {
            // Find the length of encoded Public Key
            if ((Reference[index + 1] & 0x80) == 0x80)
            {
                int numBytes = Reference[index + 1] & 0x7F;
                for (int m = 0; m < numBytes; m++)
                {
                    length += (Reference[index + 2 + m] << ((numBytes - 1 - m) * 8));
                }

                length += 4;
            }
            else
            {
                length = Reference[index + 1] + 2;
            }
        }

        return index;
    }

    static public byte[] EncodeOID(string szOID)
    {
        int[] OIDNums;
        byte[] pbEncodedTemp = new byte[64];
        byte[] pbEncoded = null;
        int n, index, num, count;

        OIDNums = ParseOID(szOID);

        pbEncodedTemp[0] = 6;

        pbEncodedTemp[2] = Convert.ToByte(OIDNums[0] * 40 + OIDNums[1]);
        count = 1;

        for (n = 2, index = 3; n < OIDNums.Length; n++)
        {
            num = OIDNums[n];

            if (num >= 16384)
            {
                pbEncodedTemp[index++] = Convert.ToByte(num / 16384 | 0x80);
                num = num % 16384;

                count++;
            }

            if (num >= 128)
            {
                pbEncodedTemp[index++] = Convert.ToByte(num / 128 | 0x80);
                num = num % 128;

                count++;
            }


            pbEncodedTemp[index++] = Convert.ToByte(num);
            count++;
        }

        pbEncodedTemp[1] = Convert.ToByte(count);

        pbEncoded = new byte[count + 2];
        Array.Copy(pbEncodedTemp, 0, pbEncoded, 0, count + 2);

        return pbEncoded;
    }

    static public int[] ParseOID(string szOID)
    {
        int nlast, n = 0;
        bool fFinished = false;
        int count = 0;
        int[] dwNums = null;

        do
        {
            nlast = n;
            n = szOID.IndexOf(".", nlast);
            if (n == -1) fFinished = true;
            count++;
            n++;
        } while (fFinished == false);

        dwNums = new int[count];

        count = 0;
        fFinished = false;

        do
        {
            nlast = n;
            n = szOID.IndexOf(".", nlast);
            if (n != -1)
            {
                dwNums[count] = Convert.ToInt32(szOID.Substring(nlast, n - nlast), 10);
            }
            else
            {
                fFinished = true;
                dwNums[count] = Convert.ToInt32(szOID.Substring(nlast, szOID.Length - nlast), 10);
            }

            n++;
            count++;
        } while (fFinished == false);

        return dwNums;
    }


    Carlos Lopez - Microsoft Escalation Engineer

    Monday, March 11, 2013 7:34 PM
    Moderator
  • Wow, thanks Carlos. So it seems there is no built in way in WinRT to do that? Thanks for your efforts. Will test it.
    Tuesday, March 12, 2013 10:52 AM
  • Thanks Carlos for the awesome code!

    I just find that if we do not have to directly parse the public key in windows store app, we can pre-extract the public key through the following standard .NET code (run in console or winform):

    var store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadOnly); var certs = store.Certificates.Find(X509FindType.FindBySubjectName, txtSubjectName.Text, false); var cert = certs[0]; var pubKeyStr = Convert.ToBase64String(cert.GetPublicKey());


    Then, we can use the key info in windows store app code as below:

    // Here is the public key obtained outside the windows store app var pubKeyStr = "MIGJAoGBAN34SRLiTkVvaUXXhLir3eGmEzV8M8x1Qf+WX6U67ML6tyzWN3oaMya94C1/G5VaG4fut9LpR/047rjuCJFZ3fItgOZXJJqzP6cX4lqVLj954IBQAJMrzzZrGhmBLuPjx9DcISYG+v0DlAj9gstxPPGFwjG2yJK9ADG/hIsi84OZAgMBAAE="; var pubKeyBytes = cryptoNS.CryptographicBuffer.decodeFromBase64String(pubKeyStr); var alg = cryptoNS.Core.AsymmetricKeyAlgorithmProvider.openAlgorithm(cryptoNS.Core.AsymmetricAlgorithmNames.rsaPkcs1); var pubKey = alg.importPublicKey(pubKeyBytes, cryptoNS.Core.CryptographicPublicKeyBlobType.pkcs1RsaPublicKey); var dataToEncrypt = document.getElementById("txtDataToSecure").value; var dataBytes = cryptoNS.CryptographicBuffer.convertStringToBinary(dataToEncrypt, cryptoNS.BinaryStringEncoding.utf8); var encryptedBytes = cryptoNS.Core.CryptographicEngine.encrypt(pubKey, dataBytes, null); var encryptedData = cryptoNS.CryptographicBuffer.encodeToBase64String(encryptedBytes); var msg = "Encrypted data:" + encryptedData; console.log(msg);

    I've also summarized both means in a blog entry in case someone else might need it.

    http://blogs.msdn.com/b/stcheng/archive/2013/03/12/windows-store-app-how-to-perform-rsa-data-encryption-with-x509-certificate-based-key-in-windows-store-application.aspx


    Please remember to mark the replies as answers if they help and unmark them if they provide no help.



    Tuesday, March 12, 2013 2:12 PM
    Moderator
  • Steve and Phil,

    With my solution you don't have to pre-extract the public key.  You can extract the key on the fly from a Windows store application. 
    Hopefully, the .NET X509Certificates classes will be made available from WinRT in future releases.

    Thanks


    Carlos Lopez - Microsoft Escalation Engineer

    Tuesday, March 12, 2013 8:06 PM
    Moderator