I have successfully modified this code to sign and validate the signature on an XML file. But I am having trouble then validating the signature on a machine that doesn't have the cert installed. I found this Microsoft reference for doing it in C#, but that seems to also require the Cert be installed. Or is the "Key Container" just an executable signed with the same Cert? Can someone point me at a resource for this using PowerShell rather than C#, since my C# skills are WEAK! It seems like validating a signature on XML without having the cert installed should be possible. Fingers crossed.
-
1Please update your question to include exactly what you have tried so far along with the specific issues you are getting? Using links for this purpose is bad as they are rendered useless when the link expires :) – I.T Delinquent Jul 11 '19 at 09:55
-
The basis for public key signature verification is... well, the _public key_ part of whatever key was used to sign something - and certificates are a super common way of authenticating and distributing public keys! So you either need to make the certificate (sans the private key) available publicly, or export and ship the public key material alongside the signed document – Mathias R. Jessen Jul 11 '19 at 09:56
-
Interesting. I assume when using PowerShell code signing Windows is just extracting the public key from the signed PS1 automatically, and with XML I need to do this myself? I'll look into using Get-AuthenticodeSignature since in my case I do have a signed executable to reference. I guess the issue really is that the certificate is bundled into the executable, while XML is not? – Gordon Jul 11 '19 at 10:06
1 Answers
I am having trouble then validating the signature on a machine that doesn't have the cert installed.
That makes sense!
The basis for public key signature verification is... well, the public key part of whatever key was used to sign something - and certificates are a super common way of authenticating and distributing public keys!
So you either need to make the certificate (sans the private key) available publicly, or export and ship the public key material alongside the signed document.
We can update the Sign-XML
routine to copy the certificate itself into the signature blob in the xml document like this:
function Sign-XML {
param(
[xml]$xml,
[System.Security.Cryptography.X509Certificates.X509Certificate2]$certificate
)
[System.Security.Cryptography.xml.SignedXml]$signedXml = $NULL
$signedXml = New-Object System.Security.Cryptography.Xml.SignedXml -ArgumentList $xml
# Grab the PrivateKey from the cert - fail if not present
if(-not $certificate.HasPrivateKey){
throw 'Private Key not passed to Sign-XML, cannot sign document'
return
}
$signedXml.SigningKey = $certificate.PrivateKey
# Add a KeyInfo blob to the SignedXml element
# KeyInfo blob will hold the certificate and therefore the public key
$keyInfo = New-Object System.Security.Cryptography.Xml.KeyInfo
$keyInfo.AddClause((New-Object System.Security.Cryptography.Xml.KeyInfoX509Data -ArgumentList $certificate))
$signedXml.KeyInfo = $keyInfo
$Reference = New-Object System.Security.Cryptography.Xml.Reference
$Reference.Uri = ""
$env = New-Object System.Security.Cryptography.Xml.XmlDsigEnvelopedSignatureTransform
$Reference.AddTransform($env);
$signedXml.AddReference($Reference)
$signedXml.ComputeSignature()
[System.Xml.XmlElement]$xmlSignature = $signedXml.GetXml()
#Add signature to end of xml file
[void]$xml.DocumentElement.AppendChild($xml.ImportNode($xmlSignature, $true))
if ($xml.FirstChild -is [system.xml.XmlDeclaration]) {
[void]$xml.RemoveChild($xml.FirstChild);
}
$xml
}
2 things to notice here:
- Second parameter now takes a
X509Certificate2
object instead of a raw key container - We add a
KeyInfoX509Data
clause to the signature info in the document containing the certificate
Now that the certificate is part of the signed document, a recipient can easily verify the signature and public key programmatically:
function Verify-XmlSignature {
Param (
[xml]$checkxml,
[switch]$Force
)
# Grab signing certificate from document
$rawCertBase64 = $signed.DocumentElement.Signature.KeyInfo.X509Data.X509Certificate
if(-not $rawCertBase64){
throw 'Unable to locate signing certificate in signed document'
return
}
$rawCert = [convert]::FromBase64String($rawCertBase64)
$signingCertificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(,$rawCert)
[System.Security.Cryptography.Xml.SignedXml]$signedXml = New-Object System.Security.Cryptography.Xml.SignedXml -ArgumentList $checkxml
$XmlNodeList = $checkxml.GetElementsByTagName("Signature")
$signedXml.LoadXml([System.Xml.XmlElement] ($XmlNodeList[0]))
return $signedXml.CheckSignature($signingCertificate, $Force)
}
Passing $Force
to the second argument of CheckSignature()
means that verification of trust in the certificate itself won't influence the outcome of the verification routine if the user specifies -Force

- 157,619
- 12
- 148
- 206
-
Thanks for this. It's going to be exactly what I need. I am curious, in your code you reference 'system.security.cryptography.X509Certificates.X509Certificate2'. What is the difference between that and without the terminal 2? I am seeing it work either way, which seems odd. – Gordon Jul 11 '19 at 20:09
-
1@Gordon Check out [this answer](https://stackoverflow.com/questions/1182612/what-is-the-difference-between-x509certificate2-and-x509certificate-in-net) - the "2" version simply extends the first one with a larger API surface (including the `Verify()` method and `HasPrivateKey` and `PrivateKey` properties needed for a scenario like this one). Check the docs or the output from `[System.Security.Cryptography.X509Certificates.X509Certificate2].GetMembers()|Select Name,DeclaringType` to see how it extends the first one – Mathias R. Jessen Jul 11 '19 at 20:14
-
Interesting. At first I thought I had screwed up the copy/paste, so I pulled out the 2 and it still works. Then I came back here and I see that you have the 2. More to read up on tomorrow I think. FWIW, X509Certificate2 also seems a tiny bit faster, which makes sense if it's later and perhaps more optimized code. – Gordon Jul 11 '19 at 20:16