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?
This method builds a simple chain for the certificate and applies the base policy to that chain. If you need more information about a failure, validate the certificate directly using the X509Chain object. Note that the default chaining engine can be overridden using the CryptoConfig class.
X509Certificate2(String, String, X509KeyStorageFlags) Initializes a new instance of the X509Certificate2 class using a certificate file name, a password used to access the certificate, and a key storage flag. X509Certificate2(String, SecureString, X509KeyStorageFlags)
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With