/*
* eID Applet Project.
* Copyright (C) 2008-2009 FedICT.
*
* 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.unit.be.fedict.eid.applet.service;
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.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.ServerSocket;
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.NoSuchProviderException;
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 javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.io.IOUtils;
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.security.SslSocketConnector;
import org.mortbay.jetty.testing.ServletTester;
import be.fedict.eid.applet.service.Address;
import be.fedict.eid.applet.service.AppletServiceServlet;
import be.fedict.eid.applet.service.Identity;
import be.fedict.eid.applet.shared.AppletProtocolMessageCatalog;
import be.fedict.eid.applet.shared.HelloMessage;
import be.fedict.eid.applet.shared.IdentificationRequestMessage;
import be.fedict.eid.applet.shared.IdentityDataMessage;
import be.fedict.eid.applet.shared.protocol.Transport;
import be.fedict.eid.applet.shared.protocol.Unmarshaller;
public class AppletServiceServletTest {
private static final Log LOG = LogFactory.getLog(AppletServiceServletTest.class);
private ServletTester servletTester;
private String location;
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, NoSuchProviderException {
KeyStore keyStore = KeyStore.getInstance("pkcs12", BouncyCastleProvider.PROVIDER_NAME);
keyStore.load(null, keyStorePassword);
LOG.debug("keystore security provider: " + keyStore.getProvider().getName());
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;
}
@Before
public void setUp() throws Exception {
this.servletTester = new ServletTester();
this.servletTester.addServlet(AppletServiceServlet.class, "/");
Security.addProvider(new BouncyCastleProvider());
KeyPair keyPair = generateKeyPair();
DateTime notBefore = new DateTime();
DateTime notAfter = notBefore.plusMonths(1);
X509Certificate certificate = generateSelfSignedCertificate(keyPair, "CN=localhost", notBefore, notAfter);
File tmpP12File = File.createTempFile("ssl-", ".p12");
LOG.debug("p12 file: " + tmpP12File.getAbsolutePath());
persistKey(tmpP12File, keyPair.getPrivate(), 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();
this.location = this.servletTester.createSocketConnector(true);
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager trustManager = new TestTrustManager(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();
}
@Test
public void get() throws Exception {
// setup
LOG.debug("URL: " + this.location);
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod(this.location);
// operate
int result = httpClient.executeMethod(getMethod);
// verify
assertEquals(HttpServletResponse.SC_OK, result);
String responseBody = getMethod.getResponseBodyAsString();
LOG.debug("Response body: " + responseBody);
assertTrue(responseBody.indexOf("applet service") != 1);
}
@Test
public void doPostRequiresSSL() throws Exception {
// setup
LOG.debug("URL: " + this.location);
HttpClient httpClient = new HttpClient();
PostMethod postMethod = new PostMethod(this.location);
// operate
int result = httpClient.executeMethod(postMethod);
// verify
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, result);
}
// @Test
public void sslPostIdentityMessage() throws Exception {
// setup
byte[] idFile = IOUtils.toByteArray(AppletServiceServletTest.class.getResourceAsStream("/id-alice.tlv")); // XXX:
// expired
LOG.debug("SSL URL: " + this.sslLocation);
HttpClient httpClient = new HttpClient();
HelloMessage helloMessage = new HelloMessage();
PostMethod postMethod = new PostMethod(this.sslLocation);
PostMethodHttpTransmitter httpTransmitter = new PostMethodHttpTransmitter(postMethod);
Transport.transfer(helloMessage, httpTransmitter);
int result = httpClient.executeMethod(postMethod);
assertEquals(HttpServletResponse.SC_OK, result);
Header setCookieHeader = postMethod.getResponseHeader("Set-Cookie");
String setCookieValue = setCookieHeader.getValue();
int sessionIdIdx = setCookieValue.indexOf("JSESSIONID=") + "JESSSIONID=".length();
String sessionId = setCookieValue.substring(sessionIdIdx, setCookieValue.indexOf(";", sessionIdIdx));
LOG.debug("session id: " + sessionId);
postMethod = new PostMethod(this.sslLocation);
postMethod.addRequestHeader("X-AppletProtocol-Version", "1");
postMethod.addRequestHeader("X-AppletProtocol-Type", "IdentityDataMessage");
postMethod.addRequestHeader("X-AppletProtocol-IdentityFileSize", Integer.toString(idFile.length));
RequestEntity requestEntity = new ByteArrayRequestEntity(idFile);
postMethod.setRequestEntity(requestEntity);
// operate
result = httpClient.executeMethod(postMethod);
// verify
assertEquals(HttpServletResponse.SC_OK, result);
HttpSession httpSession = this.servletTester.getContext().getSessionHandler().getSessionManager()
.getHttpSession(sessionId);
Identity identity = (Identity) httpSession.getAttribute("eid.identity");
assertNotNull(identity);
assertEquals("Alice Geldigekaart2266", identity.firstName);
Address address = (Address) httpSession.getAttribute("eid.address");
assertNull(address);
}
// @Test
public void sslPostIdentityMessageViaTransport() throws Exception {
// setup
byte[] idFile = IOUtils.toByteArray(AppletServiceServletTest.class.getResourceAsStream("/id-alice.tlv")); // XXX:
// expired
LOG.debug("SSL URL: " + this.sslLocation);
HttpClient httpClient = new HttpClient();
HelloMessage helloMessage = new HelloMessage();
PostMethod postMethod = new PostMethod(this.sslLocation);
PostMethodHttpTransmitter httpTransmitter = new PostMethodHttpTransmitter(postMethod);
Transport.transfer(helloMessage, httpTransmitter);
int result = httpClient.executeMethod(postMethod);
assertEquals(HttpServletResponse.SC_OK, result);
Header setCookieHeader = postMethod.getResponseHeader("Set-Cookie");
String setCookieValue = setCookieHeader.getValue();
int sessionIdIdx = setCookieValue.indexOf("JSESSIONID=") + "JSESSIONID=".length();
String sessionId = setCookieValue.substring(sessionIdIdx, setCookieValue.indexOf(";", sessionIdIdx));
LOG.debug("session id: " + sessionId);
postMethod = new PostMethod(this.sslLocation);
httpTransmitter = new PostMethodHttpTransmitter(postMethod);
IdentityDataMessage identityDataMessage = new IdentityDataMessage();
identityDataMessage.identityFileSize = idFile.length;
identityDataMessage.body = idFile;
Transport.transfer(identityDataMessage, httpTransmitter);
// operate
result = httpClient.executeMethod(postMethod);
// verify
assertEquals(HttpServletResponse.SC_OK, result);
HttpSession httpSession = this.servletTester.getContext().getSessionHandler().getSessionManager()
.getHttpSession(sessionId);
Identity identity = (Identity) httpSession.getAttribute("eid.identity");
assertNotNull(identity);
assertEquals("Alice Geldigekaart2266", identity.firstName);
Address address = (Address) httpSession.getAttribute("eid.address");
assertNull(address);
}
@Test
public void helloMessage() throws Exception {
// setup
HttpClient httpClient = new HttpClient();
PostMethod postMethod = new PostMethod(this.sslLocation);
HelloMessage helloMessage = new HelloMessage();
PostMethodHttpTransmitter httpTransmitter = new PostMethodHttpTransmitter(postMethod);
Transport.transfer(helloMessage, httpTransmitter);
// operate
int result = httpClient.executeMethod(postMethod);
// verify
assertEquals(HttpServletResponse.SC_OK, result);
Unmarshaller unmarshaller = new Unmarshaller(new AppletProtocolMessageCatalog());
PostMethodHttpReceiver httpReceiver = new PostMethodHttpReceiver(postMethod);
Object resultMessageObject = unmarshaller.receive(httpReceiver);
assertTrue(resultMessageObject instanceof IdentificationRequestMessage);
}
}