10

I'm receiving a SAML request via HTTP-redirect binding the content of the SAML request look like this

{"SigAlg"=>"http://www.w3.org/2000/09/xmldsig#rsa-sha1", "SAMLRequest"=>"lVLLaoQwFP0VyT5jEqPG4AiFoSDMtNApXXQzxDxaQRObRDqfX3XoolAKXd7DPQ/uuXUQ4zDxo3tzc3zSH7MOMWkPe3DpcixzVVVQl4RBqoiCncEYEmkoY7k00hCQvGgfemf3gOwQSNoQZt3aEIWNC4RwCRGGiD6jkmPMs2KHUPYKksPi0lsRN+Z7jFPgafqpvejtbtQpSK7jYAPfsu3B7C13IvSBWzHqwKPk57vTkS+WfPIuOukG0NSbub9R/yaJELRfzUGzrhmtFut15qdeeheciY926K2u05toUz8sIu0huXd+FPFv9RXpFTTbKp/WA4WobQT/jEYrykwhNaQ66yDNMwY7wijEtMCmysqqo6xOb8Ga+tbjWYe1jtYqfW0uCucoYwWCHS3F0kRGoajWTpAiiJRZJRmu01+Y3+CPt2i+AA=="}

It also has a Signature value

WkDaGzC6vPTlzh+EnFA5/8IMmV7LviyRh2DA5EHF0K0nl+xzBlKfNCYRnunpwoEvGhereGdI5xBpv+mc9IguiCaLZSZjDh6lIDdpvctCnmSNzORqzWQwQGeZ9vjgtCLjUn35VZLNs3WgEqbi2cL+ObrUDS2gV1XvBA3Q3RRhoDmi+XE89Ztnd1cNpR3XdA+EL2ENbMI2XAD9qSgMufUJY/3GBBpT7Vg1ODtPxBudq+sXrgPh/+WtUUitLkkfC8tdRTCS1EZPv+h27I5g/VNza23Xl8w2HdAuYP0F2FjREo8VV2aUtaOUd/jAF9+bfkGV93y1PzFttLxdBbFoxp6qBg==

But I fail to understand how to verify this signature is correct.

Section 3.4.4.1 on SAML binding https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf

To construct the signature, a string consisting of the concatenation of the RelayState (if present),
SigAlg, and SAMLRequest (or SAMLResponse) query string parameters (each one URLencoded)
is constructed in one of the following ways (ordered as below):
SAMLRequest=value&RelayState=value&SigAlg=value
SAMLResponse=value&RelayState=value&SigAlg=value

I tried the approach but

  • The signature I generated using the Private key does not match to the one I received from my SP. (posted above)

  • Also, I'm not able to decrypt the signed message using the Private key (I'm assuming the Signature was created using the public that I federated it with.)

<samlp:LogoutRequest ID="_36167d94-d868-4c04-aee3-8bbd4ed91317" Version="2.0" IssueInstant="2017-01-05T16:21:55.704Z" Destination="https://werain.me/" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"><Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">urn:federation:MicrosoftOnline</Issuer><NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">4948f6ce-4e3b-4538-b284-1461f9379b48</NameID><samlp:SessionIndex>_eafbb730-b590-0134-a918-00d202739c81</samlp:SessionIndex></samlp:LogoutRequest>

Any help here.

Anders Revsgaard
  • 3,636
  • 1
  • 9
  • 25
Ratatouille
  • 1,372
  • 5
  • 23
  • 50

7 Answers7

16

A SAML authentication message is a XML document with an embedded (enveloped) XMLDSig signature or a deflated encoding signature

Enveloped XMLDSign signature

<samlp:LogoutRequest>
    <...saml message...> 
    <ds:Signature>
         <ds:SignedInfo />
         <ds:SignatureValue /> 
         <ds:KeyInfo /> 
    </ds:Signature> 
</samlp:LogoutRequest>

<ds:SignatureValue> contains the signature, <ds:SignedInfo> the signed data and a reference to the message and <ds:KeyInfo> usually contains the X509Certificate with the identity of the signer, or a reference to that certicate

Deflated encoding in URL

