diff --git a/docs/index.html b/docs/index.html
index 0fbcdaeb08..7ed10b9236 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -775,6 +775,16 @@
-Dgreenmail.auth.disabled |
Disables user authentication check, so that any password works.
diff --git a/greenmail-core/src/main/java/com/icegreen/greenmail/util/DummySSLServerSocketFactory.java b/greenmail-core/src/main/java/com/icegreen/greenmail/util/DummySSLServerSocketFactory.java
index f55fbd8876..18a0da3610 100644
--- a/greenmail-core/src/main/java/com/icegreen/greenmail/util/DummySSLServerSocketFactory.java
+++ b/greenmail-core/src/main/java/com/icegreen/greenmail/util/DummySSLServerSocketFactory.java
@@ -8,12 +8,7 @@
import org.slf4j.LoggerFactory;
import javax.net.ServerSocketFactory;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLServerSocket;
-import javax.net.ssl.SSLServerSocketFactory;
-import javax.net.ssl.TrustManager;
+import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -24,7 +19,6 @@
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
-
/**
* DummySSLServerSocketFactory - NOT SECURE
*
@@ -38,7 +32,11 @@
*
* The system property {@value #GREENMAIL_KEYSTORE_PASSWORD_PROPERTY} can override the default keystore password.
*
- * GreenMail provides the keystore resource. For customization, place your greenmail.p12 before greenmail JAR in the classpath.
+ * The system property {@value #GREENMAIL_KEY_PASSWORD_PROPERTY} can override the default key password
+ * (defaults to keystore password).
+ *
+ * GreenMail provides the keystore resource. For customization, place your greenmail.p12 before
+ * greenmail JAR in the classpath.
*
* @author Wael Chatila
* @since Feb 2006
@@ -47,6 +45,7 @@ public class DummySSLServerSocketFactory extends SSLServerSocketFactory {
protected final Logger log = LoggerFactory.getLogger(DummySSLServerSocketFactory.class);
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_KEY_PASSWORD_PROPERTY = "greenmail.tls.key.password";
public static final String GREENMAIL_KEYSTORE_P12 = "greenmail.p12";
public static final String GREENMAIL_KEYSTORE_JKS = "greenmail.jks";
private final SSLServerSocketFactory factory;
@@ -54,21 +53,20 @@ public class DummySSLServerSocketFactory extends SSLServerSocketFactory {
// 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"
- , "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"
- , "SSL_DH_anon_WITH_DES_CBC_SHA"
- , "SSL_DH_anon_WITH_RC4_128_MD5"
- , "TLS_DH_anon_WITH_AES_128_CBC_SHA"
- , "TLS_DH_anon_WITH_AES_128_CBC_SHA256"
- , "TLS_DH_anon_WITH_AES_256_CBC_SHA"
- , "TLS_DH_anon_WITH_AES_256_CBC_SHA256"
- , "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA"
- , "TLS_ECDH_anon_WITH_AES_128_CBC_SHA"
- , "TLS_ECDH_anon_WITH_AES_256_CBC_SHA"
- , "TLS_ECDH_anon_WITH_NULL_SHA"
- , "TLS_ECDH_anon_WITH_RC4_128_SHA"
- };
+ "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
+ "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5",
+ "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA",
+ "SSL_DH_anon_WITH_DES_CBC_SHA",
+ "SSL_DH_anon_WITH_RC4_128_MD5",
+ "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+ "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
+ "TLS_DH_anon_WITH_AES_256_CBC_SHA",
+ "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
+ "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
+ "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
+ "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
+ "TLS_ECDH_anon_WITH_NULL_SHA",
+ "TLS_ECDH_anon_WITH_RC4_128_SHA"};
public DummySSLServerSocketFactory() {
try {
@@ -76,13 +74,16 @@ public DummySSLServerSocketFactory() {
String defaultAlg = KeyManagerFactory.getDefaultAlgorithm();
KeyManagerFactory km = KeyManagerFactory.getInstance(defaultAlg);
ks = KeyStore.getInstance(KeyStore.getDefaultType());
- char[] pass = System.getProperty(GREENMAIL_KEYSTORE_PASSWORD_PROPERTY,"changeit").toCharArray();
+
+ char[] pass = System.getProperty(GREENMAIL_KEYSTORE_PASSWORD_PROPERTY, "changeit").toCharArray();
loadKeyStore(pass);
- km.init(ks, pass);
+
+ String keyPassStr = System.getProperty(GREENMAIL_KEY_PASSWORD_PROPERTY);
+ char[] keyPass = keyPassStr != null ? keyPassStr.toCharArray() : pass;
+ km.init(ks, keyPass);
+
KeyManager[] kma = km.getKeyManagers();
- sslcontext.init(kma,
- new TrustManager[]{new DummyTrustManager()},
- null);
+ sslcontext.init(kma, new TrustManager[]{new DummyTrustManager()}, null);
factory = sslcontext.getServerSocketFactory();
} catch (Exception e) {
throw new IllegalStateException("Can not create and initialize SSL", e);
@@ -110,8 +111,8 @@ private void loadKeyStore(KeyStore keyStore, char[] pass, String keystoreResourc
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);
+ throw new IllegalStateException(
+ "Can not load greenmail keystore from '" + keystoreResource + "' in classpath", ex);
}
}
@@ -122,8 +123,7 @@ private void loadKeyStore(KeyStore keyStore, char[] pass, File keystoreResource)
keyStore.load(is, pass);
} catch (IOException ex) {
// Try hard coded default keystore
- throw new IllegalStateException(
- "Can not load greenmail keystore from file '" + keystoreResource + "'", ex);
+ throw new IllegalStateException("Can not load greenmail keystore from file '" + keystoreResource + "'", ex);
}
}
diff --git a/greenmail-core/src/test/java/com/icegreen/greenmail/DummySSLServerSocketFactoryTest.java b/greenmail-core/src/test/java/com/icegreen/greenmail/DummySSLServerSocketFactoryTest.java
index 6ff1f17d13..6da9e36ef7 100644
--- a/greenmail-core/src/test/java/com/icegreen/greenmail/DummySSLServerSocketFactoryTest.java
+++ b/greenmail-core/src/test/java/com/icegreen/greenmail/DummySSLServerSocketFactoryTest.java
@@ -1,16 +1,19 @@
package com.icegreen.greenmail;
import com.icegreen.greenmail.util.DummySSLServerSocketFactory;
+import org.junit.After;
import org.junit.Test;
+import sun.security.x509.*;
-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.math.BigInteger;
+import java.security.*;
import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat;
@@ -23,44 +26,106 @@ public void testLoadDefaultKeyStore() throws KeyStoreException {
}
@Test
- public void testLoadKeyStoreViaSystemProperty() throws KeyStoreException, CertificateException,
- IOException, NoSuchAlgorithmException {
- // Load default KS, as re-using an existing cert is easier than 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());
- }
+ public void testLoadKeyStoreViaSystemPropertyWithDefaultKeyPwd()
+ throws GeneralSecurityException, IOException {
+ testLoadKeyStoreViaSystemProperty("store password", null);
+ }
+
+ @Test
+ public void testLoadKeyStoreViaSystemPropertyWithProvidedKeyPwd()
+ throws GeneralSecurityException, IOException {
+ testLoadKeyStoreViaSystemProperty("store password", "key password");
+ }
+
+ @After
+ public void cleanup() {
+ System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_FILE_PROPERTY);
+ System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_PASSWORD_PROPERTY);
+ System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEY_PASSWORD_PROPERTY);
+ }
+ public void testLoadKeyStoreViaSystemProperty(String storePassword, String keyPassword)
+ throws GeneralSecurityException, IOException {
// Prepare new keystore
KeyStore testKs = KeyStore.getInstance(KeyStore.getDefaultType());
testKs.load(null, null); // Initialize
- // Create dummy entry
- String testAlias = "greenmail-testLoadKeyStoreViaSystemProperty-alias";
- String baseAlias = systemKs.aliases().nextElement(); // Any alias is fine
- final Certificate testCert = systemKs.getCertificate(baseAlias);
- assertThat(testCert).isNotNull();
- testKs.setCertificateEntry(testAlias, testCert);
+ // Create key and certificate
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(4096);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ String dn = "CN=greenmail.test";
+ final X509Certificate cert = generateCertificate(dn, keyPair, 1, AlgorithmId.get("SHA256WithRSA"));
+ final String alias = "test-key";
+ testKs.setKeyEntry(alias, keyPair.getPrivate(),
+ (null != keyPassword ? keyPassword : storePassword).toCharArray(),
+ new Certificate[]{cert});
// Save to file
- String password = "some password";
final String filename = "testLoadKeyStoreViaSystemProperty." + testKs.getType();
try (FileOutputStream fos = new FileOutputStream(filename)) {
- testKs.store(fos, password.toCharArray());
+ testKs.store(fos, storePassword.toCharArray());
}
try {
System.setProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_FILE_PROPERTY, filename);
- System.setProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_PASSWORD_PROPERTY, password);
+ System.setProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_PASSWORD_PROPERTY, storePassword);
+ if (null != keyPassword) {
+ System.setProperty(DummySSLServerSocketFactory.GREENMAIL_KEY_PASSWORD_PROPERTY, keyPassword);
+ }
// Check if loaded
DummySSLServerSocketFactory factory = new DummySSLServerSocketFactory();
KeyStore ks = factory.getKeyStore();
- assertThat(ks.containsAlias(testAlias)).isTrue();
+ assertThat(ks.containsAlias(alias)).isTrue();
} finally {
System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_FILE_PROPERTY);
System.clearProperty(DummySSLServerSocketFactory.GREENMAIL_KEYSTORE_PASSWORD_PROPERTY);
}
}
+
+ /**
+ * Create a self-signed X.509 certificate
+ *
+ * Based on https://stackoverflow.com/questions/1615871
+ *
+ * @param dn the X.509 Distinguished Name
+ * @param pair the KeyPair
+ * @param days how many days till expiration
+ * @param algo the signing algorithm, eg "SHA256WithRSA"
+ */
+ public X509Certificate generateCertificate(String dn, KeyPair pair, int days, AlgorithmId algo)
+ throws GeneralSecurityException, IOException {
+ PrivateKey privateKey = pair.getPrivate();
+ X509CertInfo info = new X509CertInfo();
+
+ Instant now = Instant.now();
+ CertificateValidity interval = new CertificateValidity(
+ Date.from(now),
+ Date.from(now.plus(Duration.ofDays(days)))
+ );
+
+ X500Name owner = new X500Name(dn);
+
+ info.set(X509CertInfo.VALIDITY, interval);
+ info.set(X509CertInfo.SERIAL_NUMBER,
+ new CertificateSerialNumber(new BigInteger(64, new SecureRandom())));
+ info.set(X509CertInfo.SUBJECT, owner);
+ info.set(X509CertInfo.ISSUER, owner);
+ info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
+ info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
+ info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
+
+ // Sign the cert to identify the algorithm that's used.
+ X509CertImpl cert = new X509CertImpl(info);
+ cert.sign(privateKey, algo.getName());
+
+ // Update the algorithm, and resign.
+ algo = (AlgorithmId) cert.get(X509CertImpl.SIG_ALG);
+ info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
+ cert = new X509CertImpl(info);
+ cert.sign(privateKey, algo.getName());
+ return cert;
+ }
}
|