Digitally Signing XML in CRM 2016

Purpose

This article talks about using Digitally Signing XML, which will help verify the integrity of an XML document inside of CRM 2016 on premise. This is useful for situations where you want to pass information to a third party and verify that the information has not been altered. To verify the integrity of the XML, we will need two keys that will act as our encryption/decryption keys. In our implementation, we will not always be distributing our XML document to areas with a reliable internet source, therefore it must be able to decrypted locally. To accomplish this, we will be utilizing Public/Private key cryptography.

For those who don’t know what Public/Private key cryptography is, or what it accomplishes, I have included a definition from Wikipedia.

Public key cryptography, or asymmetric cryptography, accomplishes two functions: authentication, which is when the public key is used to verify that a holder of the paired private key sent the message, and encryption, whereby only the holder of the paired private key can decrypt the message encrypted with the public key.” – Wikipedia

Certificates

For testing purposes, we will be generating a self-signed certificate that will hold both our Public and our Private keys. The following sections will detail how to create/import a local certificate and how to utilize it inside of your code.

Creating the Certificate

After some searching I stumbled upon HowTo: Create self-signed certificates with PowerShell 4.0 which proved to be quite useful. If you follow the steps provided you will more than likely get the results you desire. Alas, if you miss a step or forget a parameter it can be quite frustrating to figure out why it didn’t work. To alleviate this issue, I wrote a little PS script that will do most of the heavy lifting with minimal input. 

#inputs from user
$storeType = Read-Host -Prompt “Which store do you want to install the cert to? CurrentUser or LocalMachine”
$certName = Read-Host -Prompt “Certificate Name”
$certpwd = Read-Host -Prompt “Certificate Password”
$filePath = Read-Host -Prompt “Where to export Certificate (C:\[certificateName])”
#convert password to secure string
$certPassword = ConvertTo-SecureString -String $certpwd -Force -AsPlainText
#create the cert
New-SelfSignedCertificate -DnsName $certName -CertStoreLocation Cert:\$storeType\My
#get the thumbprint of the certificate
$thumbprint=(dir cert:\$storeType\My -recurse | where {$_.Subject -match “CN=” + $certName} | Select-Object -Last 1).thumbprint
$msg = “Generated ” + $certName
Write-Host $msg
if($thumbprint.Length -gt 0)
{
  #found the thumbprint
  Write-Host “Found thumbprint”
  $SelfSignedCert = (Get-ChildItem -Path cert:\$storeType\My\$thumbprint)
  $certPath = $filePath + “.cer”
  Write-Host “Exporting Certification”
  Export-Certificate -Cert $SelfSignedCert -FilePath $certPath -Type CERT
  $prvPath = $filePath + “PrivateKey.pfx”
  Write-Host “Exporting Certification PFX”
  Export-PfxCertificate -Cert $SelfSignedCert -FilePath $prvPath -Password $certPassword
  Write-Host “Importing Certification PFX”
  Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\My -FilePath $prvPath -Password $certPassword -Exportable
}

Once you have generated the certificate, you should check to make sure that it got imported into your local store correctly. Navigating to the Microsoft Management Console and looking under Personal > Certificates will display a list of certificates valid for use.

Utilizing the Certificate

After creating your certificate, you are ready to use it inside of your code. In my implementation, I created a class called Certificate that would get the certificate from your local store based on its name. There are other ways to find a certificate, but for my testing purposes, it was good enough. After getting the certificate I could test the signature and verify that it was getting encrypted and decrypted correctly while utilizing public/private keys. Note: the following code will only work with .Net Framework 4.6 or greater.

public class Certificate
    {
        private const string certName = "CertificateName";
        protected static X509Certificate2 cert = GetCertificate();
        protected static X509Certificate2 GetCertificate()
        {
            var store = new X509Store(StoreName.My);
            store.Open(OpenFlags.ReadOnly);
            var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, certName, false);
            store.Close();
            if (certificates.Count < 1)
            {
                throw new InvalidProgramException(
                           string.Format("Cannot locate Certification {0}. Please contact your system administrator.", certName)
                           );
            }
            return new X509Certificate2(certificates[0].Export(X509ContentType.Pfx));
        }
        public static RSA PrivateKey
        {
            get
            {
                return cert.GetRSAPrivateKey();
            }
        }
        public static RSA PublicKey
        {
            get
            {
                return cert.GetRSAPublicKey();
            }
        }
    }

This class has two public properties: PublicKey and PrivateKey. Each of these will return their respective key for consumption inside of your plugin.

Signing/Verifying

After reading the following MSDN article, I created two simple functions that closely mirror those in the article. In my implementation I needed to sign an entire XML document as opposed to an element of an XML document, therefore the parameters required are the whole XML document, if you need to sign only an element of a document, I would reference the MSND article.

Signing the XML will return the signed XML document along with the digital signature inserted as a child element of the main XML document.

private XmlDocument Sign(XmlDocument xmlDoc)
        {
            xmlDoc.PreserveWhitespace = true;
            SignedXml signedXML = new SignedXml(xmlDoc);
            signedXML.SigningKey = Certification.PrivateKey;

            // Get the signature object from the SignedXml object.
            Signature XMLSignature = signedXML.Signature;

            // Create a reference to be signed.  Pass "" to specify that all of the current XML document should 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 object to the Signature object.
            XMLSignature.SignedInfo.AddReference(reference);

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

            //Get the XML representation of the signature and save it to an XmlElement object.
            XmlElement xmlDigitalSignature = signedXML.GetXml();
            xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));

            return xmlDoc;
        }

Verifying the XML will strip out the Signature Node from the main XML document and compare the nodes to verify the XML’s integrity.

private bool Verify(XmlDocument xmlDoc)
        {
            SignedXml signedXML = new SignedXml(xmlDoc);
            XmlElement signatureNode = xmlDoc.GetElementsByTagName("Signature")[0] as XmlElement;
            signedXML.LoadXml(signatureNode);
            return signedXML.CheckSignature(new RSACryptoServiceProvider()); ;
        }

Register the Assembly

The assembly that is checking the XML validity must be registered outside of the sandbox. Computing the signature inside of your plugin will require that the assembly is fully trusted. In an on premise deployment, this would mean registering your assembly outside of the sandbox. In an online deployment, all assemblies have to be registered inside of the sandbox. As far as I am aware, there is no work-around for this.

Verify Functionality

Once your assembly has been registered, it’s time to test the functionality of your plugin. Everyone will have a different functionality and purpose for their plugins, but there are two things that need to be verified to ensure everything is working correctly:

  1. Ensure that the XML is getting correctly signed using the test local Public Key
  2. Ensure that the XML is verified correctly using the test local Private Key

Once you ensure that both of those are working correctly, then you are off to verifying XML integrity!

I hope this has been as educational for you as it was for me.

Good Luck!

 

By | 2016-11-21T10:21:03+00:00 November 14th, 2016|CRM Posts|

About the Author:

Joel joined Sockeye after completing his degree at UAA. He is a skilled Developer who also dabbles in design. He enjoys doing anything outside! Recreational hiking is a passion of his and he enjoys fishing, hunting, and playing and watching soccer.