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?