SAMLRequest=value&RelayState=value&SigAlg=value&Signature=value

Where each value is url encoded

SAMLRequest=urlencode(base64(<samlp:LogoutRequest> <...saml message...> </samlp:LogoutRequest>))

And the signature is done on a concatenation of query string algorithm using the algorithm SigAlg

Signature = urlencode( base64 ( SigAlg ("SAMLRequest=value&RelayState=value&SigAlg=value")))

Digital signature of SAML messages

SAML message is digitally signed (not encrypted) with the private key of the issuer (SP), and can be verified with the public key of the SP. A SAML response must be signed with the private key of the identity provider (IdP), and the SP can verify the message with the public key of the IdP.

If you act as IdP and you want to verify a SAML request of the SP, you need:

  • Verify the digital signature: Verify using the public key of the SP that the signature match with the signed message to ensure the identity of the signer and the message has not been altered

  • Authorize the request: Verify that the identity of the signer can perform the requested operation. Usually you have to match the serial number or the subject of the certificate with a pre-existent list, or verify that the certificate has been issued by a trusted certificate authority

  • Generate the SAML response: Generate a XML message with the SAML data and sign it with your private key to send to SP

Most programming languages support XMLDsig signatures but in your case is used the deflated encoding that is a specific characteristic of SAML binding, so if your SAML library does not support it, you have to verify the signature manually. These are more or less the steps to follow according to specification

 //get params from query string 
String samlrequest = getQueryParam("SAMLRequest");
String relaystate = getQueryParam("RelayState");
String sigalg = getQueryParam("SigAlg");
String signature = getQueryParam("Signature");


//The signature
byte signature[] = URLDecoder.decode(Base64.getDecoder().decode(signature ), "UTF-8");

//The signed data. build the following string checking if RelayState is null
//SAMLRequest=samlrequest&RelayState=relaystate&SigAlg=sigalg
byte signedData[] = concat(samlrequest,relaystate,sigalg);

//The signature algorithm could be "SHA1WithRSA" or "SHA1withDSA" depending on sigalg is http://www.w3.org/2000/09/xmldsig#rsa-sha1 or http://www.w3.org/2000/09/xmldsig#dsa-sha1 
String signatureAlgorithm = extractSignatureAlgorithm(sigalg);

//get the public key of the SP. It must be registered before this process
PublicKey publicKey = ...

