/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., 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.web.saml.handlers; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.fail; import java.io.IOException; import java.io.StringWriter; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.stream.StreamResult; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request; import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response; import org.picketlink.identity.federation.core.config.IDPType; import org.picketlink.identity.federation.core.config.ProviderType; import org.picketlink.identity.federation.core.config.SPType; import org.picketlink.identity.federation.core.exceptions.ConfigurationException; import org.picketlink.identity.federation.core.exceptions.ParsingException; import org.picketlink.identity.federation.core.exceptions.ProcessingException; import org.picketlink.identity.federation.core.saml.v2.common.SAMLDocumentHolder; import org.picketlink.identity.federation.core.saml.v2.factories.SAML2HandlerChainFactory; import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerChainConfig; import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerConfig; import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerRequest; import org.picketlink.identity.federation.core.saml.v2.impl.DefaultSAML2HandlerResponse; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2Handler.HANDLER_TYPE; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerChain; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerChainConfig; import org.picketlink.identity.federation.core.saml.v2.util.DocumentUtil; import org.picketlink.identity.federation.core.sts.PicketLinkCoreSTS; import org.picketlink.identity.federation.core.util.TransformerUtil; import org.picketlink.identity.federation.saml.v2.assertion.NameIDType; import org.picketlink.identity.federation.saml.v2.protocol.AuthnRequestType; import org.picketlink.identity.federation.web.constants.GeneralConstants; import org.picketlink.identity.federation.web.core.HTTPContext; import org.picketlink.identity.federation.web.core.IdentityServer; import org.picketlink.identity.federation.web.handlers.saml2.SAML2AuthenticationHandler; import org.picketlink.identity.federation.web.handlers.saml2.SAML2EncryptionHandler; import org.picketlink.identity.federation.web.handlers.saml2.SAML2SignatureValidationHandler; import org.picketlink.identity.federation.web.roles.DefaultRoleValidator; import org.picketlink.test.identity.federation.web.mock.MockHttpServletRequest; import org.picketlink.test.identity.federation.web.mock.MockHttpServletResponse; import org.picketlink.test.identity.federation.web.mock.MockHttpSession; import org.picketlink.test.identity.federation.web.mock.MockServletContext; import org.w3c.dom.Document; /** * @author <a href="mailto:psilva@redhat.com">Pedro Silva</a> * */ public class SAML2EncryptionHandlerUnitTestCase { private static final String SERVICE_PROVIDER_URL = "http://service-provider.picketlink.org"; private static final String IDENTITY_PROVIDER_URL = "http://identity-provider.picketlink.org"; private MockServletContext servletContext = new MockServletContext(); private KeyPair keyPair; @Before public void onSetup() throws NoSuchAlgorithmException { // install the STS default configurations PicketLinkCoreSTS.instance().installDefaultConfiguration(); // register a identity server into the ServletContext servletContext.setAttribute(GeneralConstants.IDENTITY_SERVER, new IdentityServer()); // creates a random and temporary keypair for encryption and signing this.keyPair = KeyPairGenerator.getInstance("RSA").genKeyPair(); } /** * <p> * Try to issue an encrypted and signed SAML Assertion and to process it by the SP. * </p> * * @throws Exception */ @Test public void testEncryptAssertion() throws Exception { Document assertionDocument = issueSAMLAssertion(); assertNotNull(assertionDocument); System.out.println(prettyPrintDocument(assertionDocument).getBuffer().toString()); processSAMLAssertion(assertionDocument); } /** * <p> * This method asks the IDP for a new encrypted and signed SAML Assertion by sending an AuthnRequest. * </p> * * @return * @throws ConfigurationException * @throws ProcessingException */ private Document issueSAMLAssertion() throws ConfigurationException, ProcessingException { NameIDType issuerNameID = new NameIDType(); issuerNameID.setValue(IDENTITY_PROVIDER_URL); SAML2Request samlRequest = new SAML2Request(); AuthnRequestType authnRequestType = samlRequest.createAuthnRequestType("AuthnRequest_FAKE_ID", SERVICE_PROVIDER_URL, SERVICE_PROVIDER_URL, SERVICE_PROVIDER_URL); DefaultSAML2HandlerRequest handlerAuthnRequest = new DefaultSAML2HandlerRequest(new HTTPContext( new MockHttpServletRequest(new MockHttpSession(), "POST"), new MockHttpServletResponse(), servletContext), issuerNameID, new SAMLDocumentHolder(authnRequestType), HANDLER_TYPE.IDP); handlerAuthnRequest.addOption(GeneralConstants.SENDER_PUBLIC_KEY, getKeyPair().getPublic()); DefaultSAML2HandlerResponse handlerAuthnResponse = new DefaultSAML2HandlerResponse(); try { for (SAML2Handler handler : getIDPHandlerChain().handlers()) { handler.handleRequestType(handlerAuthnRequest, handlerAuthnResponse); } } catch (Exception e) { e.printStackTrace(); fail("Error while issuing encrypted and signed SAML Assertion."); } return handlerAuthnResponse.getResultingDocument(); } /** * <p> * Given the SAML Assertion, process it by the SP and make sure it can be decrypted and have its signature validated. * </p> * * @param assertionDocument * @throws ParsingException * @throws ConfigurationException * @throws ProcessingException * @throws NoSuchAlgorithmException */ private void processSAMLAssertion(Document assertionDocument) throws ParsingException, ConfigurationException, ProcessingException, NoSuchAlgorithmException { NameIDType issuerSPNameID = new NameIDType(); issuerSPNameID.setValue(IDENTITY_PROVIDER_URL); DefaultSAML2HandlerRequest handlerAssertionResponseRequest = new DefaultSAML2HandlerRequest(new HTTPContext( new MockHttpServletRequest(new MockHttpSession(), "POST"), new MockHttpServletResponse(), servletContext), issuerSPNameID, new SAMLDocumentHolder(new SAML2Response().getSAML2ObjectFromStream(DocumentUtil .getNodeAsStream(assertionDocument)), assertionDocument), HANDLER_TYPE.SP); handlerAssertionResponseRequest.addOption(GeneralConstants.DECRYPTING_KEY, getKeyPair().getPrivate()); handlerAssertionResponseRequest.addOption(GeneralConstants.SENDER_PUBLIC_KEY, getKeyPair().getPublic()); DefaultSAML2HandlerResponse handlerAssertionRequestResponse = new DefaultSAML2HandlerResponse(); try { for (SAML2Handler handler : getSPHandlerChain().handlers()) { handler.handleStatusResponseType(handlerAssertionResponseRequest, handlerAssertionRequestResponse); } } catch (Exception e) { e.printStackTrace(); fail("Error while processing the encrypted and signed SAML Assertion."); } } private SAML2HandlerChainConfig createHandlerChainConfig(ProviderType configuration) throws NoSuchAlgorithmException { SAML2HandlerChainConfig chainConfig = new DefaultSAML2HandlerChainConfig(); Map<String, Object> chainOptions = new HashMap<String, Object>(); chainOptions.put(GeneralConstants.CONFIGURATION, configuration); chainOptions.put(GeneralConstants.KEYPAIR, getKeyPair()); chainOptions.put(GeneralConstants.ROLE_VALIDATOR, new DefaultRoleValidator()); chainConfig.set(chainOptions); return chainConfig; } /** * <p> * Creates an return a random RSA {@link KeyPair} for testing. * </p> * * @return * @throws NoSuchAlgorithmException */ private KeyPair getKeyPair() { return this.keyPair; } private SAML2HandlerChain getIDPHandlerChain() throws ConfigurationException { List<SAML2Handler> handlers = new ArrayList<SAML2Handler>(); handlers.add(createAuthenticationHandler()); handlers.add(createEncryptionHandler()); IDPType idpType = new IDPType(); idpType.setEncrypt(true); return getHandlerChain(idpType, handlers); } private SAML2HandlerChain getSPHandlerChain() throws ConfigurationException { List<SAML2Handler> handlers = new ArrayList<SAML2Handler>(); handlers.add(createSignatureValidationHandler()); handlers.add(createAuthenticationHandler()); return getHandlerChain(new SPType(), handlers); } private SAML2HandlerChain getHandlerChain(ProviderType configuration, List<SAML2Handler> handlers) { SAML2HandlerChain handlerChain = null; try { SAML2HandlerChainConfig handlerChainConfig = createHandlerChainConfig(configuration); handlerChain = SAML2HandlerChainFactory.createChain(); for (SAML2Handler saml2Handler : handlers) { saml2Handler.initChainConfig(handlerChainConfig); handlerChain.add(saml2Handler); } } catch (Exception e) { e.printStackTrace(); Assert.fail("Handler chain configuration error."); } return handlerChain; } private SAML2EncryptionHandler createEncryptionHandler() throws ConfigurationException { SAML2EncryptionHandler handler = new SAML2EncryptionHandler(); DefaultSAML2HandlerConfig handlerConfig = new DefaultSAML2HandlerConfig(); handler.initHandlerConfig(handlerConfig); return handler; } private SAML2SignatureValidationHandler createSignatureValidationHandler() throws ConfigurationException { SAML2SignatureValidationHandler handler = new SAML2SignatureValidationHandler(); handler.initHandlerConfig(new DefaultSAML2HandlerConfig()); return handler; } private SAML2AuthenticationHandler createAuthenticationHandler() throws ConfigurationException { SAML2AuthenticationHandler handler = new SAML2AuthenticationHandler(); handler.initHandlerConfig(new DefaultSAML2HandlerConfig()); return handler; } private StringWriter prettyPrintDocument(Document authnRequestDocument) { StringWriter writer = new StringWriter(); try { Transformer transformer = TransformerUtil.getTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); transformer.transform(DocumentUtil.getXMLSource(authnRequestDocument), new StreamResult(writer)); } catch (Exception e) { e.printStackTrace(); fail("Error printing the document."); } finally { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } return writer; } }