How to verify an X509Certificate2 against an X509Certificate2Collection chain

Keith picture Keith · May 25, 2011 · Viewed 8.6k times · Source

I'm writing a SAML 2.0 response parser to handle POST authentication in ASP.Net (in C# and MVC, but that's less relevant).

So I have a .p7b file to validate with and that can be read into a X509Certificate2Collection and a sample assertion - a base 64 encoded SAML response.

Ideally I want to use the built in WSSecurityTokenSerializer, but that fails, so I'm looking for a way that works.

I'm reading the XML directly instead:

// get the base 64 encoded SAML
string samlAssertionRaw = GetFromHttpRequest();

// load a new XML document
var assertion = new XmlDocument { PreserveWhitespace = true };
assertion.LoadXml(samlAssertionRaw);

// use a namespace manager to avoid the worst of xpaths
var ns = new XmlNamespaceManager(assertion.NameTable);
ns.AddNamespace("samlp", @"urn:oasis:names:tc:SAML:2.0:protocol");
ns.AddNamespace("saml", @"urn:oasis:names:tc:SAML:2.0:assertion");
ns.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);

// get the signature XML node
var signNode = assertion.SelectSingleNode(
    "/samlp:Response/saml:Assertion/ds:Signature", ns);

// load the XML signature
var signedXml = new SignedXml(assertion.DocumentElement);
signedXml.LoadXml(signNode as XmlElement);

// get the certificate, basically:
// signedXml.KeyInfo.OfType<KeyInfoX509Data>().First().
//     Certificates.OfType<X509Certificate2>().First()
// but with added checks
var certificate = GetFirstX509Certificate(signedXml);

// check the key and signature match
if (!signedXml.CheckSignature(certificate, true))
{
    throw new SecurityException("Signature check failed.");
}

// go on and read the SAML attributes from the XML doc

That lot works, but all it's doing is checking that the signature and the X509Certificate2 public key in the SAML response match. It doesn't in any way verify who it's from, and I need to do that before accepting the SAML authentication.

There appear to be two ways to check the certificate found in the SAML response - I can do certificate.Verify() or I can do the check with the signature signedXml.CheckSignature(certificate, false).

However both return false.

I think this is because they're being checked against the machine store or possibly online (I'm not sure how to check). I want to check them against the X509Certificate2Collection retrieved from the .p7b file instead - the certificates registered on the machine should be ignored and just the .p7b certificates checked.

There doesn't appear to be any way to pass the X509Certificate2Collection to either the Verify or CheckSignature methods.

Is this the right check to be doing on the SAML response?

Is there any way to use the .p7b certificates the way I want to?

Answer

Jo&#227;o Angelo picture João Angelo · May 31, 2011

Have you tried using a custom X509Chain configured to search an ExtraStore of certificates during the validation process. Something like the following:

// Placeholder for the certificate to validate
var targetCertificate = new X509Certificate2();
// Placeholder for the extra collection of certificates to be used
var certificates = new X509Certificate2Collection();

var chain = new X509Chain();

chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.ExtraStore.AddRange(certificates);

bool isValidCertificate = chain.Build(targetCertificate);

In the example the revocation check is also disabled but if you have online or offline access to the CRL you could enable it.


The ExtraStore should allow to include intermediate certificates that are not in the machine/user store. However, the trusted root certificate may need to be in the machine or user store depending on the one specified in X509Chain because otherwise you'll get an UntrustedRoot fail. If not even the root can be available in a machine or user store you could try to walk up the resulting chain and guarantee that the only error you have is due to an untrusted root and at the same time guaranteeing that the chain root is what you would expect based on the X509Certificate2Collection you have for validation.

Alternatively you could create your own custom X509CertificateValidator to validate a certificate taking only in consideration a provided X509Certificate2Collection.