10

I've looked at other posts on here regarding this issue and none of them seem to address my situation.

I've been trying to verify a SAML assertion for the last week and I have 2 clients that have sent me SAML but I cannot verify it.

The main process is we get a base64 encoded assertion and I decode it. Load it into an XmlDocment with PreserveWhitespace = true.

The verify method is

  public static bool Verify(X509Certificate2 cert, XmlElement xmlElement, SignedXml signedXml)
  {
       bool flag;
       try
       {
           KeyInfo keyInfo = new KeyInfo();
           var clause = new KeyInfoX509Data(cert);
           keyInfo.AddClause(clause);

            XmlElement signatureElement = GetSignatureElement(xmlElement);
            if (signatureElement == null)
            {
                string message = "The XML does not contain a signature.";
                throw new SAMLSignatureException(message);
            }
            signedXml.LoadXml(signatureElement);
            if (keyInfo != null)
            {
                signedXml.KeyInfo = keyInfo;
            }
            SetSigningKeyFromKeyInfo(signedXml);
            flag = signedXml.CheckSignature(cert.PublicKey.Key);
        }
        catch (Exception exception)
        {
            throw new SAMLSignatureException("Failed to verify the XML signature.", exception);
        }
        return flag;
    }

 private static void SetSigningKeyFromKeyInfo(SignedXml signedXml)
    {
        IEnumerator enumerator = signedXml.KeyInfo.GetEnumerator();
        while (enumerator.MoveNext())
        {
            if (enumerator.Current is KeyInfoX509Data)
            {
                var current = (KeyInfoX509Data) enumerator.Current;
                if (current.Certificates.Count != 0)
                {
                    var certificate = (X509Certificate) current.Certificates[0];
                    var certificate2 = new X509Certificate2(certificate);
                    AsymmetricAlgorithm key = certificate2.PublicKey.Key;
                    signedXml.SigningKey = key;
                    return;
                }
            }
            else
            {
                if (enumerator.Current is RSAKeyValue)
                {
                    var value2 = (RSAKeyValue) enumerator.Current;
                    signedXml.SigningKey = value2.Key;
                    return;
                }
                if (enumerator.Current is DSAKeyValue)
                {
                    var value3 = (DSAKeyValue) enumerator.Current;
                    signedXml.SigningKey = value3.Key;
                    return;
                }
            }
        }
        throw new SAMLSignatureException("No signing key could be found in the key info.");
    }

I have the certificate from the client that I read in from Web.Config (its stored as base64 encoded string) xmlelement is the signed element, signedXml is a SignedXml object that was created with new SignedXml(xmlElement)

Both clients get false returned by checksignature but when I create my own signed saml with my certificate it will return true.

What am I missing here?

EDIT: Yes both of the clients are on Java and I posted the SetSigningKeyFromKeyInfo method

Perception
  • 79,279
  • 19
  • 185
  • 195
Jonathan S.
  • 541
  • 1
  • 3
  • 13
  • 1
    Let me guess, the assertion you receive has been generated in a non-.net language, such as Java? – Klaus Byskov Pedersen Oct 14 '10 at 17:11
  • What does `SetSigningKeyFromKeyInfo(signedXml);` do? – Klaus Byskov Pedersen Oct 14 '10 at 17:13
  • When you have base64decoded the assertion, can you dump the xml to a file and compare it to one of your own assertions to check for (subtle) structural inconsistencies? – Klaus Byskov Pedersen Oct 14 '10 at 17:17
  • See my edit. As far as comparing the 2 assertions yes. I've looked at both assertions and cant find anything that sticks out at me. – Jonathan S. Oct 14 '10 at 17:22
  • You may want to check this implementation: http://digitaliser.dk/resource/558794/artefact/oiosaml-dot-net-1.6.zip It is an open source implementation of the SAML protocol in .net that I participated in writing some years ago. I haven't checked the current version, but you should be able to get some inspiration from there. – Klaus Byskov Pedersen Oct 14 '10 at 17:22
  • I have had a vaguely similar experience with encryption that turned out to be different encodings being used (utf-8 vs utf-16). Could a similar problem be occurring here? I am pretty sure the default encoding is utf-8 for Java and utf-16 for .net – Michael Jun 29 '12 at 03:39

4 Answers4

10

I dealt with signed XML's a lot in the past. All I can say is that it was a nightmare. Basically, when you sign XML, it goes through a process called canonicalization (C14N). It needs to turn XML text to a byte stream which can be signed. Whitespace & namespace handling, among others, in XML C14N standards are hard to understand, even harder to implement right. There are even multiple types of C14N.

The .NET implementation is very selective about what it accepts. It's quite possible that your other implementation doesn't work in the exact same way as the .NET one. This is very sad indeed. If you can eliminate whitespace and namespaces from your source XML before signing, for example, that could help. Also if you could make sure that both implementations use the same C14N settings.

Otherwise a lot of debugging awaits you. You could debug into the framework, or call its internal methods by hand with reflection, to see how it calculates the XML fragment and the signature. And do the same with the other implementation. Basically you need to see the exact byte streams that are signed in both cases. This is the final step of the conversion before signing. If those byte streams match, then you'll have no problems with the RSA signing part in my experience. If those don't match, as is in your case, at least you'll see where the problem is.

fejesjoco
  • 11,763
  • 3
  • 35
  • 65
  • Had this issue with the `ITFoxtec.Identity.Saml2.MvcCore` package and it turns out it's well documented - https://github.com/ITfoxtec/ITfoxtec.Identity.Saml2/issues/10 – user692942 Apr 11 '23 at 14:05
8

I just had a similar issue and lost a lot of time, maybe this can help someone.

My environment is 100% .Net 4.5, and my code uses only the SignedXml class. But a SAML assertion was accepted at one place and refused at another one.

Turned out that one place was loading the assertion through an XmlDocument instance initialized with PreserveWhitespace = true, while the other one was not.

And the assertion had been pretty-printed, so it had carriage returns and a lot of indentation spaces. Removing all carriage returns and the indentation spaces fixed my issue.

Timores
  • 14,439
  • 3
  • 46
  • 46
  • The default value for XmlDocument.PreserveWhitespace is false. Switched to true and was able to get a 'true' back from CheckSignature. I'm guessing that the whitespace was used when calculating the signature with the certificate, and when stripped out the signature was not seen as valid. – voidsstr Jun 24 '20 at 11:15
2

Had a similar issue with Saml as Timores. The Saml needed to be decoded from Base64 but first i used:

var saml = System.Text.Encoding.Default.GetString(Convert.FromBase64String(samlToken))

But this used ASCII decoding and had trouble with special characters. Which means the XML was slightly different from when it was signed and that is why it failed. Changed it to:

var saml = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(samlToken))

and it worked for all cases.

So be sure that you are using the right encoding!

Jeroen VL
  • 341
  • 5
  • 9
2

I've spent a lot of time on this issue and then realized that I am not checking the signature with the right certificate.

So I decided to check the certificate I am receiving in the XML response file from Azure:

signedXml.LoadXml((XmlElement)nodeList[0]);

X509Certificate2 serviceCertificate = null;
foreach (KeyInfoClause clause in signedXml.KeyInfo)
{
    if (clause is KeyInfoX509Data)
    {
        if (((KeyInfoX509Data)clause).Certificates.Count > 0)
        {
            serviceCertificate = (X509Certificate2)((KeyInfoX509Data)clause).Certificates[0];
        }
    }
}

Then:

bool bTest = signedXml.CheckSignature(serviceCertificate , true);

The bTest value was finally set to true.

Tom
  • 16,842
  • 17
  • 45
  • 54