Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bouncy Castle Sign and Verify SHA256 Certificate With C#

I have been through a large number of examples of how people use Bouncy Castle to dynamically generate RSA Key Pairs and then sign and verify all within one block of code. Those answers were great and really helped me ramp up quickly!

That said, I need to create a client that can pass through WCF JSON calls a public key to be used later for signature verification and I cannot seem to get this to work. First here is the code I use to generate the Certificate:

    public System.Security.Cryptography.X509Certificates.X509Certificate2 LoadCertificate()
    {
        System.Security.Cryptography.X509Certificates.X509Certificate2 returnX509 = null;
        //X509Certificate returnCert = null;

        try
        {
            returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword);
            //returnCert = DotNetUtilities.FromX509Certificate(returnX509);
        }
        catch
        {
            Console.WriteLine("Failed to obtain cert - trying to make one now.");

            try
            {
                Guid nameGuid = Guid.NewGuid();
                AsymmetricCipherKeyPair kp;
                var x509 = GenerateCertificate("CN=Server-CertA-" + nameGuid.ToString(), out kp);
                SaveCertificateToFile(x509, kp, CertificateName, CertificateAlias, CertificatePassword);
                returnX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(CertificateName, CertificatePassword);
                //returnCert = DotNetUtilities.FromX509Certificate(returnX509);
                Console.WriteLine("Successfully wrote and retrieved cert!");
            }
            catch (Exception exc)
            {
                Console.WriteLine("Failed to create cert - exception was: " + exc.ToString());
            }
        }

        return returnX509;
    }

With that completed, I then use the following code to sign a message:

    public string SignData(string Message, string PrivateKey)
    {
        try
        {
            byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message);
            byte[] privateKey = UnicodeEncoding.ASCII.GetBytes(PrivateKey);

            string curveName = "P-521";
            X9ECParameters ecP = NistNamedCurves.GetByName(curveName);
            ECDomainParameters ecSpec = new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed());

            ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
            BigInteger biPrivateKey = new BigInteger(privateKey);
            ECPrivateKeyParameters keyParameters = new ECPrivateKeyParameters(biPrivateKey, ecSpec);

            signer.Init(true, keyParameters);
            signer.BlockUpdate(orgBytes, 0, orgBytes.Length);

            byte[] data = signer.GenerateSignature();
            //Base64 Encode
            byte[] encodedBytes;
            using (MemoryStream encStream = new MemoryStream())
            {
                base64.Encode(orgBytes, 0, orgBytes.Length, encStream);
                encodedBytes = encStream.ToArray();
            }

            if (encodedBytes.Length > 0)
                return UnicodeEncoding.ASCII.GetString(encodedBytes);
            else
                return "";
        }
        catch (Exception exc)
        {
            Console.WriteLine("Signing Failed: " + exc.ToString());
            return "";
        }
    }

And, finally, my attempt to verify:

    public bool VerifySignature(string PublicKey, string Signature, string Message)
    {
        try
        {
            AsymmetricKeyParameter pubKey = new AsymmetricKeyParameter(false);

            var publicKey = PublicKeyFactory.CreateKey(Convert.FromBase64String(PublicKey));
            ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
            byte[] orgBytes = UnicodeEncoding.ASCII.GetBytes(Message);

            signer.Init(false, publicKey);
            signer.BlockUpdate(orgBytes, 0, orgBytes.Length);

            //Base64 Decode
            byte[] encodeBytes = UnicodeEncoding.ASCII.GetBytes(Signature);
            byte[] decodeBytes;
            using (MemoryStream decStream = new MemoryStream())
            {
                base64.Decode(encodeBytes, 0, encodeBytes.Length, decStream);
                decodeBytes = decStream.ToArray();
            }

            return signer.VerifySignature(decodeBytes);
        }
        catch (Exception exc)
        {
            Console.WriteLine("Verification failed with the error: " + exc.ToString());
            return false;
        } 
    }

Here is my test app:

        Console.WriteLine("Attempting to load cert...");
        System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();
        if (thisCert != null)
        {
            Console.WriteLine(thisCert.IssuerName.Name);
            Console.WriteLine("Signing the text - Mary had a nuclear bomb");
            string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true));

            Console.WriteLine("Signature: " + signature);

            Console.WriteLine("Verifying Signature");

            if (VerifySignature(thisCert.PublicKey.Key.ToXmlString(false), signature, "Mary had a nuclear bomb."))
                Console.WriteLine("Valid Signature!");
            else
                Console.WriteLine("Signature NOT valid!");

        }

When I attempt to run the test app, I am getting the error "Key not valid for use in specified state." on the line:

    string signature = SignData("Mary had a nuclear bomb", thisCert.PublicKey.Key.ToXmlString(true));

I tried replacing the "PublicKey.Key" with "PrivateKey" but that did not make a difference. I also tried using the BounceyCastle X509Certificate but I could not figure out how to extract the keys. Any ideas?

Thank you!

