Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single Logout issue (Signature validation failed. Logout Response rejected) #80

Closed
m1kl opened this issue Jul 21, 2015 · 4 comments
Closed

Comments

@m1kl
Copy link

m1kl commented Jul 21, 2015

Hi,

I've been trying to use OneLogin PHP Toolkit (v2.6) to enable SSO with our IDP (ADFS 3.0). So far I've been able to make single sign on to work however I am still having issues with the single logout process.

After some research about the issue I've found out that I'm having exactly the same issue reported by @jacquesd but for him it happened with the OneLogin Python Toolkit (SAML-Toolkits/python-saml#53).

I've tried to set in ADFS side to use RSA-SHA256 as well as RSA-SHA1 with no luck.

The error on the SP side is:

Signature validation failed. Logout Response rejected

And the error reported by ADFS is listed below.

The SAML Single Logout request does not correspond to the logged-in session participant.
Requestor: https://laravel-adfs.apps.domain.pt/saml2/metadata
Request name identifier: Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient, NameQualifier:  SPNameQualifier: https://laravel-adfs.apps.domain.pt/saml2/metadata, SPProvidedId:
Logged-in session participants:
Count: 1, [Issuer: https://laravel-adfs.apps.domain.pt/saml2/metadata, NameID: (Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient, NameQualifier:  SPNameQualifier: , SPProvidedId: )]

This request failed.

User Action
Verify that the claim provider trust or the relying party trust configuration is up to date. If the name identifier in the request is different from the name identifier in the session only by NameQualifier or SPNameQualifier, check and correct the name identifier policy issuance rule using the AD FS Management snap-in.

Below are the SAML AuthnRequest and its response as well as the Logout request and corresponding response.

  • AuthnRequest for Login:
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                    ID="ONELOGIN_1ad5626afbd4d710ee1af977ad85170513b0e70c"
                    Version="2.0"
                    ProviderName="Display Name"
                    IssueInstant="2015-07-21T15:05:32Z"
                    Destination="https://login.domain.pt/adfs/ls/"
                    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                    AssertionConsumerServiceURL="https://laravel-adfs.apps.domain.pt/saml2/acs"
                    >
    <saml:Issuer>https://laravel-adfs.apps.domain.pt/saml2/metadata</saml:Issuer>
    <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
                        AllowCreate="true"
                        />
    <samlp:RequestedAuthnContext Comparison="exact">
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
  • Response for login:
<samlp:Response ID="_e0d1cfdc-6325-4c37-918e-77cf1a49f3e4"
                Version="2.0"
                IssueInstant="2015-07-21T15:05:37.942Z"
                Destination="https://laravel-adfs.apps.domain.pt/saml2/acs"
                Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
                InResponseTo="ONELOGIN_1ad5626afbd4d710ee1af977ad85170513b0e70c"
                xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                >
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://login.domain.pt/adfs/services/trust</Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </samlp:Status>
    <EncryptedAssertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
                            xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
                            >
            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
                    <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
                        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                    </e:EncryptionMethod>
                    <KeyInfo>
                        <ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                            <ds:X509IssuerSerial>
                                <ds:X509IssuerName>E=usi.dsi@domain.pt, OU=DSI, L=Leiria, CN=laravel-adfs.apps.domain.pt, O=domain, S=Leiria, C=pt</ds:X509IssuerName>
                                <ds:X509SerialNumber>0</ds:X509SerialNumber>
                            </ds:X509IssuerSerial>
                        </ds:X509Data>
                    </KeyInfo>
                    <e:CipherData>
                        <e:CipherValue>{CIPHER_VALUE}</e:CipherValue>
                    </e:CipherData>
                </e:EncryptedKey>
            </KeyInfo>
            <xenc:CipherData>
                <xenc:CipherValue>{CIPHER_VALUE}</xenc:CipherValue>
            </xenc:CipherData>
        </xenc:EncryptedData>
    </EncryptedAssertion>
</samlp:Response>
  • Logout request:
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                     xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
                     ID="ONELOGIN_6d44e21bea5525f610e944263d2216cb13d4e947"
                     Version="2.0"
                     IssueInstant="2015-07-21T15:05:39Z"
                     Destination="https://login.domain.pt/adfs/ls/"
                     >
    <saml:Issuer>https://laravel-adfs.apps.domain.pt/saml2/metadata</saml:Issuer>
    <saml:NameID SPNameQualifier="https://laravel-adfs.apps.domain.pt/saml2/metadata"
                 Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
                 >michael.pinheiro@domain.pt</saml:NameID>
    <samlp:SessionIndex>_5ff47856-2a72-43c2-a512-22443ee38496</samlp:SessionIndex>
</samlp:LogoutRequest>
  • Logout response:
<samlp:LogoutResponse ID="_43c58d04-48a7-45bb-917a-610ec22df0d9"
                      Version="2.0"
                      IssueInstant="2015-07-21T15:05:39.442Z"
                      Destination="https://laravel-adfs.apps.domain.pt/saml2/sls"
                      Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
                      InResponseTo="ONELOGIN_6d44e21bea5525f610e944263d2216cb13d4e947"
                      xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                      >
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://login.domain.pt/adfs/services/trust</Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Requester" />
    </samlp:Status>
</samlp:LogoutResponse>

The onelogin configurations that I've been using is listed below:

<?php

$idp_hostname = 'login.ipleiria.pt';

return $settings = array(
    'useRoutes' => true, //include library routes and controllers
    'routesPrefix' => '/saml2',
    'logoutRoute' => '/',
    'loginRoute' => '/',
    'errorRoute' => '/',


    /*****
     * One Loign Settings
     */
    'strict' => false, //@todo: make this depend on laravel config
    'debug' => true, //@todo: make this depend on laravel config
    'sp' => array(
        'NameIDFormat' => OneLogin_Saml2_Constants::NAMEID_TRANSIENT,
        'x509cert' => file_get_contents(base_path('certificates/sp.crt')),
        'privateKey' => file_get_contents(base_path('certificates/sp.key')),
        'entityId' => 'https://laravel-adfs.apps.domain.pt/saml2/metadata', //LARAVEL: This would be set to saml_metadata route
        'assertionConsumerService' => array(
            'url' => 'https://laravel-adfs.apps.domain.pt/saml2/acs', //LARAVEL: This would be set to saml_acs route
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
        ),
        'singleLogoutService' => array(
            'url' => 'https://laravel-adfs.apps.domain.pt/saml2/sls', //LARAVEL: This would be set to saml_sls route
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
        ),
    ),

    'idp' => array(
        'entityId' => 'http://' . $idp_hostname . '/adfs/services/trust',
        'singleSignOnService' => array(
            'url' => 'https://' . $idp_hostname . '/adfs/ls/',
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
        ),
        'singleLogoutService' => array(
            'url' => 'https://' . $idp_hostname . '/adfs/ls/',
            'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
        ),
        'x509cert' => file_get_contents(base_path('certificates/adfs-signing.crt')),
    ),

    /***
     *  OneLogin advanced settings
     */
    'security' => array(
        'nameIdEncrypted' => false,
        'authnRequestsSigned' => true,
        'logoutRequestSigned' => true,
        'logoutResponseSigned' => true,
        'signMetadata' => false,
        'wantMessagesSigned' => true,
        'wantAssertionsSigned' => true,
        'wantNameIdEncrypted' => false,
        'requestedAuthnContext' => true,
        //'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
    ),

    // Contact information template, it is recommended to suply a technical and support contacts
    'contactPerson' => array(
        'technical' => array(
            'givenName' => 'name',
            'emailAddress' => 'no@reply.com'
        ),
        'support' => array(
            'givenName' => 'Support',
            'emailAddress' => 'no@reply.com'
        ),
    ),

    // Organization information template, the info in en_US lang is recomended, add more if required
    'organization' => array(
        'en-US' => array(
            'name' => 'Name',
            'displayname' => 'Display Name',
            'url' => 'http://url'
        ),
    ),

);

We also noticed that the signature verification that fails is using the IDP certificate and we ensure that it was correct.

@pitbulk
Copy link
Contributor

pitbulk commented Jul 21, 2015

ADFS returns A Logout Response with:

<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Requester" />

So the Logout Process failed. If you read ADFS report:

The SAML Single Logout request does not correspond to the logged-in session participant.
Requestor: https://laravel-adfs.apps.domain.pt/saml2/metadata
Request name identifier: Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient, NameQualifier:  SPNameQualifier: https://laravel-adfs.apps.domain.pt/saml2/metadata, SPProvidedId:
Logged-in session participants:
Count: 1, [Issuer: https://laravel-adfs.apps.domain.pt/saml2/metadata, NameID: (Format: urn:oasis:names:tc:SAML:2.0:nameid-format:transient, NameQualifier:  SPNameQualifier: , SPProvidedId: )]

is not a signature validation problem, instead: "The SAML Single Logout request does not correspond to the logged-in session participant."

I can't see what NameID was at the SAML Response sent by ADFS since the Assertion is encrypted, but seems that is different than the nameID provided by you on the Logout Request.

    <saml:NameID SPNameQualifier="https://laravel-adfs.apps.domain.pt/saml2/metadata"
                 Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
                 >michael.pinheiro@domain.pt</saml:NameID>

@jlmnrc
Copy link

jlmnrc commented Jul 21, 2015

I got this error before when doing slo with ADFS. To resolve it, keep the $auth->getNameId() and $auth->getSessionIndex() (that you received from login response), then pass it to the $auth->logout function when doing logout.

@m1kl
Copy link
Author

m1kl commented Jul 23, 2015

@johnliman: I was already keeping the nameId and the sessionIndex to send them throught the logout method.

@pitbulk: Indeed I was missing the SPNameQualifer in my claims rules and I fixed it using the following custom rule in ADFS side.

c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
 => issue(
Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = c.Value, ValueType = c.ValueType, 
Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", 
Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/spnamequalifier"] = "https://laravel-adfs.apps.domain.pt/saml2/metadata");

After fixing the ADFS claim rule, the user was successfully logged out from ADFS side however I was still having the signature verification failure. I found out that setting the $retrieveParametersFromServer parameter to true in the processSLO method called in the sls endpoint fixed my issue.

Thanks all for your help!

@m1kl m1kl closed this as completed Jul 23, 2015
@rudischenck
Copy link

I may have found a bug with this issue, not sure. The reason It works when you call processSLO with $retrieveParametersFromServer set to false is because the signed query does not get URL encoded before validation against the certificate. The offending code starts on Utils.php:1505. Because the signed query is URL encoded the url's don't match what's in the logout response and it throws an error.

        if ($retrieveParametersFromServer) {
            $signedQuery = $messageType.'='.Utils::extractOriginalQueryParam($messageType);
            if (isset($getData['RelayState'])) {
                $signedQuery .= '&RelayState='.Utils::extractOriginalQueryParam('RelayState');
            }
            $signedQuery .= '&SigAlg='.Utils::extractOriginalQueryParam('SigAlg');
        } else {
            $signedQuery = $messageType.'='.urlencode($getData[$messageType]);
            if (isset($getData['RelayState'])) {
                $signedQuery .= '&RelayState='.urlencode($getData['RelayState']);
            }
            $signedQuery .= '&SigAlg='.urlencode($signAlg);
        }

I could be wrong idk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants