Package org.picketlink.test.identity.federation.core.saml.v2

Source Code of org.picketlink.test.identity.federation.core.saml.v2.SAMLAssertionWrappingAttackTestCase

/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.picketlink.test.identity.federation.core.saml.v2;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.io.InputStream;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Enumeration;

import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.XMLSignatureException;

import org.apache.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response;
import org.picketlink.identity.federation.api.saml.v2.sig.SAML2Signature;
import org.picketlink.identity.federation.core.exceptions.ConfigurationException;
import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator;
import org.picketlink.identity.federation.core.saml.v2.constants.JBossSAMLURIConstants;
import org.picketlink.identity.federation.core.saml.v2.holders.IssuerInfoHolder;
import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil;
import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil;
import org.picketlink.identity.federation.core.util.JAXPValidationUtil;
import org.picketlink.identity.federation.core.util.KeyStoreUtil;
import org.picketlink.identity.federation.core.util.XMLSignatureUtil;
import org.picketlink.identity.federation.saml.v2.assertion.AssertionType;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeStatementType.ASTChoiceType;
import org.picketlink.identity.federation.saml.v2.assertion.AttributeType;
import org.picketlink.identity.federation.saml.v2.assertion.AuthnStatementType;
import org.picketlink.identity.federation.saml.v2.assertion.NameIDType;
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType;
import org.picketlink.identity.federation.saml.v2.assertion.SubjectType.STSubType;
import org.picketlink.identity.federation.saml.v2.protocol.ResponseType;
import org.picketlink.test.identity.federation.api.saml.v2.SignatureValidationUnitTestCase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
* <p>
* This test case demonstrates how PicketLink behaves when a XML Signature Wrapping Attack is performed using a SAML Response
* document. It also forces a successful attack.
* </p>
* <p>
* What is protecting PicketLink to the XML Signature Wrapping Attack is how the idness of attributes is configured for XML elements. PicketLink
* expects to manually set the idness of attributes after Apache Santuario version update.
* </p>
* <p>
* It is strongly recommended to use signatures when configuring IDPs and SPs.
* </p>
* @author <a href="mailto:psilva@redhat.com">Pedro Silva</a>
*/
public class SAMLAssertionWrappingAttackTestCase {

    private String keystoreLocation = "keystore/jbid_test_keystore.jks";
    private String keystorePass = "store123";
    private String alias = "servercert";
    private String keyPass = "test123";
    private PublicKey publicKey;
    private PrivateKey privateKey;

    @Before
    public void onSetup() throws Exception {
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        InputStream ksStream = tcl.getResourceAsStream(keystoreLocation);
        assertNotNull("Input keystore stream is not null", ksStream);

        KeyStore ks = KeyStoreUtil.getKeyStore(ksStream, keystorePass.toCharArray());
        assertNotNull("KeyStore is not null", ks);

        // Check that there are aliases in the keystore
        Enumeration<String> aliases = ks.aliases();
        assertTrue("Aliases are not empty", aliases.hasMoreElements());

        this.publicKey = KeyStoreUtil.getPublicKey(ks, alias, keyPass.toCharArray());
        assertNotNull("Public Key is not null", publicKey);

        this.privateKey = (PrivateKey) ks.getKey(alias, keyPass.toCharArray());
    }

    /**
     * <p>
     * Tests if PicketLink is blinded for XML Signature Wrapping Attacks when using a SAML Response. In this case an exception
     * should be throw because the ID used to reference the signed Response will not be found. This tests shows how PicketLink
     * reacts when a XML Signature Wrapping Attack is performed.
     * </p>
     *
     * @throws Exception
     */
    @Test(expected = XMLSignatureException.class)
    public void testWrappingAttack() throws Exception {
        ResponseType responseType = createSignedResponse();

        SAML2Signature ss = new SAML2Signature();
        ss.setSignatureMethod(SignatureMethod.RSA_SHA1);
        Document signedDoc = ss.sign(responseType, new KeyPair(publicKey, privateKey));

        Logger.getLogger(SignatureValidationUnitTestCase.class).debug(DocumentUtil.asString(signedDoc));
        JAXPValidationUtil.validate(DocumentUtil.getNodeAsStream(signedDoc));

        // Validate the signature
        boolean isValid = XMLSignatureUtil.validate(signedDoc, publicKey);

        assertTrue(isValid);

        // now let's change the response document and wrap a another SAML assertion

        // clone the whole document. The root element is the Response
        Document clonedResponse = (Document) signedDoc.cloneNode(true);

        // let's remove the Signature from the cloned response
        Element signature = (Element) clonedResponse.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature")
                .item(0);

        signature.getParentNode().removeChild(signature);

        // let's remove the original assertion. Later it will be replaced by a another one.
        Element originalAssertion = (Element) signedDoc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Assertion").item(0);

        originalAssertion.getParentNode().removeChild(originalAssertion);

        // let's load a forged assertion
        String fileName = "saml2-wrapping-attack.xml";
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        InputStream is = tcl.getResourceAsStream(fileName);
        Document evilAssertion = DocumentUtil.getDocument(is);

        // let's wrap the forged assertion into the original document.
        Element element = evilAssertion.getDocumentElement();

        Node adoptNode = signedDoc.adoptNode(element);

        signedDoc.getDocumentElement().appendChild(adoptNode);

        // let's append the cloned response document as a child of the original Signature element
        Element signatureOriginal = (Element) signedDoc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#",
                "Signature").item(0);

        Element importedClonedResponse = (Element) signatureOriginal.getOwnerDocument().adoptNode(
                clonedResponse.getDocumentElement());

        signatureOriginal.appendChild(importedClonedResponse);

        // let's change the original response ID attribute value
        signedDoc.getDocumentElement().setAttribute("ID", "evilAssertion");

        // validate the original response with the wrapped assertion
        isValid = XMLSignatureUtil.validate(signedDoc, publicKey);

        assertTrue(false);
    }

    /**
     * <p>
     * Forces the XML Signature Wrapping Attack. This test creates a valid SAML Response properly signed.
     * </p>
     *
     * @throws Exception
     */
    @Test
    public void testForceWrappingAttack() throws Exception {
        ResponseType responseType = createSignedResponse();

        SAML2Signature ss = new SAML2Signature();
        ss.setSignatureMethod(SignatureMethod.RSA_SHA1);
        Document signedDoc = ss.sign(responseType, new KeyPair(publicKey, privateKey));

        Logger.getLogger(SignatureValidationUnitTestCase.class).debug(DocumentUtil.asString(signedDoc));
        JAXPValidationUtil.validate(DocumentUtil.getNodeAsStream(signedDoc));

        // Validate the signature
        boolean isValid = XMLSignatureUtil.validate(signedDoc, publicKey);

        assertTrue(isValid);

        // now let's change the response document and wrap a another SAML assertion

        // clone the whole document. The root element is the Response
        Document clonedResponse = (Document) signedDoc.cloneNode(true);

        // let's remove the Signature from the cloned response
        Element signature = (Element) clonedResponse.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature")
                .item(0);

        signature.getParentNode().removeChild(signature);

        // let's remove the original assertion. Later it will be replaced by a another one.
        Element originalAssertion = (Element) signedDoc.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:assertion",
                "Assertion").item(0);

        originalAssertion.getParentNode().removeChild(originalAssertion);

        // let's load a forged assertion
        ClassLoader tcl = Thread.currentThread().getContextClassLoader();
        String fileName = "saml2-wrapping-attack.xml";
        InputStream is = tcl.getResourceAsStream(fileName);
        Document evilAssertion = DocumentUtil.getDocument(is);

        // let's wrap the forged assertion into the original document.
        Element element = evilAssertion.getDocumentElement();

        Node adoptNode = signedDoc.adoptNode(element);

        signedDoc.getDocumentElement().appendChild(adoptNode);

        // let's append the cloned response document as a child of the original Signature element
        Element signatureOriginal = (Element) signedDoc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#",
                "Signature").item(0);

        Element importedClonedResponse = (Element) signatureOriginal.getOwnerDocument().adoptNode(
                clonedResponse.getDocumentElement());

        signatureOriginal.appendChild(importedClonedResponse);

        // let's change the original response ID attribute value
        signedDoc.getDocumentElement().setAttribute("ID", "evilAssertion");

        // let's set the idness of the ID attribute. This should force the signature validation to be successful. The two lines
        // bellow are responsible to allow the attack.
        importedClonedResponse = (Element) signatureOriginal.getElementsByTagNameNS("urn:oasis:names:tc:SAML:2.0:protocol",
                "Response").item(0);
        importedClonedResponse.setIdAttribute("ID", true);

        // validate the original response with the wrapped assertion
        isValid = XMLSignatureUtil.validate(signedDoc, publicKey);

        assertTrue(isValid);
    }

    private ResponseType createSignedResponse() throws ConfigurationException {
        IssuerInfoHolder issuerInfo = new IssuerInfoHolder("testIssuer");
        String id = IDGenerator.create("ID_");

        SAML2Response response = new SAML2Response();

        String authnContextDeclRef = JBossSAMLURIConstants.AC_PASSWORD_PROTECTED_TRANSPORT.get();

        AuthnStatementType authnStatement = response.createAuthnStatement(authnContextDeclRef, XMLTimeUtil.getIssueInstant());

        // Create an assertion
        AssertionType assertion = response.createAssertion(id, issuerInfo.getIssuer());

        SubjectType subject = new SubjectType();

        subject.setSubType(new STSubType());
        NameIDType nameId = new NameIDType();
        nameId.setValue("jduke");
        subject.getSubType().addBaseID(nameId);

        assertion.setSubject(subject);
        assertion.addStatement(authnStatement);

        AttributeStatementType attributes = new AttributeStatementType();

        AttributeType attribute = new AttributeType("Role");

        attribute.addAttributeValue("Manager");

        attributes.addAttribute(new ASTChoiceType(attribute));

        assertion.addStatement(attributes);

        id = IDGenerator.create("ID_"); // regenerate

        return response.createResponseType(id, issuerInfo, assertion);
    }

}
TOP

Related Classes of org.picketlink.test.identity.federation.core.saml.v2.SAMLAssertionWrappingAttackTestCase

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.