TL;DR

Some message parts in SAML 2.0 require fiddling to see what’s inside.

In SAML 2.0, the SAMLRequest parameter is included in a URL like this:

https://example.com/?SAMLRequest=...the-request...

where ...the-request... is built as follows:

  • start from the XML text that represents the AuthnRequest
  • apply the DEFLATE algorithm to obtain a compressed binary string
  • apply the Base64 encoding
  • apply url encoding to the Base64 string

So, if we start from this example:

<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="identifier_1"
    Version="2.0"
    IssueInstant="2004-12-05T09:21:59Z"
    AssertionConsumerServiceIndex="1">
    <saml:Issuer>https://sp.example.com/SAML2</saml:Issuer>
    <samlp:NameIDPolicy 
      AllowCreate="true"  
      Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>

we end up with this:

https://example.com/?SAMLRequest=fZFLa8MwEITvhf4Ho3v8ojlksQ0hoWBoS2lKD70UYW%2BIQA9Xu2rdf1%2FFSUqaQ3ScmU87WlUkjR5gGXhnX%2FAzIPHtTRLPaLQlmNxaBG%2FBSVIEVhok4A42y8cHKNMcBu%2FYdU6LS%2B46JonQs3L2yLXrWqgeLautQv9RHOU39BRDtYjMKUkUsLXE0nLU8%2FxuVpSzfP6aL6AsYL54PwaXpxErZykY9Bv0X6qLbI9jLQrRHHLVvi1M1%2FpmxzwQZBkNKY7SDBrTzpls37ussvPkGTzAU3xgu352WnU%2FycGJBbR23yuPkrEW7AOK5M%2B7d95Ivr6jvaL62XaKAntpScUNiSzOPnT5%2F3PNLw%3D%3D

OK, now we received this and want to look inside… what do we do?

This:

Local versions: cpanfile and saml-request.

If we use Carton, the cpanfile will help us installing the modules. We’re standing on the shoulders of giants here: IO-Compress, XML::Twig, and Mojolicious.

The program does the reverse of the encoding operations described above:

  • function get_urlparam gives us the value of the SAMLRequest URL parameter. Mojo::URL takes care to reverse the url encoding for us;
  • function decode_saml does the heavylifting of turning the encoded value back into an XML string
  • function pretty_xml helps us pretty-printing the XML text on the output.

Let’s see how it goes:

$ perl saml-request 'https://example.com/?SAMLRequest=fZFLa8MwEITvhf4Ho3v8ojlksQ0hoWBoS2lKD70UYW%2BIQA9Xu2rdf1%2FFSUqaQ3ScmU87WlUkjR5gGXhnX%2FAzIPHtTRLPaLQlmNxaBG%2FBSVIEVhok4A42y8cHKNMcBu%2FYdU6LS%2B46JonQs3L2yLXrWqgeLautQv9RHOU39BRDtYjMKUkUsLXE0nLU8%2FxuVpSzfP6aL6AsYL54PwaXpxErZykY9Bv0X6qLbI9jLQrRHHLVvi1M1%2FpmxzwQZBkNKY7SDBrTzpls37ussvPkGTzAU3xgu352WnU%2FycGJBbR23yuPkrEW7AOK5M%2B7d95Ivr6jvaL62XaKAntpScUNiSzOPnT5%2F3PNLw%3D%3D'

SAMLRequest = fZFLa8MwEITvhf4Ho3v8ojlksQ0hoWBoS2lKD70UYW+IQA9Xu2rdf1/FSUqaQ3ScmU87WlUkjR5gGXhnX/AzIPHtTRLPaLQlmNxaBG/BSVIEVhok4A42y8cHKNMcBu/YdU6LS+46JonQs3L2yLXrWqgeLautQv9RHOU39BRDtYjMKUkUsLXE0nLU8/xuVpSzfP6aL6AsYL54PwaXpxErZykY9Bv0X6qLbI9jLQrRHHLVvi1M1/pmxzwQZBkNKY7SDBrTzpls37ussvPkGTzAU3xgu352WnU/ycGJBbR23yuPkrEW7AOK5M+7d95Ivr6jvaL62XaKAntpScUNiSzOPnT5/3PNLw==

<samlp:AuthnRequest AssertionConsumerServiceIndex="1" ID="identifier_1" IssueInstant="2004-12-05T09:21:59Z" Version="2.0" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <saml:Issuer>https://sp.example.com/SAML2</saml:Issuer>
  <samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>

It seems to be working… good.

Enough for today, stay safe folks!