/* * eID Applet Project. * Copyright (C) 2008-2009 FedICT. * Copyright (C) 2014 e-Contract.be BVBA. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version * 3.0 as published by the Free Software Foundation. * * 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, see * http://www.gnu.org/licenses/. */ package test.be.fedict.eid.applet; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.awt.Component; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.math.BigInteger; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.URL; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.RSAKeyGenParameterSpec; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import javax.servlet.http.HttpSession; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.X509Principal; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.joda.time.DateTime; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mortbay.jetty.SessionManager; import org.mortbay.jetty.security.SslSocketConnector; import org.mortbay.jetty.servlet.HashSessionManager; import org.mortbay.jetty.servlet.ServletHolder; import org.mortbay.jetty.servlet.SessionHandler; import org.mortbay.jetty.testing.ServletTester; import be.fedict.eid.applet.Applet; import be.fedict.eid.applet.Controller; import be.fedict.eid.applet.Messages; import be.fedict.eid.applet.Runtime; import be.fedict.eid.applet.Status; import be.fedict.eid.applet.View; import be.fedict.eid.applet.service.AppletServiceServlet; import be.fedict.eid.applet.service.Identity; import be.fedict.eid.applet.service.spi.AuthenticationService; /** * Integration tests for the eID Applet controller component and the eID Applet * Service. * * @author Frank Cornelis * */ public class ControllerTest { private static final Log LOG = LogFactory.getLog(ControllerTest.class); private ServletTester servletTester; private String sslLocation; private KeyPair generateKeyPair() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); SecureRandom random = new SecureRandom(); keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4), random); KeyPair keyPair = keyPairGenerator.generateKeyPair(); return keyPair; } private SubjectKeyIdentifier createSubjectKeyId(PublicKey publicKey) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded()); SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject()); return new SubjectKeyIdentifier(info); } private AuthorityKeyIdentifier createAuthorityKeyId(PublicKey publicKey) throws IOException { ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded()); SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject()); return new AuthorityKeyIdentifier(info); } private void persistKey(File pkcs12keyStore, PrivateKey privateKey, X509Certificate certificate, char[] keyStorePassword, char[] keyEntryPassword) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { KeyStore keyStore = KeyStore.getInstance("pkcs12"); keyStore.load(null, keyStorePassword); keyStore.setKeyEntry("default", privateKey, keyEntryPassword, new Certificate[] { certificate }); FileOutputStream keyStoreOut = new FileOutputStream(pkcs12keyStore); keyStore.store(keyStoreOut, keyStorePassword); keyStoreOut.close(); } private X509Certificate generateSelfSignedCertificate(KeyPair keyPair, String subjectDn, DateTime notBefore, DateTime notAfter) throws IOException, InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, SignatureException, CertificateException { PublicKey subjectPublicKey = keyPair.getPublic(); PrivateKey issuerPrivateKey = keyPair.getPrivate(); String signatureAlgorithm = "SHA1WithRSAEncryption"; X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator(); certificateGenerator.reset(); certificateGenerator.setPublicKey(subjectPublicKey); certificateGenerator.setSignatureAlgorithm(signatureAlgorithm); certificateGenerator.setNotBefore(notBefore.toDate()); certificateGenerator.setNotAfter(notAfter.toDate()); X509Principal issuerDN = new X509Principal(subjectDn); certificateGenerator.setIssuerDN(issuerDN); certificateGenerator.setSubjectDN(new X509Principal(subjectDn)); certificateGenerator.setSerialNumber(new BigInteger(128, new SecureRandom())); certificateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false, createSubjectKeyId(subjectPublicKey)); PublicKey issuerPublicKey; issuerPublicKey = subjectPublicKey; certificateGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, createAuthorityKeyId(issuerPublicKey)); certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(true)); X509Certificate certificate; certificate = certificateGenerator.generate(issuerPrivateKey); /* * Next certificate factory trick is needed to make sure that the * certificate delivered to the caller is provided by the default * security provider instead of BouncyCastle. If we don't do this trick * we might run into trouble when trying to use the CertPath validator. */ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); certificate = (X509Certificate) certificateFactory .generateCertificate(new ByteArrayInputStream(certificate.getEncoded())); return certificate; } private static int getFreePort() throws Exception { ServerSocket serverSocket = new ServerSocket(0); int port = serverSocket.getLocalPort(); serverSocket.close(); return port; } private ServletHolder servletHolder; private X509Certificate certificate; @Before public void setUp() throws Exception { this.servletTester = new ServletTester(); this.servletHolder = this.servletTester.addServlet(AppletServiceServlet.class, "/"); Security.addProvider(new BouncyCastleProvider()); KeyPair keyPair = generateKeyPair(); DateTime notBefore = new DateTime(); DateTime notAfter = notBefore.plusMonths(1); this.certificate = generateSelfSignedCertificate(keyPair, "CN=localhost", notBefore, notAfter); File tmpP12File = File.createTempFile("ssl-", ".p12"); LOG.debug("p12 file: " + tmpP12File.getAbsolutePath()); persistKey(tmpP12File, keyPair.getPrivate(), this.certificate, "secret".toCharArray(), "secret".toCharArray()); SslSocketConnector sslSocketConnector = new SslSocketConnector(); sslSocketConnector.setKeystore(tmpP12File.getAbsolutePath()); sslSocketConnector.setTruststore(tmpP12File.getAbsolutePath()); sslSocketConnector.setTruststoreType("pkcs12"); sslSocketConnector.setKeystoreType("pkcs12"); sslSocketConnector.setPassword("secret"); sslSocketConnector.setKeyPassword("secret"); sslSocketConnector.setTrustPassword("secret"); sslSocketConnector.setMaxIdleTime(30000); int sslPort = getFreePort(); sslSocketConnector.setPort(sslPort); this.servletTester.getContext().getServer().addConnector(sslSocketConnector); this.sslLocation = "https://localhost:" + sslPort + "/"; this.servletTester.start(); SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManager trustManager = new TestTrustManager(this.certificate); sslContext.init(null, new TrustManager[] { trustManager }, null); SSLContext.setDefault(sslContext); } private static class TestTrustManager implements X509TrustManager { private final X509Certificate serverCertificate; public TestTrustManager(X509Certificate serverCertificate) { this.serverCertificate = serverCertificate; } public void checkClientTrusted(X509Certificate[] chain, String authnType) throws CertificateException { throw new CertificateException("not implemented"); } public void checkServerTrusted(X509Certificate[] chain, String authnType) throws CertificateException { if (false == this.serverCertificate.equals(chain[0])) { throw new CertificateException("server certificate not trusted"); } } public X509Certificate[] getAcceptedIssuers() { return null; } } @After public void tearDown() throws Exception { this.servletTester.stop(); } private class TestRuntime implements Runtime { @Override public URL getDocumentBase() { LOG.debug("getDocumentBase()"); try { return new URL(ControllerTest.this.sslLocation); } catch (MalformedURLException e) { throw new RuntimeException("URL error"); } } @Override public String getParameter(String name) { LOG.debug("getParameter(\"" + name + "\")"); if ("AppletService".equals(name)) { return ControllerTest.this.sslLocation; } return null; } @Override public void gotoTargetPage() { LOG.debug("gotoTargetPage()"); } @Override public Applet getApplet() { return null; } @Override public boolean gotoCancelPage() { return false; } @Override public void gotoAuthorizationErrorPage() { LOG.debug("gotoAuthorizationErrorPage"); } } private static class TestView implements View { private Messages messages = new Messages(Locale.getDefault()); @Override public void addDetailMessage(String detailMessage) { LOG.debug("detail message: " + detailMessage); } @Override public Component getParentComponent() { LOG.debug("getParentComponent()"); return null; } @Override public boolean privacyQuestion(boolean includeAddress, boolean includePhoto, String identityDataUsage) { LOG.debug("privacyQuestion()"); return true; } @Override public void setStatusMessage(Status status, Messages.MESSAGE_ID messageId) { String statusMessage = this.messages.getMessage(messageId); LOG.debug("status message: " + status + ": " + statusMessage); if (Status.ERROR == status) { throw new RuntimeException("status ERROR received"); } } @Override public void increaseProgress() { } @Override public void resetProgress(int max) { } @Override public void setProgressIndeterminate() { } @Override public void confirmAuthenticationSignature(String message) { } @Override public int confirmSigning(String description, String digestAlgo) { return 0; } } @Test public void controllerIdentification() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); // operate controller.run(); // verify LOG.debug("verify..."); SessionHandler sessionHandler = this.servletTester.getContext().getSessionHandler(); SessionManager sessionManager = sessionHandler.getSessionManager(); LOG.debug("session manager type: " + sessionManager.getClass().getName()); HashSessionManager hashSessionManager = (HashSessionManager) sessionManager; LOG.debug("# sessions: " + hashSessionManager.getSessions()); assertEquals(1, hashSessionManager.getSessions()); Map<String, HttpSession> sessionMap = hashSessionManager.getSessionMap(); LOG.debug("session map: " + sessionMap); Entry<String, HttpSession> sessionEntry = sessionMap.entrySet().iterator().next(); HttpSession httpSession = sessionEntry.getValue(); assertNotNull(httpSession.getAttribute("eid")); Identity identity = (Identity) httpSession.getAttribute("eid.identity"); assertNotNull(identity); assertNotNull(identity.name); LOG.debug("name: " + identity.name); LOG.debug("document type: " + identity.getDocumentType()); LOG.debug("duplicate: " + identity.getDuplicate()); assertNull(httpSession.getAttribute("eid.identifier")); assertNull(httpSession.getAttribute("eid.address")); assertNull(httpSession.getAttribute("eid.photo")); } @Test public void controllerIdentificationWithAddressAndPhoto() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); this.servletHolder.setInitParameter("IncludeAddress", "true"); this.servletHolder.setInitParameter("IncludePhoto", "true"); // operate controller.run(); // verify LOG.debug("verify..."); SessionHandler sessionHandler = this.servletTester.getContext().getSessionHandler(); SessionManager sessionManager = sessionHandler.getSessionManager(); LOG.debug("session manager type: " + sessionManager.getClass().getName()); HashSessionManager hashSessionManager = (HashSessionManager) sessionManager; LOG.debug("# sessions: " + hashSessionManager.getSessions()); assertEquals(1, hashSessionManager.getSessions()); Map<String, HttpSession> sessionMap = hashSessionManager.getSessionMap(); LOG.debug("session map: " + sessionMap); Entry<String, HttpSession> sessionEntry = sessionMap.entrySet().iterator().next(); HttpSession httpSession = sessionEntry.getValue(); assertNotNull(httpSession.getAttribute("eid")); Identity identity = (Identity) httpSession.getAttribute("eid.identity"); assertNotNull(identity); assertNotNull(identity.name); LOG.debug("name: " + identity.name); LOG.debug("nationality: " + identity.getNationality()); LOG.debug("national number: " + identity.getNationalNumber()); assertNull(httpSession.getAttribute("eid.identifier")); assertNotNull(httpSession.getAttribute("eid.address")); assertNotNull(httpSession.getAttribute("eid.photo")); } @Test public void controllerKioskMode() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); this.servletHolder.setInitParameter("Kiosk", "true"); // operate controller.run(); // verify LOG.debug("verify..."); } public static class TestAuthenticationService implements AuthenticationService { private static boolean called; @Override public void validateCertificateChain(List<X509Certificate> certificateChain) throws SecurityException { called = true; } } @Test public void controllerAuthentication() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); this.servletHolder.setInitParameter("AuthenticationServiceClass", TestAuthenticationService.class.getName()); this.servletHolder.setInitParameter("Logoff", "true"); // operate controller.run(); // verify LOG.debug("verify..."); SessionHandler sessionHandler = this.servletTester.getContext().getSessionHandler(); SessionManager sessionManager = sessionHandler.getSessionManager(); LOG.debug("session manager type: " + sessionManager.getClass().getName()); HashSessionManager hashSessionManager = (HashSessionManager) sessionManager; LOG.debug("# sessions: " + hashSessionManager.getSessions()); assertEquals(1, hashSessionManager.getSessions()); Map<String, HttpSession> sessionMap = hashSessionManager.getSessionMap(); LOG.debug("session map: " + sessionMap); Entry<String, HttpSession> sessionEntry = sessionMap.entrySet().iterator().next(); HttpSession httpSession = sessionEntry.getValue(); assertNotNull(httpSession.getAttribute("eid")); assertNull(httpSession.getAttribute("eid.identity")); assertNull(httpSession.getAttribute("eid.address")); assertNull(httpSession.getAttribute("eid.photo")); String identifier = (String) httpSession.getAttribute("eid.identifier"); assertNotNull(identifier); LOG.debug("identifier: " + identifier); assertTrue(TestAuthenticationService.called); } @Test public void testAuthnSessionIdChannelBinding() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); this.servletHolder.setInitParameter("AuthenticationServiceClass", TestAuthenticationService.class.getName()); this.servletHolder.setInitParameter("Logoff", "true"); this.servletHolder.setInitParameter("SessionIdChannelBinding", "true"); // operate controller.run(); // verify LOG.debug("verify..."); SessionHandler sessionHandler = this.servletTester.getContext().getSessionHandler(); SessionManager sessionManager = sessionHandler.getSessionManager(); LOG.debug("session manager type: " + sessionManager.getClass().getName()); HashSessionManager hashSessionManager = (HashSessionManager) sessionManager; LOG.debug("# sessions: " + hashSessionManager.getSessions()); assertEquals(1, hashSessionManager.getSessions()); Map<String, HttpSession> sessionMap = hashSessionManager.getSessionMap(); LOG.debug("session map: " + sessionMap); Entry<String, HttpSession> sessionEntry = sessionMap.entrySet().iterator().next(); HttpSession httpSession = sessionEntry.getValue(); assertNotNull(httpSession.getAttribute("eid")); assertNull(httpSession.getAttribute("eid.identity")); assertNull(httpSession.getAttribute("eid.address")); assertNull(httpSession.getAttribute("eid.photo")); String identifier = (String) httpSession.getAttribute("eid.identifier"); assertNotNull(identifier); LOG.debug("identifier: " + identifier); assertTrue(TestAuthenticationService.called); } @Test public void testAuthnServerCertificateChannelBinding() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); this.servletHolder.setInitParameter("AuthenticationServiceClass", TestAuthenticationService.class.getName()); this.servletHolder.setInitParameter("Logoff", "true"); File tmpCertFile = File.createTempFile("ssl-server-cert-", ".crt"); FileUtils.writeByteArrayToFile(tmpCertFile, this.certificate.getEncoded()); this.servletHolder.setInitParameter("ChannelBindingServerCertificate", tmpCertFile.toString()); // operate controller.run(); // verify LOG.debug("verify..."); SessionHandler sessionHandler = this.servletTester.getContext().getSessionHandler(); SessionManager sessionManager = sessionHandler.getSessionManager(); LOG.debug("session manager type: " + sessionManager.getClass().getName()); HashSessionManager hashSessionManager = (HashSessionManager) sessionManager; LOG.debug("# sessions: " + hashSessionManager.getSessions()); assertEquals(1, hashSessionManager.getSessions()); Map<String, HttpSession> sessionMap = hashSessionManager.getSessionMap(); LOG.debug("session map: " + sessionMap); Entry<String, HttpSession> sessionEntry = sessionMap.entrySet().iterator().next(); HttpSession httpSession = sessionEntry.getValue(); assertNotNull(httpSession.getAttribute("eid")); assertNull(httpSession.getAttribute("eid.identity")); assertNull(httpSession.getAttribute("eid.address")); assertNull(httpSession.getAttribute("eid.photo")); String identifier = (String) httpSession.getAttribute("eid.identifier"); assertNotNull(identifier); LOG.debug("identifier: " + identifier); assertTrue(TestAuthenticationService.called); } @Test public void testAuthnHybridChannelBinding() throws Exception { // setup Messages messages = new Messages(Locale.getDefault()); Runtime runtime = new TestRuntime(); View view = new TestView(); Controller controller = new Controller(view, runtime, messages); // make sure that the session cookies are passed during conversations CookieManager cookieManager = new CookieManager(); cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); CookieHandler.setDefault(cookieManager); this.servletHolder.setInitParameter("AuthenticationServiceClass", TestAuthenticationService.class.getName()); this.servletHolder.setInitParameter("Logoff", "true"); File tmpCertFile = File.createTempFile("ssl-server-cert-", ".crt"); FileUtils.writeByteArrayToFile(tmpCertFile, this.certificate.getEncoded()); this.servletHolder.setInitParameter("ChannelBindingServerCertificate", tmpCertFile.toString()); this.servletHolder.setInitParameter("SessionIdChannelBinding", "true"); // operate controller.run(); // verify LOG.debug("verify..."); SessionHandler sessionHandler = this.servletTester.getContext().getSessionHandler(); SessionManager sessionManager = sessionHandler.getSessionManager(); LOG.debug("session manager type: " + sessionManager.getClass().getName()); HashSessionManager hashSessionManager = (HashSessionManager) sessionManager; LOG.debug("# sessions: " + hashSessionManager.getSessions()); assertEquals(1, hashSessionManager.getSessions()); Map<String, HttpSession> sessionMap = hashSessionManager.getSessionMap(); LOG.debug("session map: " + sessionMap); Entry<String, HttpSession> sessionEntry = sessionMap.entrySet().iterator().next(); HttpSession httpSession = sessionEntry.getValue(); assertNotNull(httpSession.getAttribute("eid")); assertNull(httpSession.getAttribute("eid.identity")); assertNull(httpSession.getAttribute("eid.address")); assertNull(httpSession.getAttribute("eid.photo")); String identifier = (String) httpSession.getAttribute("eid.identifier"); assertNotNull(identifier); LOG.debug("identifier: " + identifier); assertTrue(TestAuthenticationService.called); } }