diff --git a/README.md b/README.md index b270e5f..84ec16c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ OIOSAML.Java is a SAML 2.0 framework for Java, intended for use with NemLog-in3, but it can also be used as a generic SAML framework for Java. -The framework supports the OIO SAML 3.0 profile and can generate `AuthnRequest`s and perform validation on SAML Assertions according to this profile. It works with **Java 11+**. +The framework supports the OIO SAML 3.0 profile and can generate `AuthnRequest`s and perform validation on SAML Assertions according to this profile. It works with **Java 11+** and requires a **Servlet 3.1** or later container. This document covers how to use and configure the OIOSAML.Java 3.x framework. @@ -323,10 +323,11 @@ Finally, there are three folders, each containing identical JSP files. These fil Compiling and running the demo application is performed with Maven like this: ```bash -$ mvn clean install tomcat7:run-war +$ mvn clean install +$ mvn -pl demo jetty:run-war ``` -> Note that Tomcat 7 performs some validation on class files during startup, which has some issues with JAXB. This results in warnings that can safely be ignored. Tomcat 8 does not have these issues. +> The demo runs on Jetty 9.4, which implements Servlet 3.1 as the library requires. It listens on HTTPS only, using the demo keystore in `misc/ssl-demo.pfx`; the connector is configured in `demo/src/main/jetty/jetty-https.xml`. Once the application is running, it can be accessed at . diff --git a/demo/pom.xml b/demo/pom.xml index 6b591d4..febee05 100644 --- a/demo/pom.xml +++ b/demo/pom.xml @@ -13,16 +13,27 @@ + - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 + org.eclipse.jetty + jetty-maven-plugin + 9.4.53.v20231009 - 8080 - 8443 - ${project.basedir}/../misc/ssl-demo.pfx - Test1234 - /oiosaml3-demo.java + + /oiosaml3-demo.java + + ${project.basedir}/src/main/jetty/jetty-https.xml + + + demo.keystore.path + ${project.basedir}/../misc/ssl-demo.pfx + + + demo.keystore.password + Test1234 + + @@ -46,7 +57,7 @@ javax.servlet javax.servlet-api - 3.0.1 + 3.1.0 provided diff --git a/demo/src/main/jetty/jetty-https.xml b/demo/src/main/jetty/jetty-https.xml new file mode 100644 index 0000000..73d9f97 --- /dev/null +++ b/demo/src/main/jetty/jetty-https.xml @@ -0,0 +1,52 @@ + + + + + + + + https + 8443 + + + + + + + + + + PKCS12 + + + + + + + + + + + + + http/1.1 + + + + + + + + + + + 8443 + + + + + diff --git a/oiosaml/pom.xml b/oiosaml/pom.xml index 79cee13..abdad86 100644 --- a/oiosaml/pom.xml +++ b/oiosaml/pom.xml @@ -150,7 +150,7 @@ javax.servlet javax.servlet-api - 3.0.1 + 3.1.0 provided diff --git a/oiosaml/src/main/java/dk/gov/oio/saml/servlet/AssertionHandler.java b/oiosaml/src/main/java/dk/gov/oio/saml/servlet/AssertionHandler.java index 2a31227..175f14c 100644 --- a/oiosaml/src/main/java/dk/gov/oio/saml/servlet/AssertionHandler.java +++ b/oiosaml/src/main/java/dk/gov/oio/saml/servlet/AssertionHandler.java @@ -142,6 +142,9 @@ public void handlePost(HttpServletRequest httpServletRequest, HttpServletRespons AssertionWrapper assertionWrapper = new AssertionWrapper(assertion); + // The session is authenticated from here, so it must not keep the id it had before login + rotateSessionId(httpServletRequest, sessionHandler, authnRequest); + sessionHandler.storeAssertion(session, assertionWrapper); OIOSAML3Service.getAuditService().auditLog(AuditRequestUtil @@ -159,4 +162,17 @@ public void handlePost(HttpServletRequest httpServletRequest, HttpServletRespons httpServletResponse.sendRedirect(url); } + + /** + * Give the session a new id now that it carries an authenticated user, so an id planted in the browser + * before login cannot be used afterwards. + * + *

Session state is keyed on the container session id, so the in-flight AuthnRequest is stored again + * under the new id.

+ */ + private void rotateSessionId(HttpServletRequest httpServletRequest, SessionHandler sessionHandler, AuthnRequestWrapper authnRequest) throws InternalException { + httpServletRequest.changeSessionId(); + + sessionHandler.storeAuthnRequest(httpServletRequest.getSession(), authnRequest); + } } diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/servlet/AssertionHandlerTest.java b/oiosaml/src/test/java/dk/gov/oio/saml/servlet/AssertionHandlerTest.java index 05f30c4..4b1041d 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/servlet/AssertionHandlerTest.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/servlet/AssertionHandlerTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mockito; import org.opensaml.core.xml.util.XMLObjectSupport; import org.opensaml.messaging.context.MessageContext; @@ -85,6 +86,47 @@ public void testPostWithValidAssertion() throws Exception { Assertions.assertEquals(assertionWrapperArgumentCaptor.getValue().getNsisLevel(), NSISLevel.SUBSTANTIAL); } + @DisplayName("Test that handler changes the session id when the assertion is accepted") + @Test + public void testPostWithValidAssertionChangesSessionId() throws Exception { + // Create MessageContext, Response and Assertion + String nameID = "https://data.gov.dk/model/core/eid/person/uuid/37a5a1aa-67ce-4f70-b7c0-b8e678d585f7"; + String inResponseToId = UUID.randomUUID().toString(); + MessageContext messageContext = IdpUtil.createMessageWithAssertion(true, true, true, nameID, TestConstants.SP_ENTITY_ID, TestConstants.SP_ASSERTION_CONSUMER_URL, inResponseToId); + + Element marshalledMessage = XMLObjectSupport.marshall(messageContext.getMessage()); + String base64EncodedMessage = Base64Support.encode(SerializeSupport.nodeToString(marshalledMessage).getBytes("UTF-8"), Base64Support.UNCHUNKED); + + // Create AuthnRequest + AuthnRequestService authnRequestService = new AuthnRequestService(); + MessageContext authnRequestMessageContext = authnRequestService.createMessageWithAuthnRequest(false, false, NSISLevel.SUBSTANTIAL, null, null); + AuthnRequest authnRequest = (AuthnRequest) authnRequestMessageContext.getMessage(); + authnRequest.setID(inResponseToId); + AuthnRequestWrapper wrapper = new AuthnRequestWrapper(authnRequest, NSISLevel.SUBSTANTIAL, ""); + + SessionHandler sessionHandler = OIOSAML3Service.getSessionHandlerFactory().getHandler(); + Mockito.when(sessionHandler.getAuthnRequest(session)).thenReturn(wrapper); + + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRequestURL()).thenReturn(new StringBuffer(TestConstants.SP_ASSERTION_CONSUMER_URL)); + Mockito.when(request.getSession()).thenReturn(session); + Mockito.when(request.getMethod()).thenReturn("POST"); + Mockito.when(request.getParameter("RelayState")).thenReturn(null); + Mockito.when(request.getParameter("SAMLResponse")).thenReturn(base64EncodedMessage); + + HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + + new AssertionHandler().handlePost(request, response); + + Mockito.verify(request).changeSessionId(); + + // Session state is keyed on the container session id, so the AuthnRequest follows the session + InOrder inOrder = Mockito.inOrder(request, sessionHandler); + inOrder.verify(request).changeSessionId(); + inOrder.verify(sessionHandler).storeAuthnRequest(session, wrapper); + inOrder.verify(sessionHandler).storeAssertion(Mockito.eq(session), Mockito.any(AssertionWrapper.class)); + } + @DisplayName("Test that handler will reject am invalid assertion") @Test public void testPostWithInvalidAssertion() throws Exception { diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutRequestHandlerTest.java b/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutRequestHandlerTest.java index 3399499..ccac42f 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutRequestHandlerTest.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutRequestHandlerTest.java @@ -7,6 +7,7 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; +import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -196,6 +197,21 @@ public void testIdPSOAPLogoutRequestWhenLoggedIn() throws Exception { public int read() throws IOException { return inputStream.read(); } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + //Do nothing + } }); // Mock DummyOutputStream @@ -390,6 +406,21 @@ public void testSOAPLogoutRequestWhenNotLoggedIn() throws Exception { public int read() throws IOException { return inputStream.read(); } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + //Do nothing + } }); // Mock DummyOutputStream diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutResponseHandlerTest.java b/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutResponseHandlerTest.java index d5fca57..fa208f3 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutResponseHandlerTest.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/servlet/LogoutResponseHandlerTest.java @@ -7,6 +7,7 @@ import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -110,5 +111,15 @@ private class DummyOutputStream extends ServletOutputStream { public void write(int i) throws IOException { //Do nothing } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + //Do nothing + } } } diff --git a/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java b/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java index 0953299..8b3b369 100644 --- a/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java +++ b/oiosaml/src/test/java/dk/gov/oio/saml/util/TestConstants.java @@ -151,8 +151,11 @@ public class TestConstants { // Value of the specVersion attribute in the OIOSAML Web SSO profiles, see [OIO-ALG-01] chapter 6 public static final String SPEC_VERSION_OIOSAML_30 = "OIO-SAML-3.0"; public static final String SPEC_VERSION_OIOSAML_40 = "https://data.gov.dk/saml/profile/oio/4.0.0/"; - - public static final String VALID_CERTIFICATE = "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"; + + // The DevTest4 NemLog-in IdP signing certificate, taken from + // demo/src/main/resources/test-devtest4-idp-metadata.xml. The revocation tests validate against the + // live test CA, so this must be a certificate the CA still knows and has not expired. + public static final String VALID_CERTIFICATE = "MIIGjDCCBMCgAwIBAgIUaGLv8eOYx3cubJyI3ymai6tAXCowQQYJKoZIhvcNAQEKMDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEgMGsxLTArBgNVBAMMJERlbiBEYW5za2UgU3RhdCBPQ0VTIHVkc3RlZGVuZGUtQ0EgMTETMBEGA1UECwwKVGVzdCAtIGN0aTEYMBYGA1UECgwPRGVuIERhbnNrZSBTdGF0MQswCQYDVQQGEwJESzAeFw0yNTA4MjAxMzQxNDBaFw0yODA4MTkxMzQxMzlaMIGhMR0wGwYDVQQDDBROZW1Mb2ctaW4gSWRQIC0gVGVzdDE3MDUGA1UEBRMuVUk6REstTzpHOmEwNDBmZTI2LWNlNzgtNDMyOC1hYzBlLTI5ZWViMTJmNDZkZjEhMB8GA1UECgwYRGlnaXRhbGlzZXJpbmdzc3R5cmVsc2VuMRcwFQYDVQRhDA5OVFJESy0zNDA1MTE3ODELMAkGA1UEBhMCREswggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCVUzcmIfK0NbJEmAQ7kFOTmXs1mkSHF0LAEFq7Ne8vpNz2H+0F5IyWCwsBi3SByPTAEIClbIT6rLFBoMmIwjfRmAXd68zW7NYwR0N8PcR/hvtoCR1tW2JuKhQlKbWlaYLRvvvS3r5giIjHgEXpmv42caB76eYE3J2qWPaACOIRMXky1vCmbQpEwqJr0S9+QDtMYh5ypVk1zX9rrY2TBbienruISBF1090uuzEQZtfiq9nZaBQAJZFlcYLftepSZXW4QgJjWH6BhMrd4nvcz17WeabG3AhhKHvOUDukJgKMzQd/OcS1tY13P7soMeT8D8h5bCnNDoCfqM/5mgxgxZZXnjJhzOCY+MdrXXBRST2uQLNg0gl/LgqmEKk0izQaMtNauR+5fFHSJMN23Ga6fAmHMlyEj+5JhGeScRvIBp9tPERPhRF0MdEjobbcweMRWqurJYsdo9cIHwrL8WIgQGhQRUrKBu9VPZQTtVs3dn3cxZIJ3axu53hWEKRwqw4UoasCAwEAAaOCAYcwggGDMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUfyif2XGZQuJ159c1di5NCCVtdl4wewYIKwYBBQUHAQEEbzBtMEMGCCsGAQUFBzAChjdodHRwOi8vY2ExLmN0aS1nb3YuZGsvb2Nlcy9pc3N1aW5nLzEvY2FjZXJ0L2lzc3VpbmcuY2VyMCYGCCsGAQUFBzABhhpodHRwOi8vY2ExLmN0aS1nb3YuZGsvb2NzcDAiBgNVHSAEGzAZMAgGBgQAj3oBATANBgsqgVCBKQEBAQMHATA7BggrBgEFBQcBAwQvMC0wKwYIKwYBBQUHCwIwHwYHBACL7EkBAjAUhhJodHRwczovL3VpZC5nb3YuZGswRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NhMS5jdGktZ292LmRrL29jZXMvaXNzdWluZy8xL2NybC9pc3N1aW5nLmNybDAdBgNVHQ4EFgQU9KSlGZd1HK7yFmo+zohr4oq6LLwwDgYDVR0PAQH/BAQDAgWgMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAIBBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAIBBQCiAwIBIAOCAYEAPt4QGIebeY3idMN6xPUQUBwYeiIjhtxqo2O9BHELEQ8Vp9fuBiExT286i1nNbd6TBgFFuUPh2kuwUqAj4YfaG7aSiCToJrTuaxBGlMI9nci2l5VDJ/1/lVVT/OkMLgR9xVXWSi8KFT8U9S+E1THE+G1YSNnt50yRDmqGNtG1XcaXQeu2Q4IcjMc2nf7NcGzGwidsKnBv0nNXwoYnh2ZLWPjQAgQzM3EYdZmEqaCrscYDFRbh4/T3TYjOo64N4rYZWTR+4/poz/29tyfVBWnr3rLuJH2q0kaiFVSXRHXoqOpCDUTVwsULShjmAHcPruQK7mRK1NQcwJtm0HQNWKUxizqc1pA38/9tU2vkQv+OxEEwHnCRK4XkLCJ8ZKCV5+c7AehhTLtDHqsA93+8afgrTLe+kWEO+xRgOJxKV9TnqkvnYiG8BtCOcb4+6yUMGAoOPULVKywIzutRsHYjZptCkuggBqNztSFU/DUd6SpSxf1MGfeh4SC4yPNvKQ+5fo7S"; public static final String REVOKED_CERTIFICATE = "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"; }