11

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?

Community
  • 1
  • 1
Keith
  • 150,284
  • 78
  • 298
  • 434
  • What does GetFirstX509Certificate do? – theycallmemorty May 25 '11 at 20:41
  • I asked a similar question here: http://security.stackexchange.com/questions/3905/verify-saml-response-is-from-a-trusted-source but ultimately ended up at your post because I'm having the same problem with CheckSignature – theycallmemorty May 25 '11 at 20:43
  • @theycallmemorty - I try to explain in the comments, but it does `signedXml.KeyInfo[0].Certificates[0] as X509Certificate2`, but with checks and casts. – Keith May 25 '11 at 22:34
  • I found this and thought you might find it helpful: http://stackoverflow.com/questions/1195728/in-c-sign-an-xml-with-a-x-509-certificate-and-check-the-signature – theycallmemorty May 26 '11 at 12:31
  • @theycallmemorty - cheers, but that's kind of a different problem: they're having problems signing, while I can check the signature and public key match, but can't verify the public key is from a trusted provider. – Keith May 27 '11 at 08:12
  • Have you toyed with the [X509Chain](http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509chain.aspx) to get better information on what part of the verification is failing? – Smudge202 May 31 '11 at 11:27
  • @Smudge202 - yes, it considers one of the certificates in my `X509Certificate2Collection` (sourced from my `.p7b`) file to not be valid. This is a development environment and they're self-issued certificates - I don't need to know whether the `.p7b` certificates are globally valid, I need to know SAML sender's certificate is valid for just that collection. Maybe if I could reset the `X509Chain` somehow to not check the source store? – Keith May 31 '11 at 12:42

1 Answers1

7

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.

João Angelo
  • 56,552
  • 12
  • 145
  • 147
  • Yes, I've tried that - `chain.Build` comes back as invalid because my `X509Certificate2Collection` is invalid. However I'm not looking to validate the collection, and I'm happy to take the contents of the `.p7b` as authoritative - I just want to check the SAML certificate against the `.p7b` collection. – Keith May 31 '11 at 12:36
  • I think the problem might be related to `chain.ChainPolicy.ExtraStore.AddRange` - I'm not looking to add to the existing machine or user store, I need to start fresh with just the keys in the `.p7b` file. – Keith May 31 '11 at 12:38
  • What's the `ChainStatus` you get after you perform the build? It was my understanding that the `ExtraStore` would not add nothing to teh machine or user stores and just include the specified certificates in the validation process. – João Angelo May 31 '11 at 12:48
  • The certificates in my `.p7b` file are test environment ones - they don't come from a valid authority. I don't want to check them though, I want to check the SAML certificate against them. – Keith May 31 '11 at 13:19
  • Ok, I can get your code to work if I add `chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;`, which I think means that it's not checking the collection it has but it's still checking the certificate it's been passed. Is this right? Any ideas what the consequences of not verifying the chain will be? – Keith Jun 01 '11 at 07:20
  • From the docs the `AllFlags` will skip every verification step associated with each of the others verification flags that are defined. In that conditions I'm not sure what exactly is checked, but have you tried a smaller combination of flags. You said that the certificates don't come from a valid CA, so the flag `AllowUnknownCertificateAuthority` is a good option. Try combining the least amount of flags while having a valid result and then check if you can live without the verification steps you skipped. – João Angelo Jun 01 '11 at 08:10