UPDATE: I did figure out how to sign and verify with just .NET but that is not really useful to me as I need to be cross platform and, in fact, our main client app is written in Java. Does anybody know the Bouncy Castle equivalent to the following code?

    public string SignDataAsXml(string Message, X509Certificate2 ThisCert)
    {
        XmlDocument doc = new XmlDocument();
        doc.PreserveWhitespace = false;
        doc.LoadXml("<core>" + Message + "</core>");

        // Create a SignedXml object.
        SignedXml signedXml = new SignedXml(doc);

        // Add the key to the SignedXml document. 
        signedXml.SigningKey = ThisCert.PrivateKey;

        // Create a reference to be signed.
        Reference reference = new Reference();
        reference.Uri = "";

        // Add an enveloped transformation to the reference.
        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
        reference.AddTransform(env);

        // Add the reference to the SignedXml object.
        signedXml.AddReference(reference);

        // Create a new KeyInfo object.
        KeyInfo keyInfo = new KeyInfo();

        // Load the certificate into a KeyInfoX509Data object 
        // and add it to the KeyInfo object.
        keyInfo.AddClause(new KeyInfoX509Data(ThisCert));

        // Add the KeyInfo object to the SignedXml object.
        signedXml.KeyInfo = keyInfo;

        // Compute the signature.
        signedXml.ComputeSignature();

        // Get the XML representation of the signature and save 
        // it to an XmlElement object.
        XmlElement xmlDigitalSignature = signedXml.GetXml();

        // Append the element to the XML document.
        doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));


        if (doc.FirstChild is XmlDeclaration)
        {
            doc.RemoveChild(doc.FirstChild);
        }

        using (var stringWriter = new StringWriter())
        {
            using (var xmlTextWriter = XmlWriter.Create(stringWriter))
            {
                doc.WriteTo(xmlTextWriter);
                xmlTextWriter.Flush();
                return stringWriter.GetStringBuilder().ToString();
            }
        }
    }

    public bool VerifyXmlSignature(string XmlMessage, X509Certificate2 ThisCert)
    {
        // Create a new XML document.
        XmlDocument xmlDocument = new XmlDocument();

        // Load the passed XML file into the document. 
        xmlDocument.LoadXml(XmlMessage);

        // Create a new SignedXml object and pass it the XML document class.
        SignedXml signedXml = new SignedXml(xmlDocument);

        // Find the "Signature" node and create a new XmlNodeList object.
        XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");

        // Load the signature node.
        signedXml.LoadXml((XmlElement)nodeList[0]);

        // Check the signature and return the result. 
        return signedXml.CheckSignature(ThisCert, true);
    }
like image 954
SEMDeveloper Avatar asked Apr 02 '15 23:04

SEMDeveloper


1 Answers

Bouncy Castle doesn't support XML formats at all. Unless your use-case strictly requires it, you'll find it much easier going just to use Base64 encodings, with certificates (X.509) and private keys (PKCS#8) stored in PEM format. These are all string formats, so should be usable with JSON directly.

There are other problems in the code samples: signing should use the private key, signatures shouldn't be treated as ASCII strings, possibly your messages are actually UTF8. I would expect the inner sign/verify routines to perhaps look like this:

    public string SignData(string msg, ECPrivateKeyParameters privKey)
    {
        try
        {
            byte[] msgBytes = Encoding.UTF8.GetBytes(msg);

            ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
            signer.Init(true, privKey);
            signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
            byte[] sigBytes = signer.GenerateSignature();

            return Convert.ToBase64String(sigBytes);
        }
        catch (Exception exc)
        {
            Console.WriteLine("Signing Failed: " + exc.ToString());
            return null;
        }
    }

    public bool VerifySignature(ECPublicKeyParameters pubKey, string signature, string msg)
    {
        try
        {
            byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
            byte[] sigBytes = Convert.FromBase64String(signature);

            ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
            signer.Init(false, pubKey);
            signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
            return signer.VerifySignature(sigBytes);
        }
        catch (Exception exc)
        {
            Console.WriteLine("Verification failed with the error: " + exc.ToString());
            return false;
        }
    }

A further issue is that I think .NET didn't get ECDSA support until .NET 3.5, in any case there's no ECDsa class in .NET 1.1 (which is BC's target for the upcoming 1.8 release - we will be "modernising" after that), so DotNetUtilities doesn't have support for ECDSA. However, we can export to PKCS#12 and import to BC. An example program:

    public void Program()
    {
        Console.WriteLine("Attempting to load cert...");
        System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();

        Console.WriteLine(thisCert.IssuerName.Name);
        Console.WriteLine("Signing the text - Mary had a nuclear bomb");

        byte[] pkcs12Bytes = thisCert.Export(X509ContentType.Pkcs12, "dummy");
        Pkcs12Store pkcs12 = new Pkcs12StoreBuilder().Build();
        pkcs12.Load(new MemoryStream(pkcs12Bytes, false), "dummy".ToCharArray());

        ECPrivateKeyParameters privKey = null;
        foreach (string alias in pkcs12.Aliases)
        {
            if (pkcs12.IsKeyEntry(alias))
            {
                privKey = (ECPrivateKeyParameters)pkcs12.GetKey(alias).Key;
                break;
            }
        }

        string signature = SignData("Mary had a nuclear bomb", privKey);

        Console.WriteLine("Signature: " + signature);

        Console.WriteLine("Verifying Signature");

        var bcCert = DotNetUtilities.FromX509Certificate(thisCert);
        if (VerifySignature((ECPublicKeyParameters)bcCert.GetPublicKey(), signature, "Mary had a nuclear bomb."))
            Console.WriteLine("Valid Signature!");
        else
            Console.WriteLine("Signature NOT valid!");
    }

I haven't really tested any of the above code, but it should give you something to go on. Note that BC has key and certificate generators too, so you could choose to use BC for everything (except XML!), and export/import to/from .NET land only where necessary.

like image 157
Peter Dettman Avatar answered Oct 11 '22 21:10

Peter Dettman