diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..9d6e377
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,143 @@
+job-defaults: &job-defaults
+ working_directory: &working_directory
+ ~/LittleProxy-mitm
+ test_results_directory: &test_results_directory
+ /tmp/test-results
+ environment:
+ - RELEASE_BRANCH: vgs-edition
+ - AWS_DEFAULT_REGION: us-west-2
+ - AWS_REGION: us-west-2
+ - AWS_PROFILE: vgs-dev
+
+# Set up AWS environment
+setup-env: &setup-env
+ run:
+ name: Setup Environment
+ command: ./scripts/env.sh
+
+# Template to save Maven dependency cache to restore it in another jobs
+save-maven-cache: &save-maven-cache
+ save_cache:
+ key: LittleProxy-mitm-{{ checksum "pom.xml" }}
+ paths:
+ - ~/.m2
+
+restore-maven-cache: &restore-maven-cache
+ restore_cache:
+ key: LittleProxy-mitm-{{ checksum "pom.xml" }}
+
+# Persists workspace so it can be attached in another jobs as soon as it was checked out and built
+persist-workspace: &persist-workspace
+ persist_to_workspace:
+ root: .
+ paths: "*"
+
+attach-workspace: &attach-workspace
+ attach_workspace:
+ at: *working_directory
+
+# === JOBS ===
+version: 2
+jobs:
+ build:
+ <<: *job-defaults
+ machine:
+ enabled: true
+ steps:
+ - checkout
+ - <<: *setup-env
+ - <<: *restore-maven-cache
+ - run:
+ name: Maven Clean Package
+ command: |
+ unset AWS_ACCESS_KEY_ID; unset AWS_SECRET_ACCESS_KEY; mvn clean dependency:go-offline install -Dmaven.test.skip=true --threads 5 -B
+ - run:
+ name: Collect Licenses
+ command: unset AWS_ACCESS_KEY_ID; unset AWS_SECRET_ACCESS_KEY; mvn license:aggregate-third-party-report
+ - store_artifacts:
+ path: target/site/aggregate-third-party-report.html
+ - <<: *save-maven-cache
+ - <<: *persist-workspace
+ test:
+ <<: *job-defaults
+ machine:
+ enabled: true
+ steps:
+ - <<: *attach-workspace
+ - <<: *restore-maven-cache
+ - run:
+ name: Run test
+ command: |
+ unset AWS_ACCESS_KEY_ID; unset AWS_SECRET_ACCESS_KEY; ./scripts/run_circle_tests.sh
+ deployment:
+ <<: *job-defaults
+ machine:
+ enabled: true
+ steps:
+ - <<: *attach-workspace
+ - <<: *restore-maven-cache
+ - <<: *setup-env
+ - run:
+ name: Deploy
+ command: |
+ unset AWS_ACCESS_KEY_ID; unset AWS_SECRET_ACCESS_KEY; mvn deploy -DskipTests=true
+ release:
+ <<: *job-defaults
+ machine:
+ enabled: true
+ steps:
+ - <<: *attach-workspace
+ - <<: *restore-maven-cache
+ - <<: *setup-env
+ - add_ssh_keys:
+ fingerprints:
+ - "3b:ed:ab:df:96:fb:b2:6c:00:44:ac:3e:3d:ae:72:90"
+ - run:
+ name: Release
+ command: |
+ unset AWS_ACCESS_KEY_ID
+ unset AWS_SECRET_ACCESS_KEY
+ git config user.name "circleci"
+ git config user.email "circleci@vgs.com"
+ git fetch
+ git checkout $RELEASE_BRANCH
+ git pull origin $RELEASE_BRANCH
+ git reset --hard
+ git tag -d $CIRCLE_TAG
+ mvn -B -X -e gitflow:release-start -DreleaseVersion=$CIRCLE_TAG
+ mvn -B -X -e gitflow:release-finish -DreleaseVersion=$CIRCLE_TAG -DpostReleaseGoals='deploy -DskipTests'
+ git push origin $RELEASE_BRANCH
+
+# === WORKFLOW ===
+workflows:
+ version: 2
+ build_test_and_deploy:
+ jobs:
+ - build:
+ context: artifact-publisher
+ filters:
+ tags:
+ only: /.*/
+ - test:
+ context: artifact-publisher
+ requires:
+ - build
+ filters:
+ tags:
+ only: /.*/
+ - deployment:
+ context: artifact-publisher
+ requires:
+ - test
+ filters:
+ branches:
+ only: vgs-edition
+ - release:
+ context: artifact-publisher
+ requires:
+ - test
+ filters:
+ branches:
+ ignore: /.*/
+ tags:
+ only: /.*/
diff --git a/.gitignore b/.gitignore
index 0ca11bd..621bbe2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,7 @@
/*.log
/littleproxy_cert
/littleproxy_keystore.jks
+
+### IntelliJ template
+.idea/
+*.iml
diff --git a/pom.xml b/pom.xml
index 4741748..30bedf1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
com.github.ganskef
littleproxy-mitm
jar
- 1.1.1-SNAPSHOT
+ 1.0.8-VGS-SNAPSHOT
LittleProxy - Man-In-The-Middle
LittleProxy is a high performance HTTP proxy written in Java and using the Netty networking framework.
@@ -16,13 +16,14 @@ This is an extension module to enable Man-In-The-Middle impersonation for HTTPS.
github
false
- 4.0.36.Final
+ 4.1.93.Final
1.7.21
- 1.51
+ 1.70
1.1.0
4.12
1.0.0.1
2.6
+ 1.20
@@ -79,14 +80,12 @@ This is an extension module to enable Man-In-The-Middle impersonation for HTTPS.
-->
-
+
@@ -117,6 +116,80 @@ This is an extension module to enable Man-In-The-Middle impersonation for HTTPS.
3.0.4
+
+
+ spring-snapshots
+ Spring Snapshots
+ http://repo.spring.io/snapshot
+
+ true
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ http://repo.spring.io/milestone
+
+ false
+
+
+
+
+ spring-releases
+ Spring Releases
+ http://repo.spring.io/release
+
+ false
+
+
+
+
+ repo.spring.io
+ repo.spring.io
+ http://repo.spring.io/libs-snapshot-local
+
+
+
+ verygood-release-repo
+ Very Good Release Repository
+ s3://vault-dev-01-audits-01-artifact-19k6160zpr44j/software/release/
+
+ true
+
+
+ false
+
+
+
+
+ verygood-snapshot-repo
+ Very Good Snapshot Repository
+ s3://vault-dev-01-audits-01-artifact-19k6160zpr44j/software/snapshot/
+
+ false
+
+
+ true
+
+
+
+
+
+
+ central
+ https://repo.maven.apache.org/maven2
+
+ false
+ fail
+
+
+ true
+ fail
+
+
+
+
@@ -212,75 +285,115 @@ This is an extension module to enable Man-In-The-Middle impersonation for HTTPS.
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.10.3
+ com.amashchenko.maven.plugin
+ gitflow-maven-plugin
+ 1.8.0
- private
-
-
- http://netty.io/4.0/api/
-
+ true
+ 2
+ true
+ false
+ true
+ true
+
+ vgs-edition
+ vgs-edition
+ release-
+
+
+
+ update versions for @{version} release
+ update for next development version @{version}
+
+
+
+
+ org.codehaus.mojo
+ license-maven-plugin
+ ${license-maven-plugin.version}
- attach-javadocs
+ download-licenses
- jar
+ download-licenses
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.3
- true
-
- ossrh
- https://oss.sonatype.org/
- true
-
-
-
-
- org.apache.maven.plugins
- maven-release-plugin
- 2.5
-
- true
- false
- release
- deploy
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- kr.motd.maven
- os-maven-plugin
- 1.2.3.Final
+ com.verygoodsecurity
+ aws-maven
+ 1.4.5
+
-
- org.codehaus.mojo
- findbugs-maven-plugin
- 2.5.2
-
+
+
+
+
+
-
-
- org.apache.maven.plugins
- maven-project-info-reports-plugin
- 2.6
-
+
+
+
+
+
+
-
- org.codehaus.mojo
- taglist-maven-plugin
- 2.4
-
- true
-
- mudo
- todo
- idea
- MUDO
- TODO
- IDEA
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-report-plugin
- 2.16
-
- false
-
-
-
-
- org.apache.maven.plugins
- maven-changes-plugin
- 2.8
-
-
-
- github-report
-
-
-
-
-
-
- org.codehaus.mojo
- jxr-maven-plugin
- 2.3
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -347,14 +460,16 @@ This is an extension module to enable Man-In-The-Middle impersonation for HTTPS.
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
- ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
+ vault-http-release
+ AWS Release Repository
+ s3://vault-dev-01-audits-01-artifact-19k6160zpr44j/software/release/
+
+ vault-http-snapshot
+ AWS Snapshot Repository
+ s3://vault-dev-01-audits-01-artifact-19k6160zpr44j/software/snapshot/
+
diff --git a/scripts/env.sh b/scripts/env.sh
new file mode 100755
index 0000000..0da2566
--- /dev/null
+++ b/scripts/env.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+mkdir -p ~/.aws
+
+echo "
+[default]
+region = us-west-2
+aws_access_key_id=$AWS_ACCESS_KEY_ID
+aws_secret_access_key=$AWS_SECRET_ACCESS_KEY
+[vgs-dev]
+region = us-west-2
+role_arn = arn:aws:iam::883127560329:role/VGSStageDeploy
+source_profile = default
+" > ~/.aws/credentials
\ No newline at end of file
diff --git a/scripts/run_circle_tests.sh b/scripts/run_circle_tests.sh
new file mode 100755
index 0000000..21245dc
--- /dev/null
+++ b/scripts/run_circle_tests.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+if [ "$CIRCLE_TAG" == "" ]
+then
+ mvn test -T2C
+fi
\ No newline at end of file
diff --git a/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java b/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java
index 24973dc..3d24d75 100644
--- a/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java
+++ b/src/main/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSource.java
@@ -79,6 +79,8 @@ public class BouncyCastleSslEngineSource implements SslEngineSource {
private Cache serverSSLContexts;
+ private CertificateSettings certificateSettings;
+
/**
* Creates a SSL engine source create a Certificate Authority if needed and
* initializes a SSL context. Exceptions will be thrown to let the manager
@@ -98,9 +100,12 @@ public class BouncyCastleSslEngineSource implements SslEngineSource {
* Generation takes between 50 to 500ms, but only once per
* thread, since there is a connection cache too. It's save to
* give a null cache to prevent memory or locking issues.
+ * @param certificateSettings
+ * parameters for generating x509 certificates
*/
public BouncyCastleSslEngineSource(Authority authority,
boolean trustAllServers, boolean sendCerts,
+ CertificateSettings certificateSettings,
Cache sslContexts)
throws GeneralSecurityException, OperatorCreationException,
RootCertificateException, IOException {
@@ -108,8 +113,18 @@ public BouncyCastleSslEngineSource(Authority authority,
this.trustAllServers = trustAllServers;
this.sendCerts = sendCerts;
this.serverSSLContexts = sslContexts;
+ this.certificateSettings = certificateSettings;
+
+ init();
+ }
+
+ protected void init()
+ throws RootCertificateException, GeneralSecurityException,
+ OperatorCreationException, IOException {
+
initializeKeyStore();
- initializeSSLContext();
+ KeyStore ks = loadCACertificates();
+ sslContext = initializeSSLContext(ks);
}
/**
@@ -126,18 +141,20 @@ public BouncyCastleSslEngineSource(Authority authority,
* @param trustAllServers
*
* @param sendCerts
+ * @param certificateSettings
+ * parameters for generating x509 certificates
*/
public BouncyCastleSslEngineSource(Authority authority,
- boolean trustAllServers, boolean sendCerts)
+ boolean trustAllServers, boolean sendCerts, CertificateSettings certificateSettings)
throws RootCertificateException, GeneralSecurityException,
IOException, OperatorCreationException {
- this(authority, trustAllServers, sendCerts,
- initDefaultCertificateCache());
+ this(authority, trustAllServers, sendCerts, certificateSettings, initDefaultCertificateCache());
}
private static Cache initDefaultCertificateCache() {
return CacheBuilder.newBuilder() //
.expireAfterAccess(5, TimeUnit.MINUTES) //
+ .expireAfterWrite(1, TimeUnit.HOURS) //
.concurrencyLevel(16) //
.build();
}
@@ -167,14 +184,14 @@ private void filterWeakCipherSuites(SSLEngine sslEngine) {
}
public SSLEngine newSslEngine() {
- SSLEngine sslEngine = sslContext.createSSLEngine();
+ SSLEngine sslEngine = getSSLContext().createSSLEngine();
filterWeakCipherSuites(sslEngine);
return sslEngine;
}
@Override
public SSLEngine newSslEngine(String remoteHost, int remotePort) {
- SSLEngine sslEngine = sslContext
+ SSLEngine sslEngine = getSSLContext()
.createSSLEngine(remoteHost, remotePort);
sslEngine.setUseClientMode(true);
if (!tryHostNameVerificationJava7(sslEngine)) {
@@ -184,6 +201,10 @@ public SSLEngine newSslEngine(String remoteHost, int remotePort) {
return sslEngine;
}
+ protected SSLContext getSSLContext() {
+ return sslContext;
+ }
+
private boolean tryHostNameVerificationJava7(SSLEngine sslEngine) {
for (Method method : SSLParameters.class.getMethods()) {
// method is available since Java 7
@@ -209,7 +230,7 @@ private boolean tryHostNameVerificationJava7(SSLEngine sslEngine) {
return false;
}
- private void initializeKeyStore() throws RootCertificateException,
+ protected void initializeKeyStore() throws RootCertificateException,
GeneralSecurityException, OperatorCreationException, IOException {
if (authority.aliasFile(KEY_STORE_FILE_EXTENSION).exists()
&& authority.aliasFile(".pem").exists()) {
@@ -217,7 +238,7 @@ private void initializeKeyStore() throws RootCertificateException,
}
MillisecondsDuration duration = new MillisecondsDuration();
KeyStore keystore = CertificateHelper.createRootCertificate(authority,
- KEY_STORE_TYPE);
+ KEY_STORE_TYPE, certificateSettings.getDefaultRootKeySize());
LOG.info("Created root certificate authority key store in {}ms",
duration);
@@ -234,13 +255,16 @@ private void initializeKeyStore() throws RootCertificateException,
exportPem(authority.aliasFile(".pem"), cert);
}
- private void initializeSSLContext() throws GeneralSecurityException,
- IOException {
+ protected KeyStore loadCACertificates() throws GeneralSecurityException, IOException {
KeyStore ks = loadKeyStore();
caCert = ks.getCertificate(authority.alias());
caPrivKey = (PrivateKey) ks.getKey(authority.alias(),
- authority.password());
+ authority.password());
+ return ks;
+ }
+ protected SSLContext initializeSSLContext(KeyStore ks) throws GeneralSecurityException,
+ IOException {
TrustManager[] trustManagers;
if (trustAllServers) {
trustManagers = InsecureTrustManagerFactory.INSTANCE
@@ -256,12 +280,12 @@ private void initializeSSLContext() throws GeneralSecurityException,
keyManagers = new KeyManager[0];
}
- sslContext = CertificateHelper.newClientContext(keyManagers,
- trustManagers);
+ SSLContext sslContext = CertificateHelper.newClientContext(keyManagers, trustManagers);
SSLEngine sslEngine = sslContext.createSSLEngine();
if (!tryHostNameVerificationJava7(sslEngine)) {
LOG.warn("Host Name Verification is not supported, causes insecure HTTPS connection to upstream servers.");
}
+ return sslContext;
}
private KeyStore loadKeyStore() throws GeneralSecurityException,
@@ -316,7 +340,8 @@ public SSLEngine createCertForHost(final String commonName,
if (serverSSLContexts == null) {
ctx = createServerContext(commonName, subjectAlternativeNames);
} else {
- ctx = serverSSLContexts.get(commonName, new Callable() {
+ final String alternativesIdentifier = subjectAlternativeNames.getAlternativesIdentifier(commonName);
+ ctx = serverSSLContexts.get(alternativesIdentifier, new Callable() {
@Override
public SSLContext call() throws Exception {
return createServerContext(commonName,
@@ -335,7 +360,7 @@ private SSLContext createServerContext(String commonName,
MillisecondsDuration duration = new MillisecondsDuration();
KeyStore ks = CertificateHelper.createServerCertificate(commonName,
- subjectAlternativeNames, authority, caCert, caPrivKey);
+ subjectAlternativeNames, authority, caCert, caPrivKey, certificateSettings.getFakeKeySize());
KeyManager[] keyManagers = CertificateHelper.getKeyManagers(ks,
authority);
@@ -351,7 +376,7 @@ public void initializeServerCertificates(String commonName,
IOException {
KeyStore ks = CertificateHelper.createServerCertificate(commonName,
- subjectAlternativeNames, authority, caCert, caPrivKey);
+ subjectAlternativeNames, authority, caCert, caPrivKey, certificateSettings.getFakeKeySize());
PrivateKey key = (PrivateKey) ks.getKey(authority.alias(),
authority.password());
@@ -378,6 +403,9 @@ private void exportPem(File exportFile, Object... certs)
}
}
+ protected Cache getServerSSLContexts() {
+ return serverSSLContexts;
+ }
}
class MillisecondsDuration {
diff --git a/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java b/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java
index a4358e2..9ea9a74 100644
--- a/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java
+++ b/src/main/java/org/littleshoot/proxy/mitm/CertificateHelper.java
@@ -1,5 +1,7 @@
package org.littleshoot.proxy.mitm;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
@@ -79,10 +81,6 @@ public final class CertificateHelper {
*/
private static final String SIGNATURE_ALGORITHM = (is32BitJvm() ? "SHA256" : "SHA512") + "WithRSAEncryption";
- private static final int ROOT_KEYSIZE = 2048;
-
- private static final int FAKE_KEYSIZE = 1024;
-
/** The milliseconds of a day */
private static final long ONE_DAY = 86400000L;
@@ -147,11 +145,11 @@ private static boolean is32BitJvm() {
}
public static KeyStore createRootCertificate(Authority authority,
- String keyStoreType) throws NoSuchAlgorithmException,
+ String keyStoreType, int keySize) throws NoSuchAlgorithmException,
NoSuchProviderException, IOException,
OperatorCreationException, CertificateException, KeyStoreException {
- KeyPair keyPair = generateKeyPair(ROOT_KEYSIZE);
+ KeyPair keyPair = generateKeyPair(keySize);
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, authority.commonName());
@@ -209,12 +207,11 @@ private static SubjectKeyIdentifier createSubjectKeyIdentifier(Key key)
public static KeyStore createServerCertificate(String commonName,
SubjectAlternativeNameHolder subjectAlternativeNames,
- Authority authority, Certificate caCert, PrivateKey caPrivKey)
+ Authority authority, Certificate caCert, PrivateKey caPrivKey, int keySize)
throws NoSuchAlgorithmException, NoSuchProviderException,
IOException, OperatorCreationException, CertificateException,
InvalidKeyException, SignatureException, KeyStoreException {
-
- KeyPair keyPair = generateKeyPair(FAKE_KEYSIZE);
+ KeyPair keyPair = generateKeyPair(keySize);
X500Name issuer = new X509CertificateHolder(caCert.getEncoded())
.getSubject();
diff --git a/src/main/java/org/littleshoot/proxy/mitm/CertificateSettings.java b/src/main/java/org/littleshoot/proxy/mitm/CertificateSettings.java
new file mode 100644
index 0000000..c7adab2
--- /dev/null
+++ b/src/main/java/org/littleshoot/proxy/mitm/CertificateSettings.java
@@ -0,0 +1,69 @@
+package org.littleshoot.proxy.mitm;
+
+import com.google.common.base.Preconditions;
+
+public class CertificateSettings {
+ public static final int DEFAULT_ROOT_KEY_SIZE = 2048;
+ public static final int DEFAULT_FAKE_KEY_SIZE = 1024;
+
+ public static final int MIN_KEY_SIZE = 1024;
+ public static final int MAX_KEY_SIZE = 8192;
+
+ private final int defaultRootKeySize;
+ private final int fakeKeySize;
+
+ /**
+ * @param defaultRootKeySize
+ * RSA key size used to generate fallback root certificate.
+ * If there's a root authority file found on system this parameter is ignored.
+ * @param fakeKeySize
+ * RSA key size for 'fake' x509 used to impersonate upstreams.
+ */
+ private CertificateSettings(int defaultRootKeySize, int fakeKeySize) {
+ this.defaultRootKeySize = defaultRootKeySize;
+ this.fakeKeySize = fakeKeySize;
+ }
+
+ public int getDefaultRootKeySize() {
+ return defaultRootKeySize;
+ }
+
+ public int getFakeKeySize() {
+ return fakeKeySize;
+ }
+
+ public static CertificateSettingsBuilder builder() {
+ return new CertificateSettingsBuilder();
+ }
+
+ public static class CertificateSettingsBuilder {
+ private int defaultRootKeySize = DEFAULT_ROOT_KEY_SIZE;
+ private int fakeKeySize = DEFAULT_FAKE_KEY_SIZE;
+
+ public CertificateSettingsBuilder setDefaultRootKeySize(int defaultRootKeySize) {
+ checkKeySize(defaultRootKeySize);
+ this.defaultRootKeySize = defaultRootKeySize;
+ return this;
+ }
+
+ public CertificateSettingsBuilder setFakeKeySize(int defaultFakeKeySize) {
+ checkKeySize(defaultFakeKeySize);
+ this.fakeKeySize = defaultFakeKeySize;
+ return this;
+ }
+
+ public CertificateSettings build() {
+ return new CertificateSettings(defaultRootKeySize, fakeKeySize);
+ }
+
+ private static void checkKeySize(int keySize) {
+ Preconditions.checkArgument(keySize >= MIN_KEY_SIZE, "keySize does not satisfy the min requirements");
+ Preconditions.checkArgument(keySize <= MAX_KEY_SIZE, "keySize does not satisfy the max requirements");
+ Preconditions.checkArgument(isPowerOf2(keySize), "keySize should be a power of 2");
+ }
+
+ private static boolean isPowerOf2(int number) {
+ return (number & (number - 1)) == 0;
+ }
+ }
+}
diff --git a/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java b/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java
index 252665c..232ca22 100644
--- a/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java
+++ b/src/main/java/org/littleshoot/proxy/mitm/CertificateSniffingMitmManager.java
@@ -24,19 +24,16 @@ public class CertificateSniffingMitmManager implements MitmManager {
private BouncyCastleSslEngineSource sslEngineSource;
- public CertificateSniffingMitmManager() throws RootCertificateException {
+ public CertificateSniffingMitmManager() throws Exception {
this(new Authority());
}
- public CertificateSniffingMitmManager(Authority authority)
- throws RootCertificateException {
- try {
- sslEngineSource = new BouncyCastleSslEngineSource(authority, true,
- true);
- } catch (final Exception e) {
- throw new RootCertificateException(
- "Errors during assembling root CA.", e);
- }
+ public CertificateSniffingMitmManager(Authority authority) throws Exception {
+ this(new BouncyCastleSslEngineSource(authority, true, true, CertificateSettings.builder().build()));
+ }
+
+ public CertificateSniffingMitmManager(BouncyCastleSslEngineSource sslEngineSource) throws RootCertificateException {
+ this.sslEngineSource = sslEngineSource;
}
public SSLEngine serverSslEngine(String peerHost, int peerPort) {
diff --git a/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java b/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java
index b24c750..8787048 100644
--- a/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java
+++ b/src/main/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolder.java
@@ -1,8 +1,11 @@
package org.littleshoot.proxy.mitm;
+import com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -17,6 +20,7 @@ public class SubjectAlternativeNameHolder {
private static final Pattern TAGS_PATTERN = Pattern.compile("["
+ GeneralName.iPAddress + GeneralName.dNSName + "]");
+ private static final String ALTERNATIVES_IDENTIFIER_DELIMITER = " ";
private final List sans = new ArrayList();
@@ -58,4 +62,18 @@ private ASN1Encodable parseGeneralName(List> nameEntry) {
}
throw new IllegalArgumentException(String.valueOf(nameEntry));
}
+
+ public String getAlternativesIdentifier(String commonName) {
+ if (commonName == null) {
+ throw new IllegalArgumentException(
+ "Error, 'commonName' is not allowed to be null!");
+ }
+ Set alternativesIdentifier = new TreeSet<>();
+ alternativesIdentifier.add(commonName);
+
+ for (ASN1Encodable san : sans) {
+ alternativesIdentifier.add(san.toString());
+ }
+ return Joiner.on(ALTERNATIVES_IDENTIFIER_DELIMITER).join(alternativesIdentifier);
+ }
}
diff --git a/src/test/java/de/ganskef/test/TrustedServer.java b/src/test/java/de/ganskef/test/TrustedServer.java
index 5f52a54..c484b99 100644
--- a/src/test/java/de/ganskef/test/TrustedServer.java
+++ b/src/test/java/de/ganskef/test/TrustedServer.java
@@ -4,6 +4,7 @@
import org.littleshoot.proxy.mitm.Authority;
import org.littleshoot.proxy.mitm.BouncyCastleSslEngineSource;
+import org.littleshoot.proxy.mitm.CertificateSettings;
import org.littleshoot.proxy.mitm.SubjectAlternativeNameHolder;
public class TrustedServer extends SecureServer {
@@ -21,7 +22,7 @@ public TrustedServer(int port) {
public Server start() throws Exception {
BouncyCastleSslEngineSource es = new BouncyCastleSslEngineSource(
- new Authority(), true, true);
+ new Authority(), true, true, CertificateSettings.builder().build());
SubjectAlternativeNameHolder san = new SubjectAlternativeNameHolder();
// san.addDomainName("localhost");
es.initializeServerCertificates(commonName, san);
diff --git a/src/test/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSourceTest.java b/src/test/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSourceTest.java
new file mode 100644
index 0000000..74df847
--- /dev/null
+++ b/src/test/java/org/littleshoot/proxy/mitm/BouncyCastleSslEngineSourceTest.java
@@ -0,0 +1,67 @@
+package org.littleshoot.proxy.mitm;
+
+import static org.littleshoot.proxy.mitm.SubjectAlternativeNameHolderTest.CN;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import javax.net.ssl.SSLContext;
+import junit.framework.TestCase;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.junit.Test;
+
+public class BouncyCastleSslEngineSourceTest extends TestCase {
+
+ private BouncyCastleSslEngineSource bouncyCastleSslEngineSource;
+
+ @Test
+ public void testCreateCertForHost() throws RootCertificateException, GeneralSecurityException,
+ IOException, OperatorCreationException, ExecutionException {
+ // given
+ bouncyCastleSslEngineSource =
+ new BouncyCastleSslEngineSource(new Authority(), false, false, CertificateSettings.builder().build());
+
+ SubjectAlternativeNameHolder subjectAlternativeNameHolderMiddesk = new SubjectAlternativeNameHolder();
+ SubjectAlternativeNameHolder subjectAlternativeNameHolderMyMy = new SubjectAlternativeNameHolder();
+ SubjectAlternativeNameHolder subjectAlternativeNameHolderRaiffeisen = new SubjectAlternativeNameHolder();
+ subjectAlternativeNameHolderMiddesk.addDomainName(CN);
+ subjectAlternativeNameHolderMiddesk.addDomainName("*.middesk.com");
+ subjectAlternativeNameHolderMiddesk.addDomainName("middesk.com");
+
+ subjectAlternativeNameHolderMyMy.addDomainName(CN);
+ subjectAlternativeNameHolderMyMy.addDomainName("*.my-my.com");
+ subjectAlternativeNameHolderMyMy.addDomainName("my-my.com");
+
+ subjectAlternativeNameHolderRaiffeisen.addDomainName(CN);
+ subjectAlternativeNameHolderRaiffeisen.addDomainName("*.raiffeisen.ua");
+ subjectAlternativeNameHolderRaiffeisen.addDomainName("raiffeisen.ua");
+
+ final String middeskCacheKey = "2: *.middesk.com 2: middesk.com 2: sni.cloudflaressl.com sni.cloudflaressl.com";
+ final String myMyCacheKey = "2: *.my-my.com 2: my-my.com 2: sni.cloudflaressl.com sni.cloudflaressl.com";
+ final String raiffeisenCacheKey = "2: *.raiffeisen.ua 2: raiffeisen.ua 2: sni.cloudflaressl.com sni.cloudflaressl.com";
+
+ bouncyCastleSslEngineSource.createCertForHost(CN, subjectAlternativeNameHolderMiddesk);
+ bouncyCastleSslEngineSource.createCertForHost(CN, subjectAlternativeNameHolderMyMy);
+ bouncyCastleSslEngineSource.createCertForHost(CN, subjectAlternativeNameHolderRaiffeisen);
+
+ // when
+ final SSLContext middeskSslContext = bouncyCastleSslEngineSource.getServerSSLContexts()
+ .getAllPresent(Collections.singletonList(middeskCacheKey)).get(middeskCacheKey);
+ final SSLContext myMySslContext = bouncyCastleSslEngineSource.getServerSSLContexts()
+ .getAllPresent(Collections.singletonList(myMyCacheKey)).get(myMyCacheKey);
+ final SSLContext raiffeisenContext = bouncyCastleSslEngineSource.getServerSSLContexts()
+ .getAllPresent(Collections.singletonList(raiffeisenCacheKey)).get(raiffeisenCacheKey);
+ final SSLContext shouldBeNull = bouncyCastleSslEngineSource.getServerSSLContexts()
+ .getAllPresent(Collections.singletonList("")).get("");
+
+ // then
+ assertEquals(3, bouncyCastleSslEngineSource.getServerSSLContexts().size());
+ assertNotNull(middeskSslContext);
+ assertNotNull(myMySslContext);
+ assertNotNull(raiffeisenContext);
+ assertNull(shouldBeNull);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/littleshoot/proxy/mitm/LittleProxyMitmProxy.java b/src/test/java/org/littleshoot/proxy/mitm/LittleProxyMitmProxy.java
index 6068a1e..edeb4fc 100644
--- a/src/test/java/org/littleshoot/proxy/mitm/LittleProxyMitmProxy.java
+++ b/src/test/java/org/littleshoot/proxy/mitm/LittleProxyMitmProxy.java
@@ -38,7 +38,7 @@ public class LittleProxyMitmProxy extends de.ganskef.test.Proxy implements
private final MitmManager mitmManager;
- public LittleProxyMitmProxy(int proxyPort) throws RootCertificateException {
+ public LittleProxyMitmProxy(int proxyPort) throws Exception {
this(proxyPort, new CertificateSniffingMitmManager());
}
diff --git a/src/test/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolderTest.java b/src/test/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolderTest.java
new file mode 100644
index 0000000..78ce64c
--- /dev/null
+++ b/src/test/java/org/littleshoot/proxy/mitm/SubjectAlternativeNameHolderTest.java
@@ -0,0 +1,55 @@
+package org.littleshoot.proxy.mitm;
+
+import junit.framework.TestCase;
+import org.junit.Test;
+
+public class SubjectAlternativeNameHolderTest extends TestCase {
+
+ static final String CN = "sni.cloudflaressl.com";
+ SubjectAlternativeNameHolder subjectAlternativeNameHolder = new SubjectAlternativeNameHolder();
+
+ @Test
+ public void testGetAlternativesIdentifier() {
+ // given
+ subjectAlternativeNameHolder.addDomainName(CN);
+ subjectAlternativeNameHolder.addDomainName("*.middesk.com");
+ subjectAlternativeNameHolder.addDomainName("middesk.com");
+
+ // when
+ final String actual = subjectAlternativeNameHolder.getAlternativesIdentifier(CN);
+
+ // then
+ assertEquals("2: *.middesk.com 2: middesk.com 2: sni.cloudflaressl.com sni.cloudflaressl.com", actual);
+ }
+
+ @Test
+ public void testGetAlternativesIdentifierWhenOnlyOneDomainName() {
+ // given
+ subjectAlternativeNameHolder.addDomainName("*." + CN);
+
+ // when
+ final String actual = subjectAlternativeNameHolder.getAlternativesIdentifier(CN);
+
+ // then
+ assertEquals("2: *.sni.cloudflaressl.com sni.cloudflaressl.com", actual);
+ }
+
+ @Test
+ public void testGetAlternativesIdentifierWhenSansEmpty() {
+ // when
+ final String actual = subjectAlternativeNameHolder.getAlternativesIdentifier(CN);
+
+ // then
+ assertEquals("sni.cloudflaressl.com", actual);
+ }
+
+ @Test
+ public void testGetAlternativesIdentifierWhenCNNull() {
+ try {
+ subjectAlternativeNameHolder.getAlternativesIdentifier(null);
+ fail( "No exception was thrown when CN was null" );
+ } catch( IllegalArgumentException e ) {
+ assertEquals( "Error, 'commonName' is not allowed to be null!", e.getMessage() ); // Optionally make sure you get the correct message, too
+ }
+ }
+}
\ No newline at end of file