Skip to content

Commit

Permalink
Make certificate configurable (issue #421)
Browse files Browse the repository at this point in the history
- Initial implementation, configurable via system properties
  • Loading branch information
marcelmay committed Feb 17, 2022
1 parent eea8ae6 commit 37a09af
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,46 @@
*/
package com.icegreen.greenmail.util;

import javax.net.ServerSocketFactory;
import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import javax.net.ServerSocketFactory;
import javax.net.ssl.*;


/**
* DummySSLServerSocketFactory - NOT SECURE
* <p/>
* Contains a preconfigured key store for convenience in testing by avoiding
* having to manually setup, install, and generate keystore / keys.
* having to manually set up, install, and generate keystore / keys.
* <p/>
* By default, the factory loads the resource <code>{@value #GREENMAIL_KEYSTORE_P12}</code> from classpath.
* A fallback to old <code>>{@value #GREENMAIL_KEYSTORE_JKS}</code> exists.
* <p/>
* The system property {@value #GREENMAIL_KEYSTORE_FILE_PROPERTY} can override the default keystore location.
* <p/>
* The system property {@value #GREENMAIL_KEYSTORE_PASSWORD_PROPERTY} can override the default keystore password.
* <p/>
* By default, the factory loads the resource <code>greenmail.p12</code> from classpath.
* A fallback to old <code>greenmail.jks</code> exists.
* <p>
* GreenMail provides the keystore resource. For customization, place your greenmail.p12 before greenmail JAR in the classpath.
*
* @author Wael Chatila
* @since Feb 2006
*/
public class DummySSLServerSocketFactory extends SSLServerSocketFactory {
public static final String GREENMAIL_KESTORE = "greenmail.p12";
public static final String GREENMAIL_JKS = "greenmail.jks";
public static final String GREENMAIL_KEYSTORE_FILE_PROPERTY = "greenmail.tls.keystore.file";
public static final String GREENMAIL_KEYSTORE_PASSWORD_PROPERTY = "greenmail.tls.keystore.password";
public static final String GREENMAIL_KEYSTORE_P12 = "greenmail.p12";
public static final String GREENMAIL_KEYSTORE_JKS = "greenmail.jks";
private final SSLServerSocketFactory factory;
private final KeyStore ks;

// From https://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SupportedCipherSuites
// From https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SupportedCipherSuites
static final String[] ANONYMOUS_CIPHERS = {
"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"
, "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5"
Expand All @@ -59,13 +67,8 @@ public DummySSLServerSocketFactory() {
String defaultAlg = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory km = KeyManagerFactory.getInstance(defaultAlg);
ks = KeyStore.getInstance(KeyStore.getDefaultType());
char[] pass = "changeit".toCharArray();
try {
loadKeystore(pass, GREENMAIL_KESTORE);
} catch (IllegalStateException ex) {
// Fallback to legacy JKS keystore
loadKeystore(pass, GREENMAIL_JKS);
}
char[] pass = System.getProperty(GREENMAIL_KEYSTORE_PASSWORD_PROPERTY,"changeit").toCharArray();
loadKeyStore(pass);
km.init(ks, pass);
KeyManager[] kma = km.getKeyManagers();
sslcontext.init(kma,
Expand All @@ -77,13 +80,38 @@ public DummySSLServerSocketFactory() {
}
}

private void loadKeystore(char[] pass, String kestoreResource) throws NoSuchAlgorithmException, CertificateException {
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(kestoreResource)) {
ks.load(is, pass);
private void loadKeyStore(char[] pass) throws NoSuchAlgorithmException, CertificateException {
String keystore = System.getProperty(GREENMAIL_KEYSTORE_FILE_PROPERTY);
if (null != keystore) {
loadKeystore(ks, pass, new File(keystore));
} else {
try {
loadKeystore(ks, pass, GREENMAIL_KEYSTORE_P12);
} catch (IllegalStateException ex) {
// Fallback to legacy JKS keystore
loadKeystore(ks, pass, GREENMAIL_KEYSTORE_JKS);
}
}
}

private void loadKeystore(KeyStore keyStore, char[] pass, String keystoreResource)
throws NoSuchAlgorithmException, CertificateException {
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(keystoreResource)) {
keyStore.load(is, pass);
} catch (IOException ex) {
// Try hard coded default keystore
throw new IllegalStateException("Can not load greenmail keystore from '" + keystoreResource +
"' in classpath", ex);
}
}

private void loadKeystore(KeyStore keyStore, char[] pass, File keystoreResource)
throws NoSuchAlgorithmException, CertificateException {
try (InputStream is = new FileInputStream(keystoreResource)) {
keyStore.load(is, pass);
} catch (IOException ex) {
// Try hard coded default keystore
throw new IllegalStateException("Can not load greenmail keystore from '" + kestoreResource +
"' in classpath", ex);
throw new IllegalStateException("Can not load greenmail keystore from file '" + keystoreResource + "'", ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
*/
public class DummySSLSocketFactory extends SSLSocketFactory {
protected static final Logger log = LoggerFactory.getLogger(DummySSLSocketFactory.class);
private SSLSocketFactory factory;
private final SSLSocketFactory factory;

public DummySSLSocketFactory() {
try {
Expand Down Expand Up @@ -101,7 +101,7 @@ public String[] getSupportedCipherSuites() {
}

/**
* We set the host name of the remote machine because otherwise the SSL implementation is going to try
* We set the host name of the remote machine because otherwise the SSL implementation is going
* to try to do a reverse lookup to find out the host name for the host which is really slow.
* Of course we don't know the host name of the remote machine so we just set a fake host name that is unique.
* <p/>
Expand All @@ -117,11 +117,7 @@ private static void trySetFakeRemoteHost(Socket socket) {
final Method setHostMethod = socket.getClass().getMethod("setHost", String.class);
String fakeHostName = "greenmailHost" + new BigInteger(130, new Random()).toString(32);
setHostMethod.invoke(socket, fakeHostName);
} catch (NoSuchMethodException e) {
log.debug("Could not set fake remote host. SSL connection setup may be slow.");
} catch (InvocationTargetException e) {
log.debug("Could not set fake remote host. SSL connection setup may be slow.");
} catch (IllegalAccessException e) {
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
log.debug("Could not set fake remote host. SSL connection setup may be slow.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,62 @@
import com.icegreen.greenmail.util.DummySSLServerSocketFactory;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;

import static org.assertj.core.api.Assertions.assertThat;

public class DummySSLServerSocketFactoryTest {
@Test
public void testKeyStore() throws KeyStoreException {
public void testLoadDefaultKeyStore() throws KeyStoreException {
DummySSLServerSocketFactory factory = new DummySSLServerSocketFactory();
KeyStore ks = factory.getKeyStore();
assertThat(ks.containsAlias("greenmail")).isTrue();
}

@Test
public void testLoadKeyStoreViaSystemProperty() throws KeyStoreException, CertificateException,
IOException, NoSuchAlgorithmException {
// Load default KS, as re-using an existing cert is easier then creating one for testing
KeyStore systemKs = KeyStore.getInstance(KeyStore.getDefaultType());
try (final FileInputStream stream = new FileInputStream(
System.getProperty("java.home") + "/lib/security/cacerts")) {
systemKs.load(stream, "changeit".toCharArray());
}

// Prepare new keystore
KeyStore testKs = KeyStore.getInstance(KeyStore.getDefaultType());
testKs.load(null, null); // Initialize

// Create dummy entry
String testAlias = "greenmail-testLoadKeyStoreViaSystemProperty-alias";
final Certificate letsencryptisrgx1 = systemKs.getCertificate("letsencryptisrgx1 [jdk]");
testKs.setCertificateEntry(testAlias, letsencryptisrgx1);

// Save to file
String password = "some password";
final String filename = "testLoadKeyStoreViaSystemProperty." + testKs.getType();
try (FileOutputStream fos = new FileOutputStream(filename)) {
testKs.store(fos, password.toCharArray());
}

try {
System.setProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_FILE_PROPERTY, filename);
System.setProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_PASSWORD_PROPERTY, password);

// Check if loaded
DummySSLServerSocketFactory factory = new DummySSLServerSocketFactory();
KeyStore ks = factory.getKeyStore();
assertThat(ks.containsAlias(testAlias)).isTrue();
} finally {
System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_FILE_PROPERTY);
System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_PASSWORD_PROPERTY);
}
}
}

0 comments on commit 37a09af

Please sign in to comment.