//Verify the signature
Signature sig = Signature.getInstance(signatureAlgorithm);
sig.initVerify(publicKey);
sig.update(signedData); 
boolean verifies = sig.verify(signature);  
pedrofb
  • 37,271
  • 5
  • 94
  • 142
  • 2
    couple of thing. Firstly it's a `LogoutRequest` not SAML `AuthRequest` (I guess I forget to mention that). Second The binding is HTTP redirect and not HTTP-POST see SECTION 3.4.4.1 of SAML bindings. – Ratatouille Jan 05 '17 at 05:14
  • Also Correct me If I'm wrong but this how SAML signature and verification work right. IDP (has it own public/private pair) SP (has it own public/private pair). If IDP has to sign a data it sign it using it own private key. the SP can then decrypt the sign data using the Public/cert of IDP (shared via metadata of IDP) . Like wise if the SP has to signed a data it does so using it own private key then IDP decrypt it using the Public/Cert of SP (accessible via SP metadata) – Ratatouille Jan 05 '17 at 05:19
  • `AuthRequest` just was an example. The signature model for IdP&SP is correct. Only the term `decrypt` is not suitable. The message is signed, not encrypted (content is not hidden), and the operation to check with public key is `verification` – pedrofb Jan 05 '17 at 06:45
  • If I'm correct you are talking about embedded signature what I'm receiving from SP is a request over HTTP-redirect binding with SAML request that has no embedded signature. Added the Snippet of my SAML logout request. – Ratatouille Jan 05 '17 at 17:17
  • Your SAML logout includes `` to identify which sessions to logout, so in this case I guess the digital signature is not needed because client was identified previously and the posession of the sessionIndex is enought to authenticate the request – pedrofb Jan 06 '17 at 10:02
  • Agreed but just as mandatory check I want to verify the signature. – Ratatouille Jan 06 '17 at 10:28
  • In the XML you have posted there is no any signature, so you cant verify it. Have you omitted something? – pedrofb Jan 06 '17 at 10:40
  • Yes exactly the signature is not embedded in the XML its a part of the `query string`. I have pasted the query string in a normalise form above. Since I'm getting the SAML request via HTTP redirect binding. – Ratatouille Jan 06 '17 at 10:56
  • Ah, I understand now. It is a detached signature over the message. Then it is needed to build a data message concatenating fields from URL according the specification, and verify that the provided signature match. This is not a xmldisg standard so you will have to execute all steps( except if your saml library supports it) i will tske a look to specificstion – pedrofb Jan 06 '17 at 11:12
  • I tried verifying the data from theMicrosoft public key from the metadata `https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml` but signature does not match. – Ratatouille Jan 06 '17 at 17:45
  • I have added to the answer a description of "Deflated encoding" present in SAML-binding documentation and the pseudocode to verify it. Note that I can not provide a full example because I have no access to an example request and a certificate, so the code is deduced from specification. I guess you will need to debug it. Check the specificacion because there are some steps not fully clear – pedrofb Jan 07 '17 at 10:42
  • Correct me if I wrong currently the SAMLRequest is deflated and base64 encode for to concatenation which of three should I used as SAMLRequest value.1) Default value the deflated and base64 encoded value. 2) The base64 encode value of the actual/plain SAML XML (obtained by inflating the SAMLRequest) and 3) the plain SAML XML. – Ratatouille Jan 07 '17 at 16:31
  • Also Public_key you are referring is the public key of the Originator of the request right?. If yes, then it that case the Public is part of the metadata of microsoft. – Ratatouille Jan 07 '17 at 16:32
  • You have to use values received in query params `SAMLRequest=`. Case 1. The `publicKey` is the owned by Originator because you want to validate its identity – pedrofb Jan 07 '17 at 17:14
  • I will cross check it again. Last time, when I tested it the verification failed with the public key of microsoft. provided in the given [metadata](https://nexus.microsoftonline-p.com/federationmetadata/saml2‌​0/federationmetadata‌​.xml) – Ratatouille Jan 09 '17 at 03:06
  • I can not access to that link – pedrofb Jan 09 '17 at 08:28
  • The corrected Metadata Link [https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml](https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml) – Ratatouille Jan 09 '17 at 10:11
  • This document contains two certificates with the same data in node ``. One signed with SHA1 and the other with SHA1. If you are using the SAML service from microsoft, probably these are the public keys you need to validate the saml requests. The ` – pedrofb Jan 10 '17 at 08:37
  • Yes but for some reason the verification of the signature against those certificate fail. – Ratatouille Jan 11 '17 at 06:13
2

I'am trying using the above answer but don't success.

Then, read the documentation and a little time, i have sucess to validate signature with Java and the fast answer is:

final String samlRequest = request.getParameter("SAMLRequest");
final String relayState = request.getParameter("RelayState");
final String sigAlg = request.getParameter("SigAlg");
final String signature = request.getParameter("Signature");

FileInputStream fis = new FileInputStream(new File("path-to-service-provider-x509-certificate"));

CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(fis);

// ps: java.net.URLEncoder;
String query = "SAMLRequest=" + URLEncoder.encode(samlRequest, "UTF-8");
query += "&RelayState=" +URLEncoder.encode(relayState, "UTF-8");
query += "&SigAlg=" + URLEncoder.encode(sigAlg, "UTF-8");

// ps: org.opensaml.xml.util.Base64
byte[] signatureBytes = Base64.decode(signature);

org.apache.xml.security.Init.init();
Signature sig = Signature.getInstance("SHA1withRSA"); // or other alg (i, e: SHA256WithRSA or others)
sig.initVerify(cert.getPublicKey());
sig.update(query.getBytes());
Boolean valid = sig.verify(signatureBytes);
2

A SAML 2.0 signature is validated differently depending on the binding (POST or Redirect). If a POST binding is used the signature is validated in the SAML XML. If a Redirect binding is used the query string is validated with the signature.

This LogoutRequest is send with a redirect binding. The following C# sample code is copied from the ITfoxtec.Identity.Saml2 component and show how to validate the signature.

var queryString = request.QueryString;
var signatureValue = Convert.FromBase64String(request.Query["Signature"]);

var messageName = "SAMLRequest";
var signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
var signatureValidationCertificate = new X509Certificate2("path-to-service-provider-x509-certificate");

var saml2Sign = new Saml2SignedText(signatureValidationCertificate, signatureAlgorithm);
if (saml2Sign.CheckSignature(Encoding.UTF8.GetBytes(new RawSaml2QueryString(queryString, messageName).SignedQueryString), signatureValue))
{
    // Signature is valid.
}
else
{
    throw new InvalidSignatureException("Signature is invalid.");
}
Anders Revsgaard
  • 3,636
  • 1
  • 9
  • 25
2

We can use the one login saml library to verify auth-request signature.They provide a lot of wrapper methods for SAML.This is a ruby implementation of it. `

def verify_signature(params)
    saml_request = URI.decode(params[:SAMLRequest])
    relay_state_string = URI.decode(params[:RelayState])
    signature = URI.decode(params[:Signature])
    sign_alg = URI.decode(params[:SigAlg])
    query_params,sig_params={},{}
    query_params[:type] = "SAMLRequest"
    query_params[:data] = saml_request
    query_params[:relay_state] = relay_state_string
    query_params[:sig_alg] = sign_alg
    query = OneLogin::RubySaml::Utils.build_query(query_params)
    sig_params[:cert] = getPublicKeyFromCertificate
    sig_params[:sig_alg] = sign_alg
    sig_params[:signature] = signature
    sig_params[:query_string] = query
    OneLogin::RubySaml::Utils.verify_signature(sig_params)
end

`

1

One point I would like to add to the above answers: URL encoding/decoding is non-canonical, meaning that every framework/language may in fact have a different way of doing it. I was stuck on verifying an HTTP-Redirect binding for many days, turns out that the Java Play 1.x framework we are using URL decodes things in a different way than the SAML framework expects.

We resolved this issue by instead taking the query parameters directly out of the query string, rather than letting Play framework decode it for us (only for us to need to re-encode it back). So if your code matches Alexandre's but the SAML framework says the signature is invalid, make sure that you're feeding into the algorithm the strings that are directly taken from the URL GET parameters.

ibly31
  • 11
  • 1
1

For those still stuck, here is the complete method

public static void verifySignature(boolean isResponse, String samlQueryString, String relayStateString, String sigAlgString, String signature, X509Certificate cert) throws Exception {
    String type = isResponse ? "SAMLResponse" : "SAMLRequest";

    String query = type + "=" + URLEncoder.encode(samlQueryString, "UTF-8");
        query += relayStateString == null ? "" : "&RelayState=" + URLEncoder.encode(relayStateString, "UTF-8");
        query += "&SigAlg=" + URLEncoder.encode(sigAlgString, "UTF-8");

    String javaSigAlgName = null;

    if(sigAlgString.equals("http://www.w3.org/2000/09/xmldsig#rsa-sha1")) {
        javaSigAlgName = "SHA1withRSA";
    } else if(sigAlgString.equals("http://www.w3.org/2000/09/xmldsig#rsa-sha256")) {
        javaSigAlgName = "SHA256withRSA";
    } else {
        throw new Exception("signature: " + sigAlgString + " not supported by SP/IDP");
    }

    byte[] signatureBytes = Base64.getDecoder().decode(signature);

    Signature sig = Signature.getInstance(javaSigAlgName);
    sig.initVerify(cert.getPublicKey());
    sig.update(query.getBytes());

    Boolean valid = sig.verify(signatureBytes);
    System.out.println("is valid: " + valid);
}
nitin
  • 35
  • 6
0

We spent hours trying to figure this out, and finally stumbled across this thread. Just wanted to attach the following image which I created to clearly show how to build a signed SAML AuthNRequest http-redirect request to contribute to this very valuable thread.

enter image description here

Appleoddity
  • 647
  • 1
  • 6
  • 21