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 - 1.7 - - 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