diff --git a/.editorconfig b/.editorconfig index fd53b56..53eb0af 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,31 +9,16 @@ trim_trailing_whitespace = true insert_final_newline = true [*.java] -indent_style = space -indent_size = 4 continuation_indent_size = 4 [*.kt] -indent_style = space -indent_size = 4 continuation_indent_size = 8 [*.md] trim_trailing_whitespace = false -[*.adoc] -trim_trailing_whitespace = false - - -[*.{js, css, html}] -indent_style = space -indent_size = 4 +[*.{js,css,html}] insert_final_newline = false -[*.{yml, yaml, json}] -indent_style = space -indent_size = 2 - -[*.xml] -indent_style = space -indent_size = 4 +[*.{yml,yaml,json}] +indent_size = 2 \ No newline at end of file diff --git a/.github/README.md b/.github/README.md index 38bee6a..07e158e 100644 --- a/.github/README.md +++ b/.github/README.md @@ -7,7 +7,7 @@ # SSL Plugin [![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/javalin/javalin-ssl/main.yaml?branch=main&label=main&logo=githubactions&logoColor=white)](https://github.com/javalin/javalin-ssl/actions?query=branch%3Amain) [![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/javalin/javalin-ssl/main.yaml?branch=dev&label=dev&logo=githubactions&logoColor=white)](https://github.com/javalin/javalin-ssl/actions?query=branch%3Adev) [![Coverage](https://codecov.io/gh/javalin/javalin-ssl/branch/dev/graphs/badge.svg)](https://app.codecov.io/gh/javalin/javalin-ssl) [![javadoc](https://javadoc.io/badge2/io.javalin.community.ssl/ssl-plugin/javadoc.svg)](https://javadoc.io/doc/io.javalin.community.ssl/ssl-plugin) -Straightforward SSL and HTTP/2 Configuration for Javalin! +Straightforward SSL, HTTP/2 and HTTP/3 Configuration for Javalin! If you're not familiar with the HTTPS protocol we have a great guide at the [Javalin website](https://javalin.io/tutorials/javalin-ssl-tutorial). @@ -53,28 +53,34 @@ Javalin.create(config->{ ```kotlin Javalin.create { config -> ... // your Javalin config here - config.plugins.register(SSLPlugin { ssl -> + config.registerPlugin(SSL) { ... // your SSL configuration here - ssl.pemFromPath("/path/to/cert.pem", "/path/to/key.pem") - }) + it.pemFromPath("/path/to/cert.pem", "/path/to/key.pem") + } } ``` ### Available config options -```java +```kotlin + // Connection options host=null; // Host to bind to, by default it will bind to all interfaces insecure=true; // Toggle the default http (insecure) connector secure=true; // Toggle the default https (secure) connector http2=true; // Toggle HTTP/2 Support +http3=false; // Toggle HTTP/3 Support -securePort=443; // Port to use on the SSL (secure) connector -insecurePort=80; // Port to use on the http (insecure) connector +securePort=443; // Port to use on the SSL (secure) connector (TCP) +insecurePort=80; // Port to use on the http (insecure) connector (TCP) +http3Port=443; // Port to use on the http3 connector (UDP) redirect=false; // Redirect all http requests to https +disableHttp3Upgrade=false; // Disable the HTTP/3 upgrade header + + sniHostCheck=true; // Enable SNI hostname verification -tlsConfig=TLSConfig.INTERMEDIATE; // Set the TLS configuration. (by default it uses Mozilla's intermediate configuration) +tlsConfig=TLSConfig.INTERMEDIATE; // Set the TLS configuration. (by default Mozilla's intermediate) // PEM loading options (mutually exclusive) pemFromPath("/path/to/cert.pem","/path/to/key.pem"); // load from the given paths @@ -92,9 +98,9 @@ keystoreFromClasspath("keyStoreName.p12","keystorePassword"); // load th keystoreFromInputStream(keystoreInputStream,"keystorePassword"); // load the keystore from the given input stream // Advanced options -configConnectors(Consumer); // Set a Consumer to configure the connectors +configConnectors { con -> con.dump() } // Set a Consumer to configure the connectors securityProvider = null; // Use a custom security provider -withTrustConfig(Consumer); // Set the trust configuration, explained below. (by default all clients are trusted) +withTrustConfig { trust -> trust.pemFromString("cert") } // Set the trust configuration, explained below. ``` #### Trust Configuration @@ -114,7 +120,7 @@ config.plugins.register(new SSLPlugin(ssl->{ })); ``` -```java +```kotlin // Certificate loading options (PEM/DER/P7B) certificateFromPath("path/to/certificate.pem"); // load a PEM/DER/P7B cert from the given path certificateFromClasspath("certificateName.pem"); // load a PEM/DER/P7B cert from the given path in the classpath @@ -132,28 +138,28 @@ trustStoreFromInputStream(inputStream, "password"); // load a trust sto #### Hot reloading Certificate reloading is supported, if you want to replace the certificate you can simply call `SSLPlugin.reload()` with the new configuration. -```java +```kotlin // Create the plugin outside the Javalin config to hold a reference to reload it -SSLPlugin sslPlugin = new SSLPlugin(ssl->{ - ssl.loadPemFromPath("/path/to/cert.pem","/path/to/key.pem"); - ssl.insecurePort = 8080; // any other config you want to change -}); +val sslPlugin = SSLPlugin { + it.loadPemFromPath("/path/to/cert.pem","/path/to/key.pem"); + it.insecurePort = 8080; // any other config you want to change +} -Javalin.create(config->{ +Javalin.create { ... // your Javalin config here - config.plugins.register(sslPlugin); -}); + it.registerPlugin(sslPlugin) +} // later on, when you want to replace the certificate -sslPlugin.reload(ssl->{ +sslPlugin.reload { // any options other than loading certificates/keys will be ignored. - ssl.loadPemFromPath("/path/to/new/cert.pem","/path/to/new/key.pem"); - + it.pemFromPath("/path/to/new/cert.pem","/path/to/new/key.pem"); + // you can also replace trust configuration - ssl.withTrustConfig(trust->{ - trust.certificateFromPath("/path/to/new/cert.pem"); - }); -}); + it.withTrustConfig{ trust -> + trust.certificateFromPath("path/to/new/certificate.pem"); + } +} ``` @@ -161,7 +167,6 @@ sslPlugin.reload(ssl->{ ## Notes - HTTP/2 **can** be used over an insecure connection. -- HTTP/3 is **not** yet supported because of some issues with Jetty's implementation. - If Jetty responds with an `HTTP ERROR 400 Invalid SNI`, you can disable SNI verification by setting `sniHostCheck = false`. - Minimizing your jar can lead to issues, [more info](https://github.com/javalin/javalin-ssl/issues/59). diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 4a9522f..0218db4 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ "main" ] + branches: [ "main", "dev" ] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: [ "main", "dev" ] schedule: - cron: '37 5 * * 4' diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 458b633..0879d7f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - java_version: [11, 17, 18] # Test all LTS releases and the latest one + java_version: [11, 17, 21] # Test all LTS releases and the latest one os: [windows-latest, macOS-latest, ubuntu-latest] steps: - name: Checkout @@ -33,9 +33,6 @@ jobs: needs: - test name: "📄 Codecov Report" - strategy: - matrix: - test-type: [unit, integration] steps: - name: Setup Java JDK uses: actions/setup-java@v3.12.0 @@ -52,20 +49,20 @@ jobs: - name: Setup and Run Gradle uses: gradle/gradle-build-action@v2.7.0 with: - arguments: ${{ matrix.test-type }}TestsCoverageReport + arguments: jacocoTestReport - name: Upload coverage to Codecov uses: codecov/codecov-action@v3.1.4 with: - files: "${{ github.workspace }}/build/reports/jacoco/${{ matrix.test-type }}TestsCoverageReport/${{ matrix.test-type }}TestsCoverageReport.xml" - flags: "${{ matrix.test-type }}Tests" + files: "${{ github.workspace }}/build/reports/jacoco/test/jacocoTestReport.xml" verbose: true token: "${{ secrets.CODECOV_TOKEN }}" - publish: + publish-release: + #TODO Support releases on 5.x if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && github.repository == 'javalin/javalin-ssl' needs: - test - name: "🛫 Publish to maven repo" + name: "🛫 Publish to maven central" runs-on: ubuntu-latest steps: - name: Checkout @@ -81,8 +78,8 @@ jobs: uses: HardNorth/github-version-generate@v1.3.0 with: version-source: file - version-file: build.gradle - version-file-extraction-pattern: '(?<=version\s*=\s*'')\S+(?='')' + version-file: build.gradle.kts + version-file-extraction-pattern: '(?<=version\s*=\s*"")\S+(?="")' - name: Validate Wrapper uses: gradle/wrapper-validation-action@v1 @@ -110,8 +107,8 @@ jobs: with: arguments: publishToSonatype closeAndReleaseStagingRepository - - name: Create Pre-Release - if: contains(env.CURRENT_VERSION, 'SNAPSHOT') + - name: Create Pre-Release for Betas + if: contains(env.CURRENT_VERSION, 'beta') uses: ncipollo/release-action@v1.12.0 with: tag: ${{ env.CURRENT_VERSION }} @@ -122,13 +119,6 @@ jobs: ## Download Instructions ### Maven ```xml - - reposilite-repository-snapshots - Reposilite Repository - https://maven.reposilite.com/snapshots - - ``` - ```xml io.javalin.community.ssl ssl-plugin @@ -136,17 +126,12 @@ jobs: ``` ### Gradle - ```groovy - maven { - url "https://maven.reposilite.com/snapshots" - } - ``` ```groovy implementation('io.javalin.community.ssl:ssl-plugin:${{ env.CURRENT_VERSION }}') ``` - name: Create Release - if: "!contains(env.CURRENT_VERSION, 'SNAPSHOT')" + if: "!contains(env.CURRENT_VERSION, 'beta') && !contains(env.CURRENT_VERSION, 'SNAPSHOT')" uses: ncipollo/release-action@v1.12.0 with: tag: ${{ env.CURRENT_VERSION }} @@ -166,6 +151,32 @@ jobs: ```groovy implementation('io.javalin.community.ssl:ssl-plugin:${{ env.CURRENT_VERSION }}') ``` + publish-snapshot: + if: github.ref == 'refs/heads/dev' && github.event_name != 'pull_request' && github.repository == 'javalin/javalin-ssl' + needs: + - test + name: "🛫 Publish snapshot to reposilite" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3.12.0 + with: + distribution: 'zulu' + java-version: '17' + - name: Validate Wrapper + uses: gradle/wrapper-validation-action@v1 + - name: Publish to Reposilite + uses: gradle/gradle-build-action@v2.7.0 + env: + MAVEN_NAME: '${{ secrets.MAVEN_NAME }}' + MAVEN_TOKEN: '${{ secrets.MAVEN_TOKEN }}' + ORG_GRADLE_PROJECT_signingKey: '${{ secrets.GPG_KEY }}' + ORG_GRADLE_PROJECT_signingPassword: '${{ secrets.GPG_PASSPHRASE }}' + ORG_GRADLE_PROJECT_signingKeyId: '${{ secrets.GPG_KEYID }}' + with: + arguments: publishMavenPublicationToReposiliteRepository diff --git a/.gitignore b/.gitignore index 3b22b85..953a44b 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ buildNumber.properties .gradle **/build/ !src/**/build/ +bin # Ignore Gradle GUI config gradle-app.setting @@ -37,3 +38,6 @@ gradle-app.setting # Eclipse Core # JDT-specific (Eclipse Java Development Tools) /.run/ + +# Vscode +.vscode diff --git a/build.gradle b/build.gradle deleted file mode 100644 index a013a27..0000000 --- a/build.gradle +++ /dev/null @@ -1,237 +0,0 @@ -plugins { - id('java-library') - id('maven-publish') - id('jacoco') - id('io.freefair.lombok') version '8.1.0' - id('signing') - id('io.github.gradle-nexus.publish-plugin') version '1.3.0' -} - -group = 'io.javalin.community.ssl' -//Must be formatted following the RegEx: /version\s*=\s*'\S+'/g -version = '5.6.3' - -jacoco { - toolVersion = '0.8.8' -} - -repositories { - mavenLocal() - mavenCentral() - maven { - url "https://maven.reposilite.com/snapshots" //Javalin Snapshots - } -} - -sourceSets { - integrationTest { - java { - srcDir 'src/intTest/java' - } - resources { - srcDir 'src/intTest/resources' - } - compileClasspath += sourceSets.main.output - runtimeClasspath += sourceSets.main.output - } -} - -configurations { - intTestImplementation.extendsFrom implementation - intTestRuntimeOnly.extendsFrom runtimeOnly -} - -dependencies { - def javalin = "5.6.2" - def junit = '5.10.0' - def sslContextKickstart = '8.2.0' - def okhttp = "4.11.0" - def annotations = "24.0.1" - - compileOnly("org.jetbrains:annotations:$annotations") - - compileOnly("io.javalin:javalin:$javalin") - implementation(platform("io.javalin:javalin-parent:$javalin")) //Javalin BOM - - implementation("org.eclipse.jetty.http2:http2-server") - implementation("org.eclipse.jetty:jetty-alpn-conscrypt-server") - implementation("org.eclipse.jetty:jetty-alpn-java-server") - //implementation("org.eclipse.jetty.http3:http3-server") - - implementation("io.github.hakky54:sslcontext-kickstart:$sslContextKickstart") - implementation("io.github.hakky54:sslcontext-kickstart-for-jetty:$sslContextKickstart") - implementation("io.github.hakky54:sslcontext-kickstart-for-pem:$sslContextKickstart") - - testImplementation("org.junit.jupiter:junit-jupiter-api:$junit") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit") - - integrationTestImplementation("io.javalin:javalin:$javalin") - integrationTestImplementation(platform("io.javalin:javalin-parent:$javalin")) - - integrationTestImplementation("io.github.hakky54:sslcontext-kickstart:$sslContextKickstart") - integrationTestImplementation("io.github.hakky54:sslcontext-kickstart-for-jetty:$sslContextKickstart") - integrationTestImplementation("io.github.hakky54:sslcontext-kickstart-for-pem:$sslContextKickstart") - - integrationTestImplementation('org.slf4j:slf4j-simple') - integrationTestImplementation("org.eclipse.jetty.http2:http2-server") - integrationTestImplementation("org.eclipse.jetty:jetty-alpn-java-server") - integrationTestImplementation("org.eclipse.jetty:jetty-alpn-conscrypt-server") - - integrationTestImplementation("com.squareup.okhttp3:okhttp:$okhttp") - integrationTestImplementation("com.squareup.okhttp3:okhttp-tls:$okhttp") - integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:$junit") - - - integrationTestRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit") -} - -publishing { - repositories { - maven { - name = "reposilite" - - def releasesRepoUrl = uri("https://maven.reposilite.com/releases") - def snapshotsRepoUrl = uri("https://maven.reposilite.com/snapshots") - - url = version.contains('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - - credentials { - username = System.getenv("MAVEN_NAME") ?: property("mavenUser").toString() - password = System.getenv("MAVEN_TOKEN") ?: property("mavenPassword").toString() - } - } - } - publications { - maven(MavenPublication) { - groupId = project.group - artifactId = rootProject.name - version = project.version - - from components.java - - pom { - name = 'Javalin SSL Plugin' - description = 'Straightforward SSL Configuration for Javalin!' - url = 'https://javalin.io/plugins/ssl-helpers' - - licenses { - license { - name = 'The Apache License, Version 2.0' - url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id = 'zugazagoitia' - name = 'Alberto Zugazagoitia' - email = 'alberto@zugazagoitia.com' - } - } - scm { - connection = 'scm:git:git://github.com/javalin/javalin-ssl.git' - developerConnection = 'scm:git:ssh://github.com:javalin/javalin-ssl.git' - url = 'https://github.com/javalin/javalin-ssl' - } - } - } - } -} - - -signing { - if (project.findProperty("signingKey")) { - useInMemoryPgpKeys(findProperty("signingKeyId"), findProperty("signingKey"), findProperty("signingPassword")) - } else { - useGpgCmd() - } - sign publishing.publications.maven -} -nexusPublishing { - repositories { - sonatype() - } -} - -tasks.withType(Javadoc) { - failOnError false - options.addStringOption('Xdoclint:none', '-quiet') - options.addStringOption('encoding', 'UTF-8') - options.addStringOption('charSet', 'UTF-8') -} - -java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - withSourcesJar() - withJavadocJar() -} - -tasks.register("integrationTests", Test) { - description = 'Runs the integration tests.' - group = 'verification' - - testClassesDirs = sourceSets.integrationTest.output.classesDirs - classpath = sourceSets.integrationTest.runtimeClasspath - - shouldRunAfter test - - outputs.upToDateWhen { false } - - testLogging { - exceptionFormat = 'full' - showStackTraces = true - } - - useJUnitPlatform { - includeTags "integration" - } - - finalizedBy integrationTestsCoverageReport - -} - -tasks.register('integrationTestsCoverageReport', JacocoReport) { - description = 'Generates code coverage report for the integrationTest task.' - group = 'verification' - - dependsOn integrationTests - - sourceSets sourceSets.main - executionData integrationTests - mustRunAfter integrationTests - - reports { - xml.required = true - } - -} - -tasks.register('unitTestsCoverageReport', JacocoReport) { - description = 'Generates code coverage report for the test task.' - group = 'verification' - - dependsOn test - - sourceSets sourceSets.main - executionData test - mustRunAfter test - - reports { - xml.required = true - } - -} - -test { - useJUnitPlatform() - finalizedBy unitTestsCoverageReport - - testLogging { - exceptionFormat = 'full' - showStackTraces = true - } -} - -check.dependsOn integrationTests - - diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..5a9bbb5 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,193 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("java-library") + id("maven-publish") + id("jacoco") + id("signing") + id("io.github.gradle-nexus.publish-plugin") version "1.3.0" + val kotlinVersion = "1.9.22" + kotlin("jvm") version kotlinVersion + kotlin("kapt") version kotlinVersion + id("org.jetbrains.dokka") version "1.9.10" + +} + +group = "io.javalin.community.ssl" +//Must be formatted following the RegEx: /version\s*=\s*"\S+"/g +version = "6.0.0-SNAPSHOT" + +jacoco { + toolVersion = "0.8.8" +} + +repositories { + mavenLocal() + mavenCentral() + maven("https://maven.reposilite.com/snapshots") { + mavenContent { + snapshotsOnly() + } + } +} + +dependencies { + val javalin = "6.0.0-beta.4" + val sslContextKickstart = "8.2.0" + + val annotations = "24.1.0" + val kotlinVersion = "1.9.0" + + val junit = "5.10.1" + val okhttp = "4.12.0" + + compileOnly("org.jetbrains:annotations:$annotations") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") + + compileOnly("io.javalin:javalin:$javalin") + implementation(platform("io.javalin:javalin-parent:$javalin")) //Javalin BOM + + implementation("org.eclipse.jetty.http2:http2-server") + implementation("org.eclipse.jetty:jetty-alpn-conscrypt-server") + implementation("org.eclipse.jetty:jetty-alpn-java-server") + + implementation("io.github.hakky54:sslcontext-kickstart:$sslContextKickstart") + implementation("io.github.hakky54:sslcontext-kickstart-for-jetty:$sslContextKickstart") + implementation("io.github.hakky54:sslcontext-kickstart-for-pem:$sslContextKickstart") + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit") + testImplementation("org.junit.jupiter:junit-jupiter-api:$junit") + testImplementation("io.javalin:javalin:$javalin") + testImplementation("org.slf4j:slf4j-simple:2.0.5") + testImplementation("com.squareup.okhttp3:okhttp:$okhttp") + testImplementation("com.squareup.okhttp3:okhttp-tls:$okhttp") + +} + +val javadocJar by tasks.registering(Jar::class) { + group = "documentation" + description = "Generates a jar file containing the generated Javadoc API documentation." + dependsOn(tasks.dokkaHtml) + archiveClassifier.set("javadoc") + from(tasks.dokkaHtml.flatMap { it.outputDirectory }) +} + +publishing { + repositories { + maven { + name = "reposilite" + + val releasesRepoUrl = uri("https://maven.reposilite.com/releases") + val snapshotsRepoUrl = uri("https://maven.reposilite.com/snapshots") + + url = if ((version as String).contains("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl + + credentials { + username = System.getenv("MAVEN_NAME") ?: property("mavenUser").toString() + password = System.getenv("MAVEN_TOKEN") ?: property("mavenPassword").toString() + } + } + } + publications { + create("maven") { + groupId = project.group as String + artifactId = rootProject.name + version = project.version as String + + from(components.getByName("java")) + artifact(javadocJar.get()) + + pom { + name.set("Javalin SSL Plugin") + description.set("Straightforward SSL Configuration for Javalin!") + url.set("https://javalin.io/plugins/ssl-helpers") + + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + id.set("zugazagoitia") + name.set("Alberto Zugazagoitia") + email.set("alberto@zugazagoitia.com") + } + } + scm { + connection.set("scm:git:git://github.com/javalin/javalin-ssl.git") + developerConnection.set("scm:git:ssh://github.com:javalin/javalin-ssl.git") + url.set("https://github.com/javalin/javalin-ssl") + } + } + } + } +} + +signing { + project.findProperty("signingKey")?.let { + useInMemoryPgpKeys( + findProperty("signingKeyId") as String, + it as String, + findProperty("signingPassword") as String) + } ?: run { + useGpgCmd() + } + + sign(publishing.publications["maven"]) +} + +nexusPublishing { + repositories { + sonatype() + } +} + +tasks.register("dokkaJavadocJar") { + dependsOn(tasks.dokkaJavadoc) + from(tasks.dokkaJavadoc.flatMap { it.outputDirectory }) + archiveClassifier.set("javadoc") +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + withSourcesJar() + //withJavadocJar() + modularity.inferModulePath.set(true) +} + +kotlin { + compilerOptions{ + jvmTarget.set(JvmTarget.JVM_11) + } +} + +tasks.jacocoTestReport{ + description = "Generates code coverage report for the test task." + group = "verification" + + dependsOn("test") + executionData.from(fileTree(project.projectDir).include("/jacoco/*.exec")) + executionData("test") + mustRunAfter("test") + + reports { + xml.required.set(true) + } + +} + +tasks.test { + useJUnitPlatform() + finalizedBy(tasks.jacocoTestReport) + + testLogging { + exceptionFormat = TestExceptionFormat.FULL + showStackTraces = true + } +} + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927..249e583 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c787..a69d9cb 100755 --- a/gradlew +++ b/gradlew @@ -205,6 +205,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..53a6b23 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/settings.gradle b/settings.gradle.kts similarity index 100% rename from settings.gradle rename to settings.gradle.kts diff --git a/src/intTest/java/io/javalin/community/ssl/IntegrationTestClass.java b/src/intTest/java/io/javalin/community/ssl/IntegrationTestClass.java deleted file mode 100644 index fffe634..0000000 --- a/src/intTest/java/io/javalin/community/ssl/IntegrationTestClass.java +++ /dev/null @@ -1,162 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.Javalin; -import io.javalin.community.ssl.certs.Server; -import lombok.Getter; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.tls.Certificates; -import okhttp3.tls.HandshakeCertificates; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509TrustManager; -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Collections; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -public abstract class IntegrationTestClass { - - static final Logger log = org.slf4j.LoggerFactory.getLogger(IntegrationTestClass.class); - - public static final String SUCCESS = "success"; - - public static final Function HTTPS_URL_WITH_PORT = (Integer port) -> String.format("https://localhost:%s/", port); - public static final Function HTTP_URL_WITH_PORT = (Integer port) -> String.format("http://localhost:%s/", port); - - public static final X509TrustManager[] trustAllCerts = new X509TrustManager[]{new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{}; - } - }}; - - protected static final AtomicInteger ports = new AtomicInteger(10000); - @Getter - private static final OkHttpClient client = createHttpsClient(); - - @Getter - private static final OkHttpClient untrustedClient = untrustedHttpsClient(); - - - - private static OkHttpClient createHttpsClient() { - HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder(); - builder.addTrustedCertificate(Certificates.decodeCertificatePem(Server.CERTIFICATE_AS_STRING)); - try { - KeyStore ks = KeyStore.getInstance("pkcs12"); - ks.load(Server.P12_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), Server.KEY_STORE_PASSWORD.toCharArray()); - for (String alias : Collections.list(ks.aliases())) { - builder.addTrustedCertificate((X509Certificate) ks.getCertificate(alias)); - } - } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) { - throw new RuntimeException(e); - } - HandshakeCertificates clientCertificates = builder.build(); - return new OkHttpClient.Builder().sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).hostnameVerifier((hostname, session) -> true).build(); - } - - private static OkHttpClient untrustedHttpsClient() { - OkHttpClient.Builder newBuilder = untrustedClientBuilder(); - - return newBuilder.build(); - } - - @NotNull - protected static OkHttpClient.Builder untrustedClientBuilder() { - - SSLContext sslContext; - try { - sslContext = SSLContext.getInstance("SSL"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - try { - sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); - } catch (KeyManagementException e) { - throw new RuntimeException(e); - } - - OkHttpClient.Builder newBuilder = new OkHttpClient.Builder(); - newBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts[0]); - newBuilder.hostnameVerifier((hostname, session) -> true); - return newBuilder; - } - - public static Javalin createTestApp(Consumer config) { - return Javalin.create((javalinConfig) -> { - javalinConfig.showJavalinBanner = false; - javalinConfig.plugins.register(new SSLPlugin(config)); - }).get("/", ctx -> ctx.result(SUCCESS)); - } - - protected static void testSuccessfulEndpoint(OkHttpClient client, String url, okhttp3.Protocol protocol) throws IOException { - Response response = client.newCall(new Request.Builder().url(url).build()).execute(); - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - assertEquals(protocol, response.protocol()); - response.close(); - } - - protected static void testSuccessfulEndpoint(String url, okhttp3.Protocol protocol) throws IOException { - IntegrationTestClass.testSuccessfulEndpoint(getClient(), url, protocol); - } - - - - void assertWorks(Protocol protocol, Consumer config) { - - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - String url = protocol == Protocol.HTTP ? http : https; - config = config.andThen(sslConfig -> { - sslConfig.insecurePort = insecurePort; - sslConfig.securePort = securePort; - }); - try (Javalin app = IntegrationTestClass.createTestApp(config)) { - app.start(); - Response response = client.newCall(new Request.Builder().url(url).build()).execute(); - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - response.close(); - } catch (IOException e) { - fail(e); - } - } - - void assertSslWorks(Consumer config) { - assertWorks(Protocol.HTTPS, config); - } - - void assertHttpWorks(Consumer config) { - assertWorks(Protocol.HTTP, config); - } - - private enum Protocol { - HTTP, HTTPS - } -} diff --git a/src/intTest/java/io/javalin/community/ssl/KeystoreLoadingTests.java b/src/intTest/java/io/javalin/community/ssl/KeystoreLoadingTests.java deleted file mode 100644 index 48434d4..0000000 --- a/src/intTest/java/io/javalin/community/ssl/KeystoreLoadingTests.java +++ /dev/null @@ -1,195 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.community.ssl.certs.Server; -import nl.altindag.ssl.exception.GenericIOException; -import nl.altindag.ssl.exception.GenericKeyStoreException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -@Tag("integration") -public class KeystoreLoadingTests extends IntegrationTestClass { - - private static final String MALFORMED_JKS_FILE_NAME = "server/malformed.jks"; - - private static final String MALFORMED_P12_FILE_NAME = "server/malformed.p12"; - - private static final String MALFORMED_JKS_FILE_PATH; - - private static final String MALFORMED_P12_FILE_PATH; - - static { - try { - MALFORMED_JKS_FILE_PATH = Path.of(ClassLoader.getSystemResource(MALFORMED_JKS_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - MALFORMED_P12_FILE_PATH = Path.of(ClassLoader.getSystemResource(MALFORMED_P12_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - public static final Supplier MALFORMED_JKS_INPUT_STREAM_SUPPLIER = () -> { - try { - return KeystoreLoadingTests.class.getResourceAsStream(MALFORMED_JKS_FILE_NAME); - } catch (Exception e) { - throw new GenericIOException(e); - } - }; - - public static final Supplier MALFORMED_P12_INPUT_STREAM_SUPPLIER = () -> { - try { - return KeystoreLoadingTests.class.getResourceAsStream(MALFORMED_JKS_FILE_NAME); - } catch (Exception e) { - throw new GenericIOException(e); - } - }; - - ////////////////////////////// - // Valid keystore loading // - ////////////////////////////// - - @Test - @DisplayName("Loading a valid JKS keystore from the classpath") - void loadValidJKSFromClasspath() { - assertSslWorks(config -> config.keystoreFromClasspath(Server.P12_KEY_STORE_NAME, Server.KEY_STORE_PASSWORD)); - } - - @Test - @DisplayName("Loading a valid P12 keystore from the classpath") - void loadValidP12FromClasspath(){ - assertSslWorks(config -> config.keystoreFromClasspath(Server.P12_KEY_STORE_NAME, Server.KEY_STORE_PASSWORD)); - } - - @Test - @DisplayName("Loading a valid JKS keystore from a path") - void loadValidJKSFromPath(){ - assertSslWorks(config -> config.keystoreFromPath(Server.P12_KEY_STORE_PATH, Server.KEY_STORE_PASSWORD)); - } - - @Test - @DisplayName("Loading a valid P12 keystore from a path") - void loadValidP12FromPath(){ - assertSslWorks(config -> config.keystoreFromPath(Server.P12_KEY_STORE_PATH, Server.KEY_STORE_PASSWORD)); - } - - @Test - @DisplayName("Loading a valid JKS keystore from an input stream") - void loadValidJKSFromInputStream(){ - assertSslWorks(config -> config.keystoreFromInputStream(Server.JKS_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), Server.KEY_STORE_PASSWORD)); - } - - @Test - @DisplayName("Loading a valid P12 keystore from an input stream") - void loadValidP12FromInputStream(){ - assertSslWorks(config -> config.keystoreFromInputStream(Server.P12_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), Server.KEY_STORE_PASSWORD)); - } - - ////////////////////////////// - // Invalid keystore loading // - ////////////////////////////// - - @Test - @DisplayName("Loading a missing JKS keystore from the classpath fails") - void loadKeystoreFromInvalidClasspath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromClasspath("invalid", Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a JKS keystore from the classpath with an invalid password fails") - void loadBadPasswordJKSFromClasspath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromClasspath(Server.JKS_KEY_STORE_NAME, "invalid"))); - } - - @Test - @DisplayName("Loading a P12 keystore from the classpath with an invalid password fails") - void loadBadPasswordP12FromClasspath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromClasspath(Server.P12_KEY_STORE_NAME, "invalid"))); - } - - @Test - @DisplayName("Loading a missing JKS keystore from a path fails") - void loadKeystoreFromInvalidPath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromPath("invalid", Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a JKS keystore from a path with an invalid password fails") - void loadBadPasswordJKSFromPath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromPath(Server.JKS_KEY_STORE_PATH, "invalid"))); - } - - @Test - @DisplayName("Loading a P12 keystore from a path with an invalid password fails") - void loadBadPasswordP12FromPath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromPath(Server.P12_KEY_STORE_PATH, "invalid"))); - } - - @Test - @DisplayName("Loading a missing JKS keystore from an input stream fails") - void loadKeystoreFromInvalidInputStream() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromInputStream(InputStream.nullInputStream(), Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a JKS keystore from an input stream with an invalid password fails") - void loadBadPasswordJKSFromInputStream() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromInputStream(Server.JKS_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), "invalid"))); - } - - @Test - @DisplayName("Loading a P12 keystore from an input stream with an invalid password fails") - void loadBadPasswordP12FromInputStream() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromInputStream(Server.P12_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), "invalid"))); - } - - @Test - @DisplayName("Loading a malformed JKS keystore from the classpath fails") - void loadMalformedJKSFromClasspath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromClasspath(MALFORMED_JKS_FILE_NAME, Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a malformed P12 keystore from the classpath fails") - void loadMalformedP12FromClasspath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromClasspath(MALFORMED_P12_FILE_NAME, Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a malformed JKS keystore from a path fails") - void loadMalformedJKSFromPath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromPath(MALFORMED_JKS_FILE_PATH, Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a malformed P12 keystore from a path fails") - void loadMalformedP12FromPath() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromPath(MALFORMED_P12_FILE_PATH, Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a malformed JKS keystore from an input stream fails") - void loadMalformedJKSFromInputStream() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromInputStream(MALFORMED_JKS_INPUT_STREAM_SUPPLIER.get(), Server.KEY_STORE_PASSWORD))); - } - - @Test - @DisplayName("Loading a malformed P12 keystore from an input stream fails") - void loadMalformedP12FromInputStream() { - assertThrows(GenericKeyStoreException.class, () -> assertSslWorks(config -> config.keystoreFromInputStream(MALFORMED_P12_INPUT_STREAM_SUPPLIER.get(), Server.KEY_STORE_PASSWORD))); - } - -} - diff --git a/src/intTest/java/io/javalin/community/ssl/PemLoadingTests.java b/src/intTest/java/io/javalin/community/ssl/PemLoadingTests.java deleted file mode 100644 index f4894ab..0000000 --- a/src/intTest/java/io/javalin/community/ssl/PemLoadingTests.java +++ /dev/null @@ -1,139 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.community.ssl.certs.Server; -import nl.altindag.ssl.exception.GenericIOException; -import nl.altindag.ssl.pem.exception.CertificateParseException; -import nl.altindag.ssl.pem.exception.PrivateKeyParseException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -@Tag("integration") -public class PemLoadingTests extends IntegrationTestClass { - - - @Test - @DisplayName("Loading a passwordless PEM file from a string works") - void loadValidPasswordlessFromString() { - assertSslWorks(config -> config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING)); - } - - @Test - @DisplayName("Loading a an invalid key PEM file from a string fails") - void loadInvalidKeyFromString() { - assertThrows(PrivateKeyParseException.class, () -> assertSslWorks(config -> config.pemFromString(Server.CERTIFICATE_AS_STRING, "invalid"))); - } - - @Test - @DisplayName("Loading a an invalid certificate PEM file from a string fails") - void loadInvalidCertificateFromString() { - assertThrows(CertificateParseException.class, () -> assertSslWorks(config -> config.pemFromString("invalid", Server.NON_ENCRYPTED_KEY_AS_STRING))); - } - - @Test - @DisplayName("Loading a PEM file with a wrong password from a string fails") - void loadInvalidPasswordFromString() { - assertThrows(PrivateKeyParseException.class, () -> assertSslWorks(config -> config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.ENCRYPTED_KEY_AS_STRING, "invalid"))); - } - - @Test - @DisplayName("Loading an encrypted PEM file from a string works") - void loadValidEncryptedFromString() { - assertSslWorks(config -> config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.ENCRYPTED_KEY_AS_STRING, Server.KEY_PASSWORD)); - } - - @Test - @DisplayName("Loading a passwordless PEM file from the classpath works") - void loadValidPasswordlessFromClasspath() { - assertSslWorks(config -> config.pemFromClasspath(Server.CERTIFICATE_FILE_NAME, Server.NON_ENCRYPTED_KEY_FILE_NAME)); - } - - @Test - @DisplayName("Loading an encrypted PEM file from the classpath works") - void loadValidEncryptedFromClasspath() { - assertSslWorks(config -> config.pemFromClasspath(Server.CERTIFICATE_FILE_NAME, Server.ENCRYPTED_KEY_FILE_NAME, Server.KEY_PASSWORD)); - } - - @Test - @DisplayName("Loading a PEM file with a wrong password from the classpath fails") - void loadInvalidPasswordFromClasspath() { - assertThrows(PrivateKeyParseException.class, () -> assertSslWorks(config -> config.pemFromClasspath(Server.CERTIFICATE_FILE_NAME, Server.ENCRYPTED_KEY_FILE_NAME, "invalid"))); - } - - @Test - @DisplayName("Loading a PEM file from an invalid classpath cert location fails") - void loadInvalidCertificateFromClasspath() { - assertThrows(IllegalArgumentException.class, () -> assertSslWorks(config -> config.pemFromClasspath("invalid", Server.NON_ENCRYPTED_KEY_FILE_NAME))); - } - - @Test - @DisplayName("Loading a PEM file from an invalid classpath key location fails") - void loadInvalidKeyFromClasspath() { - assertThrows(IllegalArgumentException.class, () -> assertSslWorks(config -> config.pemFromClasspath(Server.CERTIFICATE_FILE_NAME, "invalid"))); - } - - @Test - @DisplayName("Loading a passwordless PEM file from a path works") - void loadValidPasswordlessFromFile() { - assertSslWorks(config -> config.pemFromPath(Server.CERTIFICATE_PATH, Server.NON_ENCRYPTED_KEY_PATH)); - } - - @Test - @DisplayName("Loading an encrypted PEM file from a path works") - void loadValidEncryptedFromFile() { - assertSslWorks(config -> config.pemFromPath(Server.CERTIFICATE_PATH, Server.ENCRYPTED_KEY_PATH, Server.KEY_PASSWORD)); - } - - @Test - @DisplayName("Loading a PEM file with a wrong password from a path fails") - void loadInvalidPasswordFromFile() { - assertThrows(PrivateKeyParseException.class, () -> assertSslWorks(config -> config.pemFromPath(Server.CERTIFICATE_PATH, Server.ENCRYPTED_KEY_PATH, "invalid"))); - } - - @Test - @DisplayName("Loading a PEM file from an invalid cert path fails") - void loadInvalidCertificateFromFile() { - assertThrows(GenericIOException.class, () -> assertSslWorks(config -> config.pemFromPath("invalid", Server.NON_ENCRYPTED_KEY_PATH))); - } - - @Test - @DisplayName("Loading a PEM file from an invalid key path fails") - void loadInvalidKeyFromFile() { - assertThrows(GenericIOException.class, () -> assertSslWorks(config -> config.pemFromPath(Server.CERTIFICATE_PATH, "invalid"))); - } - - @Test - @DisplayName("Loading a passwordless PEM file from an input stream works") - void loadValidPasswordlessFromInputStream() { - assertSslWorks(config -> config.pemFromInputStream(Server.CERTIFICATE_INPUT_STREAM_SUPPLIER.get(), Server.NON_ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get())); - } - - @Test - @DisplayName("Loading an encrypted PEM file from an input stream works") - void loadValidEncryptedFromInputStream() { - assertSslWorks(config -> config.pemFromInputStream(Server.CERTIFICATE_INPUT_STREAM_SUPPLIER.get(), Server.ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get(), Server.KEY_PASSWORD)); - } - - @Test - @DisplayName("Loading a PEM file with a wrong password from an input stream fails") - void loadInvalidPasswordFromInputStream() { - assertThrows(PrivateKeyParseException.class, () -> assertSslWorks(config -> config.pemFromInputStream(Server.CERTIFICATE_INPUT_STREAM_SUPPLIER.get(), Server.ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get(), "invalid"))); - } - - @Test - @DisplayName("Loading a PEM file from an invalid cert input stream fails") - void loadInvalidCertificateFromInputStream() { - assertThrows(CertificateParseException.class, () -> assertSslWorks(config -> config.pemFromInputStream(InputStream.nullInputStream(), Server.NON_ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get()))); - } - - @Test - @DisplayName("Loading a PEM file from an invalid key input stream fails") - void loadInvalidKeyFromInputStream() { - assertThrows(PrivateKeyParseException.class, () -> assertSslWorks(config -> config.pemFromInputStream(Server.ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get(), InputStream.nullInputStream()))); - } - -} diff --git a/src/intTest/java/io/javalin/community/ssl/SSLConfigTests.java b/src/intTest/java/io/javalin/community/ssl/SSLConfigTests.java deleted file mode 100644 index fb03ad1..0000000 --- a/src/intTest/java/io/javalin/community/ssl/SSLConfigTests.java +++ /dev/null @@ -1,420 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.Javalin; -import io.javalin.community.ssl.certs.Server; -import io.javalin.community.ssl.util.SSLUtils; -import okhttp3.OkHttpClient; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.server.SslConnectionFactory; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledOnOs; - -import java.io.IOException; -import java.util.Collections; -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -@Tag("integration") -public class SSLConfigTests extends IntegrationTestClass { - - @Test - @DisplayName("Test that the insecure connector is disabled when insecure is set to false") - void testDisableInsecure() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin app = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.insecurePort = insecurePort; - }).start()) { - assertThrows(Exception.class, () -> getClient().newCall(new Request.Builder().url(http).build()).execute()); // should throw exception - Response response = getClient().newCall(new Request.Builder().url(https).build()).execute(); // should not throw exception - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - app.stop(); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the secure connector is disabled when insecure is set to true") - void testDisableSecure() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.secure = false; - config.insecurePort = insecurePort; - config.securePort = securePort; - }).start()) { - assertThrows(Exception.class, () -> getClient().newCall(new Request.Builder().url(https).build()).execute()); // should throw exception - Response response = getClient().newCall(new Request.Builder().url(http).build()).execute(); // should not throw exception - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the insecure port can be changed") - void testInsecurePortChange() { - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.secure = false; - config.insecurePort = 8080; - }).start()) { - Response response = getClient().newCall(new Request.Builder().url("http://localhost:8080/").build()).execute(); // should not throw exception - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the secure port can be changed") - void testSecurePortChange() { - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = 8443; - }).start()) { - Response response = getClient().newCall(new Request.Builder().url("https://localhost:8443/").build()).execute(); // should not throw exception - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that redirecting from http to https works") - void testRedirect(){ - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - - OkHttpClient noRedirectClient = untrustedClientBuilder().followSslRedirects(false).build(); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.insecurePort = insecurePort; - config.redirect = true; - }).start()) { - Response redirect = noRedirectClient.newCall(new Request.Builder().url(http).build()).execute(); - assertTrue(redirect.isRedirect()); - assertEquals(https, redirect.header("Location")); - Response redirected = getClient().newCall(new Request.Builder().url(http).build()).execute(); - assertEquals(200,redirected.code()); - assertEquals(SUCCESS,redirected.body().string()); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the insecure connector works with http1.1") - void testInsecureHttp1() { - int insecurePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.secure = false; - config.http2 = false; - config.insecurePort = insecurePort; - }).start()) { - testSuccessfulEndpoint(http, Protocol.HTTP_1_1); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that http2 can be disabled on the insecure connector") - void testInsecureDisableHttp2() { - OkHttpClient http2client = new OkHttpClient.Builder().protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE)).build(); - OkHttpClient http1Client = new OkHttpClient.Builder().build(); - int insecurePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.secure = false; - config.http2 = false; - config.insecurePort = insecurePort; - }).start()) { - assertThrows(Exception.class, () -> http2client.newCall(new Request.Builder().url(http).build()).execute()); // Should fail to connect using HTTP/2 - testSuccessfulEndpoint(http1Client, http, Protocol.HTTP_1_1); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that http2 can be disabled on the secure connector") - void testSecureDisableHttp2() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.http2 = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.insecurePort = insecurePort; - }).start()) { - testSuccessfulEndpoint(https, Protocol.HTTP_1_1); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the insecure connector works with http2") - void testInsecureHttp2() { - OkHttpClient client = new OkHttpClient.Builder().protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE)).build(); - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.secure = false; - config.http2 = true; - config.insecurePort = insecurePort; - config.securePort = securePort; - }).start()) { - testSuccessfulEndpoint(client, http, Protocol.H2_PRIOR_KNOWLEDGE); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the secure connector works with http2") - void testSecureHttp2() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.insecurePort = insecurePort; - }).start()) { - testSuccessfulEndpoint(https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that by default both connectors are enabled, and that http1 and http2 works") - void testDefault() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - }).start()) { - testSuccessfulEndpoint(http, Protocol.HTTP_1_1); - testSuccessfulEndpoint(https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the host can be changed") - void testMatchingHost() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.host = "localhost"; - }).start()) { - testSuccessfulEndpoint(http, Protocol.HTTP_1_1); - testSuccessfulEndpoint(https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the host change fails when it doesn't match") - void testWrongHost() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - try (Javalin ignored1 = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.host = "wronghost"; - }).start()) { - fail(); - } catch (Exception ignored) { - } - } - - @Test - @DisplayName("Test that sniHostCheck works when it matches") - void testEnabledSniHostCheckAndMatchingHostname() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.host = "localhost"; - config.sniHostCheck = true; - }).start()) { - testSuccessfulEndpoint(http, Protocol.HTTP_1_1); - testSuccessfulEndpoint(https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - - } - - @Test - @DisplayName("Test that sniHostCheck fails when it doesn't match over https") - void testEnabledSniHostCheckAndWrongHostname() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.GOOGLE_CERTIFICATE_AS_STRING, Server.GOOGLE_KEY_AS_STRING); - config.host = "localhost"; - config.sniHostCheck = true; - }).start()) { - //http request should be successful - testSuccessfulEndpoint(getUntrustedClient(), http, Protocol.HTTP_1_1); - //https request should fail - Response wrongHttpsResponse = getUntrustedClient().newCall(new Request.Builder().url(https).build()).execute(); - assertEquals(400, wrongHttpsResponse.code()); - assertTrue(Objects.requireNonNull(wrongHttpsResponse.body()).string().contains("Error 400 Invalid SNI")); - - wrongHttpsResponse.close(); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that sniHostCheck can be disabled and a request with a wrong hostname can be made") - void testDisabledSniHostCheck() { - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.GOOGLE_CERTIFICATE_AS_STRING, Server.GOOGLE_KEY_AS_STRING); - config.host = "localhost"; - config.sniHostCheck = false; - }).start()) { - testSuccessfulEndpoint(getUntrustedClient(), http, Protocol.HTTP_1_1); - testSuccessfulEndpoint(getUntrustedClient(), https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the connectors can be configured through the consumer") - void testConnectorConfigConsumer(){ - int insecurePort = ports.getAndIncrement(); - int securePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin app = IntegrationTestClass.createTestApp(config -> { - config.insecurePort = insecurePort; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.configConnectors(connector -> { - connector.setIdleTimeout(1000); - connector.setName("customName"); - }); - }).start()) { - testSuccessfulEndpoint(http, Protocol.HTTP_1_1); - testSuccessfulEndpoint(https, Protocol.HTTP_2); - for(Connector connector : app.cfg.pvt.server.getConnectors()){ - assertEquals(1000, connector.getIdleTimeout()); - assertEquals("customName", connector.getName()); - } - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the Security Provider can be automatically configured when the config is set to null") - void testNullSecurityProvider(){ - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin app = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securityProvider = null; - }).start()) { - printSecurityProviderName(app); - testSuccessfulEndpoint(https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the Security Provider works when it is set to the default") - void testDefaultSecurityProvider(){ - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin app = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - }).start()) { - printSecurityProviderName(app); - testSuccessfulEndpoint(https, Protocol.HTTP_2); - } catch (IOException e) { - fail(e); - } - } - - private static String getSecurityProviderName(Javalin app){ - ServerConnector conn = (ServerConnector) app.jettyServer().server().getConnectors()[0]; - return conn.getConnectionFactories().stream() - .filter(cf -> cf instanceof SslConnectionFactory) - .map(cf -> (SslConnectionFactory) cf) - .map(sslConnectionFactory -> sslConnectionFactory.getSslContextFactory().getSslContext().getProvider().getName()) - .findFirst() - .orElseThrow(); - } - - private static void printSecurityProviderName(Javalin app) { - System.out.println("Security provider: " + getSecurityProviderName(app)); - } - -} diff --git a/src/intTest/java/io/javalin/community/ssl/SSLPluginTest.java b/src/intTest/java/io/javalin/community/ssl/SSLPluginTest.java deleted file mode 100644 index df7d134..0000000 --- a/src/intTest/java/io/javalin/community/ssl/SSLPluginTest.java +++ /dev/null @@ -1,204 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.Javalin; -import io.javalin.community.ssl.certs.Server; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.tls.Certificates; -import okhttp3.tls.HandshakeCertificates; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -@Tag("integration") -public class SSLPluginTest extends IntegrationTestClass { - - @Test - @DisplayName("Test the reload of a pem identity") - public void testReloadIdentityPemCert() { - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - - // Create a http client that trusts the self-signed certificates - HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder(); - builder.addTrustedCertificate(Certificates.decodeCertificatePem(Server.CERTIFICATE_AS_STRING)); // Valid certificate from Vigo - builder.addTrustedCertificate(Certificates.decodeCertificatePem(Server.NORWAY_CERTIFICATE_AS_STRING)); // Valid certificate from Bergen - HandshakeCertificates clientCertificates = builder.build(); - - // Two clients are needed, one for the initial connection and one for after the reload, due to the way OkHttp caches connections - OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).hostnameVerifier((hostname, session) -> true).build(); - OkHttpClient client2 = new OkHttpClient.Builder().sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).hostnameVerifier((hostname, session) -> true).build(); - - SSLPlugin sslPlugin = new SSLPlugin(sslConfig -> { - sslConfig.insecure = false; - sslConfig.securePort = securePort; - sslConfig.pemFromString(Server.NORWAY_CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - }); - - - try (Javalin app = Javalin.create(config -> { - config.showJavalinBanner = false; - config.plugins.register(sslPlugin); - }).get("/", ctx -> ctx.result(SUCCESS)).start()) { - - // Initial connection - Response res = client.newCall(new Request.Builder().url(https).build()).execute(); - //Check that the certificate is the one we expect - X509Certificate cert = (X509Certificate) res.handshake().peerCertificates().get(0); - log.info("First Certificate: {}", cert.getSubjectX500Principal().getName()); - assertTrue(cert.getIssuerX500Principal().getName().contains("Bergen")); - - // Reload the identity - sslPlugin.reload(newConf -> { - newConf.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - }); - // Second connection - res = client2.newCall(new Request.Builder().url(https).build()).execute(); - cert = (X509Certificate) res.handshake().peerCertificates().get(0); - log.info("Second Certificate: {}", cert.getSubjectX500Principal().getName()); - assertTrue(cert.getIssuerX500Principal().getName().contains("Vigo")); - } catch (IOException e) { - fail(e); - } - } - - public void testReloadIdentityKeystore(String norwayKeyStorePath, String vigoKeyStorePath) throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException { - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - - List certificates = new ArrayList<>(); - - // Create a http client that trusts the self-signed certificates - KeyStore keyStore = KeyStore.getInstance(new File(norwayKeyStorePath), Server.KEY_STORE_PASSWORD.toCharArray()); // Valid certificate from Bergen - keyStore.aliases().asIterator().forEachRemaining(alias -> { - try { - certificates.add((X509Certificate) keyStore.getCertificate(alias)); - } catch (KeyStoreException e) { - fail(e); - } - }); - KeyStore keyStore2 = KeyStore.getInstance(new File(vigoKeyStorePath), Server.KEY_STORE_PASSWORD.toCharArray()); // Valid certificate from Vigo - keyStore2.aliases().asIterator().forEachRemaining(alias -> { - try { - certificates.add((X509Certificate) keyStore2.getCertificate(alias)); - } catch (KeyStoreException e) { - fail(e); - } - }); - - // Create a http client that trusts the self-signed certificates - HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder(); - for (X509Certificate certificate : certificates) { - builder.addTrustedCertificate(certificate); - } - HandshakeCertificates clientCertificates = builder.build(); - - // Two clients are needed, one for the initial connection and one for after the reload, due to the way OkHttp caches connections - OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).hostnameVerifier((hostname, session) -> true).build(); - OkHttpClient client2 = new OkHttpClient.Builder().sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager()).hostnameVerifier((hostname, session) -> true).build(); - - SSLPlugin sslPlugin = new SSLPlugin(sslConfig -> { - sslConfig.insecure = false; - sslConfig.securePort = securePort; - sslConfig.keystoreFromPath(norwayKeyStorePath, Server.KEY_STORE_PASSWORD); - }); - - try (Javalin app = Javalin.create(config -> { - config.showJavalinBanner = false; - config.plugins.register(sslPlugin); - }).get("/", ctx -> ctx.result(SUCCESS)).start()) { - - // Initial connection - Response res = client.newCall(new Request.Builder().url(https).build()).execute(); - //Check that the certificate is the one we expect - X509Certificate cert = (X509Certificate) res.handshake().peerCertificates().get(0); - log.info("First Certificate: {}", cert.getSubjectX500Principal().getName()); - assertTrue(cert.getIssuerX500Principal().getName().contains("Bergen")); - - // Reload the identity - sslPlugin.reload(newConf -> { - newConf.keystoreFromPath(vigoKeyStorePath, Server.KEY_STORE_PASSWORD); - }); - - // Second connection - res = client2.newCall(new Request.Builder().url(https).build()).execute(); - cert = (X509Certificate) res.handshake().peerCertificates().get(0); - log.info("Second Certificate: {}", cert.getSubjectX500Principal().getName()); - assertTrue(cert.getIssuerX500Principal().getName().contains("Vigo")); - } catch (IOException e) { - fail(e); - } - - } - - @Test - @DisplayName("Test the reload of a p12 identity") - public void testReloadP12(){ - try { - testReloadIdentityKeystore(Server.NORWAY_P12_KEY_STORE_PATH, Server.P12_KEY_STORE_PATH); - } catch (Exception e){ - fail(e); - } - } - - @Test - @DisplayName("Test the reload of JKS identity") - public void testReloadJks(){ - try { - testReloadIdentityKeystore(Server.NORWAY_JKS_KEY_STORE_PATH, Server.JKS_KEY_STORE_PATH); - } catch (Exception e){ - fail(e); - } - } - - @Test - @DisplayName("Test that the reload of a server with no SSL connector fails") - public void testReloadIdentityNonSslServer(){ - int insecurePort = ports.getAndIncrement(); - String http = HTTP_URL_WITH_PORT.apply(insecurePort); - - SSLPlugin sslPlugin = new SSLPlugin(sslConfig -> { - sslConfig.secure = false; - sslConfig.insecurePort = insecurePort; - }); - - try (Javalin app = Javalin.create(config -> { - config.showJavalinBanner = false; - config.plugins.register(sslPlugin); - }).get("/", ctx -> ctx.result(SUCCESS)).start()) { - Response res = new OkHttpClient().newCall(new Request.Builder().url(http).build()).execute(); - assertTrue(res.isSuccessful()); - assertThrows(IllegalStateException.class, () -> sslPlugin.reload(newConf -> { - newConf.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - })); - } catch (IOException e) { - fail(e); - } - } - - @Test - @DisplayName("Test that the reload of a non started server fails") - public void testReloadIdentityNonStartedServer(){ - SSLPlugin sslPlugin = new SSLPlugin(sslConfig -> { - sslConfig.secure = false; - sslConfig.insecurePort = ports.getAndIncrement(); - }); - assertThrows(IllegalStateException.class, () -> sslPlugin.reload(newConf -> { - newConf.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - })); - } - -} diff --git a/src/intTest/java/io/javalin/community/ssl/TLSConfigTest.java b/src/intTest/java/io/javalin/community/ssl/TLSConfigTest.java deleted file mode 100644 index f108d95..0000000 --- a/src/intTest/java/io/javalin/community/ssl/TLSConfigTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.Javalin; -import io.javalin.community.ssl.certs.Server; -import okhttp3.*; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import javax.net.ssl.SSLHandshakeException; -import java.net.UnknownServiceException; -import java.util.Arrays; -import java.util.Collections; - -import static org.junit.jupiter.api.Assertions.*; - -@Tag("integration") -public class TLSConfigTest extends IntegrationTestClass { - - private static String[] substractArray(String[] array, String[] toRemove) { - return Arrays.stream(array).filter(s -> !Arrays.asList(toRemove).contains(s)).toArray(String[]::new); - } - - private static OkHttpClient clientWithTLSConfig(TLSConfig config) { - - ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) - .tlsVersions(config.getProtocols()) - .cipherSuites(config.getCipherSuites()) - .build(); - - return untrustedClientBuilder() - .connectionSpecs(Collections.singletonList(spec)) - .build(); - } - - @Test - @DisplayName("Test that a Modern TLS config does not allow old protocols") - void testModernConfigWithOldProtocols() { - - String[] protocols = substractArray(TLSConfig.OLD.getProtocols(), TLSConfig.MODERN.getProtocols()); // remove modern protocols from old protocols, so that ONLY unsupported protocols are left - - OkHttpClient client = clientWithTLSConfig(new TLSConfig(TLSConfig.MODERN.getCipherSuites(), protocols)); - - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.tlsConfig = TLSConfig.MODERN; - }).start()) { - //Should fail with SSLHandshakeException because of the old protocols - assertThrows(SSLHandshakeException.class, () -> client.newCall(new Request.Builder().url(https).build()).execute()); - } - } - - @Test - @DisplayName("Test that a Modern TLS config does not allow old cipher suites") - void testModernConfigWithOldCipherSuites() { - - String[] cipherSuites = substractArray(TLSConfig.OLD.getCipherSuites(), TLSConfig.MODERN.getCipherSuites()); // remove modern cipher suites from old cipher suites, so that we can test ONLY the old cipher suites - - OkHttpClient client = clientWithTLSConfig(new TLSConfig(cipherSuites, TLSConfig.MODERN.getProtocols())); - - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.tlsConfig = TLSConfig.MODERN; - }).start()) { - //Should fail with SSLHandshakeException because of the old cipher suites - assertThrows(SSLHandshakeException.class, () -> client.newCall(new Request.Builder().url(https).build()).execute()); - } - } - - @Test - @DisplayName("Test that an Intermediate TLS config does not allow old protocols") - void testIntermediateConfigWithOldProtocols() { - - String[] protocols = substractArray(TLSConfig.OLD.getProtocols(), TLSConfig.INTERMEDIATE.getProtocols()); // remove intermediate protocols from old protocols, so that ONLY unsupported protocols are left - - OkHttpClient client = clientWithTLSConfig(new TLSConfig(TLSConfig.INTERMEDIATE.getCipherSuites(), protocols)); - - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.tlsConfig = TLSConfig.INTERMEDIATE; - }).start()) { - //Should fail with SSLHandshakeException because of the old protocols - assertThrows(UnknownServiceException.class, () -> client.newCall(new Request.Builder().url(https).build()).execute()); - } - } - - - @Test - @DisplayName("Test that an Intermediate TLS config does not allow old cipher suites") - void testIntermediateConfigWithOldCipherSuites() { - - String[] cipherSuites = substractArray(TLSConfig.OLD.getCipherSuites(), TLSConfig.INTERMEDIATE.getCipherSuites()); // remove intermediate cipher suites from old cipher suites, so that we can test ONLY the old cipher suites - - OkHttpClient client = clientWithTLSConfig(new TLSConfig(cipherSuites, TLSConfig.INTERMEDIATE.getProtocols())); - - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.tlsConfig = TLSConfig.INTERMEDIATE; - }).start()) { - //Should fail with SSLHandshakeException because of the old cipher suites - assertThrows(SSLHandshakeException.class, () -> client.newCall(new Request.Builder().url(https).build()).execute()); - } - } - - @Test - @DisplayName("Test an Intermediate TLS config works with TLSv1.3") - void testIntermediateConfigWithTLSv13() { - - ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.RESTRICTED_TLS) - .tlsVersions(TlsVersion.TLS_1_3) - .build(); - - OkHttpClient client = untrustedClientBuilder() - .connectionSpecs(Collections.singletonList(spec)) - .build(); - - - int securePort = ports.getAndIncrement(); - String https = HTTPS_URL_WITH_PORT.apply(securePort); - try (Javalin ignored = IntegrationTestClass.createTestApp(config -> { - config.insecure = false; - config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING); - config.securePort = securePort; - config.tlsConfig = TLSConfig.INTERMEDIATE; - }).start()) { - //Should work with TLSv1.3 - try (Response response = client.newCall(new Request.Builder().url(https).build()).execute()) { - assertEquals(200, response.code()); - assertEquals(TlsVersion.TLS_1_3,response.handshake().tlsVersion()); - } catch (Exception e) { - e.printStackTrace(); - fail(e); - } - } - } -} diff --git a/src/intTest/java/io/javalin/community/ssl/TrustConfigTests.java b/src/intTest/java/io/javalin/community/ssl/TrustConfigTests.java deleted file mode 100644 index 921de30..0000000 --- a/src/intTest/java/io/javalin/community/ssl/TrustConfigTests.java +++ /dev/null @@ -1,344 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.Javalin; -import io.javalin.community.ssl.certs.Client; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.tls.Certificates; -import okhttp3.tls.HandshakeCertificates; -import okhttp3.tls.HeldCertificate; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509KeyManager; -import javax.net.ssl.X509TrustManager; -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.util.Objects; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.*; - -@SuppressWarnings("CodeBlock2Expr") -@Tag("integration") -public class TrustConfigTests extends IntegrationTestClass { - - private static final Supplier authenticatedClient = - () -> httpsClientBuilder(Client.CLIENT_CERTIFICATE_AS_STRING, Client.CLIENT_PRIVATE_KEY_AS_STRING); - - - private static final Supplier wrongClient = - () -> httpsClientBuilder(Client.WRONG_CLIENT_CERTIFICATE_AS_STRING, Client.WRONG_CLIENT_PRIVATE_KEY_AS_STRING); - - private static final Supplier wrongJavaClient = - () -> javaHttpClientBuilder(Client.WRONG_CLIENT_CERTIFICATE_AS_STRING, Client.WRONG_CLIENT_PRIVATE_KEY_AS_STRING); - - private static OkHttpClient httpsClientBuilder(String clientCertificate, String privateKey) { - HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder(); - //Server certificate - builder.addTrustedCertificate(Certificates.decodeCertificatePem(Client.SERVER_CERTIFICATE_AS_STRING)); - - //Client certificate (Concatenated with the private key) - HeldCertificate heldCertificate = HeldCertificate - .decode(clientCertificate + privateKey); - builder.heldCertificate(heldCertificate); - - HandshakeCertificates clientCertificates = builder.build(); - - final SSLContext sslContext; - - try { - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, null); - sslContext.init(new X509KeyManager[]{clientCertificates.keyManager()}, - new X509TrustManager[]{clientCertificates.trustManager()}, - null); - } catch (KeyManagementException | NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - - return new OkHttpClient.Builder() - .sslSocketFactory(sslContext.getSocketFactory(), clientCertificates.trustManager()) - .hostnameVerifier((hostname, session) -> true) - .connectionPool(new okhttp3.ConnectionPool(0, 1, TimeUnit.MICROSECONDS)) - .build(); - } - - /** - * Needed in order to test multiple wrong clients, see: - * Java HTTPS client certificate authentication - * - * Java caching SSL failures - can I flush these somehow - * - */ - private static HttpClient javaHttpClientBuilder(String clientCertificate, String privateKey) { - HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder(); - //Server certificate - builder.addTrustedCertificate(Certificates.decodeCertificatePem(Client.SERVER_CERTIFICATE_AS_STRING)); - - //Client certificate (Concatenated with the private key) - HeldCertificate heldCertificate = HeldCertificate - .decode(clientCertificate + privateKey); - builder.heldCertificate(heldCertificate); - - HandshakeCertificates clientCertificates = builder.build(); - - final SSLContext sslContext; - - try { - sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, null); - sslContext.init(new X509KeyManager[]{clientCertificates.keyManager()}, - new X509TrustManager[]{clientCertificates.trustManager()}, - null); - } catch (KeyManagementException | NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - - return HttpClient.newBuilder() - .sslContext(sslContext) - .build(); - - } - - @Test - @DisplayName("Client with no certificate should not be able to access the server") - void unauthenticatedUserFails() { - OkHttpClient unauthClient = IntegrationTestClass.getClient(); //This is the client without the client certificate - - int securePort = ports.getAndIncrement(); - String url = HTTPS_URL_WITH_PORT.apply(securePort); - - try (Javalin ignored = createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.http2 = false; // Disable HTTP/2 to avoid "connection closed" errors in tests due to connection reuse - config.pemFromString(Client.SERVER_CERTIFICATE_AS_STRING, Client.SERVER_PRIVATE_KEY_AS_STRING); - config.withTrustConfig(trustConfig -> { - trustConfig.pemFromString(Client.CLIENT_CERTIFICATE_AS_STRING); - }); - }).start()) { - assertThrows(Exception.class, () -> { - unauthClient.newCall(new Request.Builder().url(url).build()).execute(); - }); - } - - } - - @Test - @DisplayName("Client with a wrong certificate should not be able to access the server") - void wrongCertificateFails() { - int securePort = ports.getAndIncrement(); - String url = HTTPS_URL_WITH_PORT.apply(securePort); - - try (Javalin ignored = createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.http2 = false; // Disable HTTP/2 to avoid "connection closed" errors in tests due to connection reuse - config.pemFromString(Client.SERVER_CERTIFICATE_AS_STRING, Client.SERVER_PRIVATE_KEY_AS_STRING); - config.withTrustConfig(trustConfig -> { - trustConfig.pemFromString(Client.CLIENT_CERTIFICATE_AS_STRING); - }); - }).start()) { - //wrongClient.get().newCall(new Request.Builder().url(url).build()).execute(); - HttpRequest req = HttpRequest.newBuilder() - .uri(URI.create(url)) - .build(); - assertThrows(Exception.class, () -> { - wrongJavaClient.get().send(req, (response) -> { - return null; - }); - }); - } - - } - - protected static void testSuccessfulEndpoint(String url) throws IOException { - OkHttpClient client = authenticatedClient.get(); - Response response = client.newCall(new Request.Builder().url(url).build()).execute(); - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - response.close(); - } - - protected static void testWrongCertOnEndpoint(String url) { - - //TrustConfigTests.wrongClient.get().newCall(new Request.Builder().url(url).build()).execute(); - HttpRequest req = HttpRequest.newBuilder() - .uri(URI.create(url)) - .build(); - - assertThrows(Exception.class, () -> { - wrongJavaClient.get().send(req, (response) -> { - System.out.println(response.statusCode()); - return null; - }); - }); - - } - - protected static void trustConfigWorks(Consumer consumer) { - int securePort = ports.getAndIncrement(); - String url = HTTPS_URL_WITH_PORT.apply(securePort); - - try (Javalin ignored = createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.pemFromString(Client.SERVER_CERTIFICATE_AS_STRING, Client.SERVER_PRIVATE_KEY_AS_STRING); - config.http2 = false; - config.withTrustConfig(consumer); - }).start()) { - testSuccessfulEndpoint(url); - testWrongCertOnEndpoint(url); - } catch (Exception e) { - fail(e); - } - } - - @Test - @DisplayName("Loading PEM from a path works") - void pemFromPathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromPath(Client.CLIENT_PEM_PATH); - }); - } - - @Test - @DisplayName("Loading P7B from a path works") - void p7bFromPathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromPath(Client.CLIENT_P7B_PATH); - }); - } - - @Test - @DisplayName("Loading DER from a path works") - void derFromPathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromPath(Client.CLIENT_DER_PATH); - }); - } - - @Test - @DisplayName("Loading PEM from the classpath works") - void pemFromClasspathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromClasspath(Client.CLIENT_PEM_FILE_NAME); - }); - } - - @Test - @DisplayName("Loading P7B from the classpath works") - void p7bFromClasspathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromClasspath(Client.CLIENT_P7B_FILE_NAME); - }); - } - - @Test - @DisplayName("Loading DER from the classpath works") - void derFromClasspathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromClasspath(Client.CLIENT_DER_FILE_NAME); - }); - } - - @Test - @DisplayName("Loading PEM from an input stream works") - void pemFromInputStreamWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromInputStream(Client.CLIENT_PEM_INPUT_STREAM_SUPPLIER.get()); - }); - } - - @Test - @DisplayName("Loading P7B from an input stream works") - void p7bFromInputStreamWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromInputStream(Client.CLIENT_P7B_INPUT_STREAM_SUPPLIER.get()); - }); - } - - @Test - @DisplayName("Loading DER from an input stream works") - void derFromInputStreamWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.certificateFromInputStream(Client.CLIENT_DER_INPUT_STREAM_SUPPLIER.get()); - }); - } - - @Test - @DisplayName("Loading PEM from a string works") - void pemFromStringWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.pemFromString(Client.CLIENT_CERTIFICATE_AS_STRING); - }); - } - - @Test - @DisplayName("Loading P7B from a string works") - void p7bFromStringWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.p7bCertificateFromString(Client.CLIENT_P7B_CERTIFICATE_AS_STRING); - }); - } - - @Test - @DisplayName("Loading a JKS Keystore from a path works") - void jksFromPathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.trustStoreFromPath(Client.CLIENT_JKS_PATH, Client.KEYSTORE_PASSWORD); - }); - } - - @Test - @DisplayName("Loading a P12 Keystore from a path works") - void p12FromPathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.trustStoreFromPath(Client.CLIENT_P12_PATH, Client.KEYSTORE_PASSWORD); - }); - } - - @Test - @DisplayName("Loading a JKS Keystore from the classpath works") - void jksFromClasspathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.trustStoreFromClasspath(Client.CLIENT_JKS_FILE_NAME, Client.KEYSTORE_PASSWORD); - }); - } - - @Test - @DisplayName("Loading a P12 Keystore from the classpath works") - void p12FromClasspathWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.trustStoreFromClasspath(Client.CLIENT_P12_FILE_NAME, Client.KEYSTORE_PASSWORD); - }); - } - - @Test - @DisplayName("Loading a JKS Keystore from an input stream works") - void jksFromInputStreamWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.trustStoreFromInputStream(Client.CLIENT_JKS_INPUT_STREAM_SUPPLIER.get(), Client.KEYSTORE_PASSWORD); - }); - } - - @Test - @DisplayName("Loading a P12 Keystore from an input stream works") - void p12FromInputStreamWorks() { - trustConfigWorks(trustConfig -> { - trustConfig.trustStoreFromInputStream(Client.CLIENT_P12_INPUT_STREAM_SUPPLIER.get(), Client.KEYSTORE_PASSWORD); - }); - } - - -} diff --git a/src/intTest/java/io/javalin/community/ssl/certs/CertificateAuthorityTests.java b/src/intTest/java/io/javalin/community/ssl/certs/CertificateAuthorityTests.java deleted file mode 100644 index 499c2fe..0000000 --- a/src/intTest/java/io/javalin/community/ssl/certs/CertificateAuthorityTests.java +++ /dev/null @@ -1,223 +0,0 @@ -package io.javalin.community.ssl.certs; - -import io.javalin.Javalin; -import io.javalin.community.ssl.IntegrationTestClass; -import io.javalin.community.ssl.SSLPlugin; -import nl.altindag.ssl.SSLFactory; -import nl.altindag.ssl.pem.util.PemUtils; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import javax.net.ssl.X509ExtendedKeyManager; -import java.io.IOException; -import java.util.Objects; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for the testing the trust of Certificates using a CA. - * issue - */ -@Tag("integration") -public class CertificateAuthorityTests extends IntegrationTestClass { - - public static final String ROOT_CERT_NAME = "ca/root-ca.cer"; - - public static final String CLIENT_FULLCHAIN_CER = "ca/client-fullchain.cer"; - public static final String CLIENT_CER = "ca/client-nochain.cer"; - public static final String CLIENT_KEY_NAME = "ca/client.key"; - - public static final String SERVER_CERT_NAME = "ca/server.cer"; - public static final String SERVER_KEY_NAME = "ca/server.key"; - - protected static void testSuccessfulEndpoint(String url, OkHttpClient client) throws IOException { - Response response = client.newCall(new Request.Builder().url(url).build()).execute(); - assertEquals(200, response.code()); - assertEquals(SUCCESS, Objects.requireNonNull(response.body()).string()); - response.close(); - client.connectionPool().evictAll(); - - } - - protected static void testWrongCertOnEndpoint(String url, OkHttpClient client) { - assertThrows(Exception.class, () -> { - client.newCall(new Request.Builder().url(url).build()).execute().close(); - client.connectionPool().evictAll(); - }); - } - - protected static void assertClientWorks(OkHttpClient client) { - int securePort = ports.getAndIncrement(); - String url = HTTPS_URL_WITH_PORT.apply(securePort); - - try (Javalin ignored = createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME); - config.http2 = false; - config.withTrustConfig(trustConfig -> trustConfig.certificateFromClasspath(ROOT_CERT_NAME)); - }).start()) { - testSuccessfulEndpoint(url, client); - } catch (Exception e) { - fail(e); - } - } - - protected static void assertClientFails(OkHttpClient client) { - int securePort = ports.getAndIncrement(); - String url = HTTPS_URL_WITH_PORT.apply(securePort); - - try (Javalin ignored = createTestApp(config -> { - config.insecure = false; - config.securePort = securePort; - config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME); - config.http2 = false; - config.withTrustConfig(trustConfig -> trustConfig.certificateFromClasspath(ROOT_CERT_NAME)); - }).start()) { - testWrongCertOnEndpoint(url, client); - } catch (Exception e) { - fail(e); - } - } - - @Test - @DisplayName("Client certificate works when trusting root CA") - void clientCertificateWorksWhenTrustingRootCA() { - final X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(CLIENT_FULLCHAIN_CER, CLIENT_KEY_NAME); - - SSLFactory sslFactory = SSLFactory.builder() - .withIdentityMaterial(keyManager) - .withTrustingAllCertificatesWithoutValidation() - .build(); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().orElseThrow()); - builder.hostnameVerifier(sslFactory.getHostnameVerifier()); - assertClientWorks(builder.build()); - } - - @Test - @DisplayName("Client fails when no certificate is provided") - void noCertificateFails() { - SSLFactory sslFactory = SSLFactory.builder() - .withTrustingAllCertificatesWithoutValidation() - .build(); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().orElseThrow()); - builder.hostnameVerifier(sslFactory.getHostnameVerifier()); - - assertClientFails(builder.build()); - } - - @Test - @DisplayName("Client fails when a self-signed certificate is provided, and a CA is trusted") - void selfsignedCertificateFails() { - SSLFactory sslFactory = SSLFactory.builder() - .withIdentityMaterial(PemUtils.parseIdentityMaterial(Client.CLIENT_CERTIFICATE_AS_STRING, Client.CLIENT_PRIVATE_KEY_AS_STRING, "".toCharArray())) - .withTrustingAllCertificatesWithoutValidation() - .build(); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().orElseThrow()); - builder.hostnameVerifier(sslFactory.getHostnameVerifier()); - - assertClientFails(builder.build()); - } - - @Test - @DisplayName("Client fails when a certificate without chain is provided, and a CA is trusted") - void certificateWithoutChainFails() { - - final X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(CLIENT_CER, CLIENT_KEY_NAME); - - SSLFactory sslFactory = SSLFactory.builder() - .withIdentityMaterial(keyManager) - .withTrustingAllCertificatesWithoutValidation() - .build(); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().orElseThrow()); - builder.hostnameVerifier(sslFactory.getHostnameVerifier()); - assertClientFails(builder.build()); - } - - @Test - @DisplayName("mTLS works when trusting a root CA, and an intermediate CA issues both the client and server certificates") - void mTLSWithIntermediateIssuerCAAndTrustedRootWorks() { - final X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(CLIENT_FULLCHAIN_CER, CLIENT_KEY_NAME); - - SSLFactory sslFactory = SSLFactory.builder() - .withIdentityMaterial(keyManager) - .withTrustMaterial(PemUtils.loadTrustMaterial(ROOT_CERT_NAME)) - .withUnsafeHostnameVerifier() // we don't care about the hostname, we just want to test the certificate - .build(); - - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - builder.sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().orElseThrow()); - builder.hostnameVerifier(sslFactory.getHostnameVerifier()); - assertClientWorks(builder.build()); - } - - @Test - @DisplayName("Hot reloading works when using mTLS") - void mTLSWithHotReloadingWorks() { - final X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(CLIENT_FULLCHAIN_CER, CLIENT_KEY_NAME); - - Supplier client = () -> { - SSLFactory sslFactory = SSLFactory.builder() - .withIdentityMaterial(keyManager) - .withTrustMaterial(PemUtils.loadTrustMaterial(ROOT_CERT_NAME)) // root cert of the client above - .withUnsafeHostnameVerifier() // we don't care about the hostname, we just want to test the certificate - .build(); - - return new OkHttpClient.Builder() - .sslSocketFactory(sslFactory.getSslSocketFactory(), sslFactory.getTrustManager().orElseThrow()) - .hostnameVerifier(sslFactory.getHostnameVerifier()) - .build(); - }; - int securePort = ports.getAndIncrement(); - String url = HTTPS_URL_WITH_PORT.apply(securePort); - - SSLPlugin sslPlugin = new SSLPlugin(config -> { - config.insecure = false; - config.securePort = securePort; - config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME); - config.http2 = false; - config.configConnectors((conn) -> conn.setIdleTimeout(0)); // disable idle timeout for testing - config.withTrustConfig(trustConfig -> trustConfig.certificateFromClasspath(ROOT_CERT_NAME)); - }); - - - try (Javalin ignored = Javalin.create((javalinConfig) -> { - javalinConfig.showJavalinBanner = false; - javalinConfig.plugins.register(sslPlugin); - }).get("/", ctx -> ctx.result(SUCCESS)) - .start()) { - testSuccessfulEndpoint(url, client.get()); // works - sslPlugin.reload(config -> { - config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME); - config.withTrustConfig(trustConfig -> { - trustConfig.certificateFromClasspath(Server.CERTIFICATE_FILE_NAME); // this is some other certificate - }); - }); - testWrongCertOnEndpoint(url, client.get()); // fails because the server now has a different trust material - sslPlugin.reload(config -> { - config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME); - config.withTrustConfig(trustConfig -> { - trustConfig.certificateFromClasspath(ROOT_CERT_NAME); // back to the original certificate - }); - }); - testSuccessfulEndpoint(url, client.get()); // works again - } catch (Exception e) { - fail(e); - } - } - - -} diff --git a/src/intTest/java/io/javalin/community/ssl/certs/Client.java b/src/intTest/java/io/javalin/community/ssl/certs/Client.java deleted file mode 100644 index 649a166..0000000 --- a/src/intTest/java/io/javalin/community/ssl/certs/Client.java +++ /dev/null @@ -1,115 +0,0 @@ -package io.javalin.community.ssl.certs; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.function.Supplier; - -public class Client { - - public static final String SERVER_CERTIFICATE_AS_STRING = "-----BEGIN CERTIFICATE-----\n" + "MIIDpjCCAo6gAwIBAgIUKK29nJVFCs8SjBqcvxrg7boyem8wDQYJKoZIhvcNAQEL\n" + "BQAwQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4GA1UECAwH\n" + "R2FsaWNpYTENMAsGA1UEBwwEVmlnbzAgFw0yMjA3MDYxMTQyMDdaGA80MDA1MDMx\n" + "MjExNDIwN1owQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4G\n" + "A1UECAwHR2FsaWNpYTENMAsGA1UEBwwEVmlnbzCCASIwDQYJKoZIhvcNAQEBBQAD\n" + "ggEPADCCAQoCggEBALtW247iPVAuCcQByuqgj8tSzJcwVqCmheT6ld0Xe7DYoLOL\n" + "EsjilB/jgG9aBEBfYJ2h74K7SIdqiSDz4rgUuJUzhZnJo5d3n3wT9Wb2AZcsqFce\n" + "JK0UNBKe2/1b01dFWtQFW4zHC/JM/Gp0dMTy1Vt1Zf/3SmQjSD/KzgJf4m2O/GOP\n" + "3iRFsCSPC4CU3TZCDmI5/qRr4icJCY5s3gJ+RT+edfsvtdkfAO4hK/p+37RrwHax\n" + "nyFLoAzYdJMcnDX/+V7Ez2y7jkTkcUk2gKG+3dpio2XqAE9pXcXa4kYk0NL9Vw6L\n" + "C2QMefFKHLDqLWx/bfQXpbULFawldETDbuLVe7UCAwEAAaOBkTCBjjAdBgNVHQ4E\n" + "FgQUiiPTBoFstcGbb0zYWsM/ZwupRRYwHwYDVR0jBBgwFoAUiiPTBoFstcGbb0zY\n" + "WsM/ZwupRRYwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr\n" + "BgEFBQcDAjAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcN\n" + "AQELBQADggEBAGvqUrtYWZpKBJNYL4UVLnm2+dQl33l8BH7PhU6YvMufThDCVjOw\n" + "IJ7ezOReDlCAmytQD7ChKpsJrAOBzKRdrifL0f88psbE83+6Ys/s/1rHMq282p/S\n" + "WPRiZDVO8Mw2ra9v9b6cprW5phHJkp7TiIBP82A+v19lt3R+vE4HZ91ZyioNqMzf\n" + "Aqvd5gfxHexpilgil0osF0o/8ajSnLiBfWI82Lz/1JB+xUMYW91ahRgt13/54h13\n" + "eL70steoAmx55he3pQaaeRZKzI1nLxsrTkjs055jDn0G/yj1L6kY3OeVFg3AhETJ\n" + "sg+yATMTef2Qskr4dgzb1LJkC9meaU2TFwk=\n" + "-----END CERTIFICATE-----\n"; - public static final String SERVER_PRIVATE_KEY_AS_STRING = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VtuO4j1QLgnE\n" + "AcrqoI/LUsyXMFagpoXk+pXdF3uw2KCzixLI4pQf44BvWgRAX2Cdoe+Cu0iHaokg\n" + "8+K4FLiVM4WZyaOXd598E/Vm9gGXLKhXHiStFDQSntv9W9NXRVrUBVuMxwvyTPxq\n" + "dHTE8tVbdWX/90pkI0g/ys4CX+Jtjvxjj94kRbAkjwuAlN02Qg5iOf6ka+InCQmO\n" + "bN4CfkU/nnX7L7XZHwDuISv6ft+0a8B2sZ8hS6AM2HSTHJw1//lexM9su45E5HFJ\n" + "NoChvt3aYqNl6gBPaV3F2uJGJNDS/VcOiwtkDHnxShyw6i1sf230F6W1CxWsJXRE\n" + "w27i1Xu1AgMBAAECggEAfPI7UZr3BckO3lnLup0ICrXYmmW1AUTPPJ8c4O7Oom55\n" + "EAaLqsvjuzkC6kGBYGW8jKX6lpjOkPKvLvk6l0fKrEhGrQFdSKKSDjFJlTgya19v\n" + "j1sdXwqAiILHer2JwUUShSJlowkGoL5UA7RURR8oye0M8KFATnVxtIpQyCinXiW/\n" + "LkDuqUr8MIbu6V/KcoSOLfJyTWyuwSRPHuFKhv154UAqaTkSPbf2mCTa9hH5Tb4f\n" + "Lfzy9o3Ux4ieZceG28De+SmC7uMzbBs1stowOuDmFg3znI/1Br/sQEAXPFngDe3s\n" + "soDD2PbLo7/4SPBNgl5vygf7jhvxHPY3DTUXOxLSgQKBgQD4EzKVTx/GpF7Yswma\n" + "oixidzSi/KnHJiMjIERF4QPVfDNnggRORNMbPnRhNWSRhS7r+INYbN4yB/vBZO5I\n" + "IIqowdJbLjGbmq91equP0zzrP2wCjqtFK6gRElX7acAWY5xTesIT5Fa1Ug++dFLS\n" + "MxCZKL6JMZaHJzZVzXugaltMsQKBgQDBUvPSaDnIBrZGdNtAyNMxZyVbp/ObIKW1\n" + "TvCDX2hqf+yiTVclbZr5QkwCE3MHErfsKlWU01K9CtzsQh4u9L5tPaeFlvm6iZq6\n" + "ktbflNvI+z+qEW3JbROR4WwwbmWFvKRLBA0OQom7tGuNnNyRtkDFxlkFJPcD6Eff\n" + "ZEq+ewrQRQKBgQCV7URM6J0TuJN58/qB8jFQ8Spmtr0FFw91UzLv6KYgiAepLvLb\n" + "Os07UeuUNGiragqJoo//CQzgv+JvZ0h7Xu9uPnWblbd1i28vWQwGyGuw4Yutn/vy\n" + "ugfBCYvdfnQRE/KOoUpaK04cF5RcToEfeK03Y2CEGewXkqNMB/wHXz/+gQKBgE8Y\n" + "34WQ+0Mp69375dEl2bL23sQXfYZU3zfFaoZ1vMUGPg1R03wO0j91rp+S0ZdtQy8v\n" + "SwCvTcTm8uj/TFYt8NPFTAtOcDKwJkx708p6n0ol8jBlHSQyqrUfJCLUqFkFi7rd\n" + "l3HkK3JPKUoxidVcWjgRJU8DhsVkfjOaVzKEKTJ5AoGARBwn7gt2H35urQ6/U3nJ\n" + "hFjOVn01F5uV0NvRtRDCsAIUMeA2T4pwALUUIqlA9HmpwYgLeG4bZ+SkhNpy70N/\n" + "qcufT1DeM+q3H5zFPANyjcqVaqa6KUnttvi/lhxMdRb6GsA9TzzHzY1P9ovpIOCK\n" + "IS639NPzxpI0Ka+v6t+nFEM=\n" + "-----END PRIVATE KEY-----\n"; - - public static final String CLIENT_CERTIFICATE_AS_STRING = "-----BEGIN CERTIFICATE-----\n" + "MIIDmDCCAoCgAwIBAgIUdWY83fnUuYRDmDnHi34wkeG+yA4wDQYJKoZIhvcNAQEL\n" + "BQAwUjEPMA0GA1UEAwwGY2xpZW50MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFk\n" + "cmlkMQ8wDQYDVQQHDAZNYWRyaWQxEDAOBgNVBAoMB0phdmFsaW4wIBcNMjMwMTEw\n" + "MTEwNTE0WhgPMjEyMjEyMTcxMTA1MTRaMFIxDzANBgNVBAMMBmNsaWVudDELMAkG\n" + "A1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMRAwDgYD\n" + "VQQKDAdKYXZhbGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsCUt\n" + "UOTWxQ3zbVXK8FPFh4DsDCphoUt/k6v8n4miZWxfw45VqsSk5JktmFhqSsmerh0N\n" + "cQZ7rji69LK/dr4wfZJjWLGlyNDJnT1W/PP3HaGErJxDl/NqLjl+xULXsp7+/SP7\n" + "Jz6QcEKmDYOyQND79MaYXlhkCLtt/RslfIP1YQ4AFCGcw4z/cGERuMtcLY8FFT+N\n" + "U4OD26AZX4fAQ+fQRAdALzp63wCnWiYyQ+0Nqeq4wDM+HYlAsUbwSwiJSseIVn2u\n" + "nn1kQq45TUcL8HUuVGr9CF8PyvkOLxbdzC0q43MfPDck7CgqR2YG9XHrca9cT6c+\n" + "zE+BGhjzOjlAxUCYqwIDAQABo2QwYjAdBgNVHQ4EFgQU3MAhBUHI6S9obysrX38v\n" + "HiGLmIcwHwYDVR0jBBgwFoAU3MAhBUHI6S9obysrX38vHiGLmIcwCwYDVR0PBAQD\n" + "AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBVjsXw\n" + "P4ZbUt5XqN8Iy30YqBi90OtfcmWxVnuc7O/HU2ue+PLM3rRyKYVgwY6G/HoRAibq\n" + "HsLnGa/2jT+qzri68CKfjE/wmBIAgzaKF4XmhOxIxQjTUuxbVd4BmrDmGqsMgWoQ\n" + "5XnYvyBQJ9LTnvvxPk4UvhUjm1/6RbPFqqxVenTUpYS4U3JTv1c9ddu9VSA8ebT4\n" + "BGBVq2iwgTm38xN9C6eS/xGCdLXGIaEQvmfgAPi1Nmw32KrLJfL3oz9bWdYhp9Hg\n" + "fZg2Pug5bLDqy8ktyTDdM+q4d+wd3XpKzuLvCIr2q03vrT9j+dMIEOTaqxWQAYiH\n" + "CYGXrU6Ry61UJSer\n" + "-----END CERTIFICATE-----\n"; - public static final String CLIENT_PRIVATE_KEY_AS_STRING = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwJS1Q5NbFDfNt\n" + "VcrwU8WHgOwMKmGhS3+Tq/yfiaJlbF/DjlWqxKTkmS2YWGpKyZ6uHQ1xBnuuOLr0\n" + "sr92vjB9kmNYsaXI0MmdPVb88/cdoYSsnEOX82ouOX7FQteynv79I/snPpBwQqYN\n" + "g7JA0Pv0xpheWGQIu239GyV8g/VhDgAUIZzDjP9wYRG4y1wtjwUVP41Tg4PboBlf\n" + "h8BD59BEB0AvOnrfAKdaJjJD7Q2p6rjAMz4diUCxRvBLCIlKx4hWfa6efWRCrjlN\n" + "RwvwdS5Uav0IXw/K+Q4vFt3MLSrjcx88NyTsKCpHZgb1cetxr1xPpz7MT4EaGPM6\n" + "OUDFQJirAgMBAAECggEADdQNV7Fvbu7mcmnu0akx865aWaYmHfyIWnaBEaFDf4Tf\n" + "i8Gr1gk0DMI9wx0F0zM64t5jBMGGiinn+3fg8hiCRAlvBTKFGlvRyCddoeQhPVFF\n" + "0is+Xzp71n8rBZ92wY4b5JGjkPQncLi6worZPp9peFDy+00jJVBZlSpBaiIN7H2E\n" + "iZQYUMI07u6xJW/EUE6X9g3AhgV9QMxfJawn8AWHXR8+9iNsOb9hlVUWBPwR7xb5\n" + "4KqB/89UFp/40tEDeKz9/MMsH5FjNCNPCaLADJS2Xy1Q1icV6V42HsaZm9vZUL+J\n" + "dru6OwEo6iJhWKjkBaWvVl4HuOPrrUP9sLSN6g6PCQKBgQDXih7xgHF35yDPvnNx\n" + "fqqxfRO+PMHq1se2tOhAdeDmdStUyl/u1NwJ9BE9Fb/lbdulYFfZJtef2TmeX31x\n" + "DaQWXrg4Pai2pnCcSfItogWJSFrg6dphbABwVslTvWw2ikB6hN2jmUaReM0atW2S\n" + "YUVWD0JFMsf4IimgAcGPgebprQKBgQDRNfB2k6NhqgKBXKrshT/3No/kMnhkNl9H\n" + "i/UmiCUYvw5E/L4q2wrsehnERAPpod3EoHjkYCmY/BK4oRCtkz6t1nnWX1zGabY3\n" + "Nn4Ie+BMCYp2NLa7yGZ0sk1rrtlgZBoaR1ZF0+HADPpffELPD6HzvUQuPyN+wlA0\n" + "SWwq8DuGtwKBgBxyxIbHlzJmNTR2RLJ0L39hrNttFYMzegSpeAYaCOciC+gTFfpl\n" + "6ez+Y9AWMM/NYjI/txiYQdl9SFeY7uufC0tQkSwLJ1uEOFTIhch0HBr0i9onw4Uc\n" + "RiqNqeD9nWzNbpk9NCvFrUTCFwAxdhbd89LaDLspaq9bgvb1hGC2mo25AoGBAKVP\n" + "ks+Pf3Unik0/tQupis7DvVVakAjXcdgt/itRPsbcCOF4OKfSZ0JOhNexysmsjnjV\n" + "OFF0rsnkvMJI+s284LUqGSHMPpnFZCciltoLUEOk8lTO+GlPQ64ISebBxaBF2N5U\n" + "6hXJA8PmPVx/6qaEurrHHf3RBDIgRpHaRm9zXgXnAoGASlEFHkUKwf8G3AePstzk\n" + "sHoxJiKMTq2qFb/NTVE4z4+pc03uhxno79+R4aV4JD0dK1gyRaX5/TCwdvI5smS3\n" + "Vfl5JN+HiO0zClecR8N83arOLka2prJ3ZjjCy2JgZKRXZQ/vcsTKnvh3DIFyR/NZ\n" + "OKM5x3IGChzxEZLumfedQX4=\n" + "-----END PRIVATE KEY-----\n"; - - public static final String WRONG_CLIENT_CERTIFICATE_AS_STRING = "-----BEGIN CERTIFICATE-----\n" + "MIIDmDCCAoCgAwIBAgIUD0yMp2hSck6P1TtqpbVvf1QlJMswDQYJKoZIhvcNAQEL\n" + "BQAwUjEPMA0GA1UEAwwGY2xpZW50MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFk\n" + "cmlkMQ8wDQYDVQQHDAZNYWRyaWQxEDAOBgNVBAoMB0phdmFsaW4wIBcNMjMwMTEw\n" + "MTE0MzM0WhgPMjEyMjEyMTcxMTQzMzRaMFIxDzANBgNVBAMMBmNsaWVudDELMAkG\n" + "A1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMRAwDgYD\n" + "VQQKDAdKYXZhbGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvt3f\n" + "vHU1vVoZFu5pV53IXjSP7FoRi6mB27RASyOTXZGCdKTmRZxlgUF7yrMjse99zkys\n" + "GR4csPE62vxDB5SHiaLdCVDWFUNmvENJ+Om6v4SnUrVju/1OUDthsTBXe6t7N0Ou\n" + "ihPxN5tZKumdDaB56djIXkEfmPFFc/7vRC9cqYISWvKtFT2bkBwzNkcUzTlR05WL\n" + "8m5napdl8SQ3/Gza+iVjDtkBDvKs4nlG+QmhT0U4+5B1vah1doKfv+Sn2CAfoTs0\n" + "aIMuHAcdApLR4IVEIADPhNb9pePurXChFHGq7kY90g+wh69rNVsi4uq8HwPSTaQe\n" + "YhsTebk71irMquoSMwIDAQABo2QwYjAdBgNVHQ4EFgQUeZ640SK+L1/GPQIis8mz\n" + "bHOOQvYwHwYDVR0jBBgwFoAUeZ640SK+L1/GPQIis8mzbHOOQvYwCwYDVR0PBAQD\n" + "AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBNinqE\n" + "9Xltwk+khvbRmkF/AIbXMIIFpgGjUWmlg42aUmba+OdQKjHbChiSZHpsue6o/Abj\n" + "AgPpb4xH9AacQVM2yFTh/o9UeRwAJtjHrSzIgkBTy2YOM6TFXi2M6a6fBWuEuYQC\n" + "jB0std0HNK0ln2MqFKJn3IMk6oiX3XslTXbcTOP8S/T2fj4bc3C4kBZWjUj3qreD\n" + "QqzvaWOpVUt7a/slICZ5fVII0vn7EnaNvjsZq9ilBs9MuBH92LNJ0nIO9rhw94TQ\n" + "xYyJ1RUBugQrcnpx6xMW3cIUuv/IXu14X+5wEOw21udKaafen5WYVqEkVBW12bgP\n" + "0I8c8C6x8S6P4eDO\n" + "-----END CERTIFICATE-----\n"; - public static final String WRONG_CLIENT_PRIVATE_KEY_AS_STRING = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+3d+8dTW9WhkW\n" + "7mlXncheNI/sWhGLqYHbtEBLI5NdkYJ0pOZFnGWBQXvKsyOx733OTKwZHhyw8Tra\n" + "/EMHlIeJot0JUNYVQ2a8Q0n46bq/hKdStWO7/U5QO2GxMFd7q3s3Q66KE/E3m1kq\n" + "6Z0NoHnp2MheQR+Y8UVz/u9EL1ypghJa8q0VPZuQHDM2RxTNOVHTlYvybmdql2Xx\n" + "JDf8bNr6JWMO2QEO8qzieUb5CaFPRTj7kHW9qHV2gp+/5KfYIB+hOzRogy4cBx0C\n" + "ktHghUQgAM+E1v2l4+6tcKEUcaruRj3SD7CHr2s1WyLi6rwfA9JNpB5iGxN5uTvW\n" + "Ksyq6hIzAgMBAAECggEBAKXa/ZGxNHqPMWAo2idFt5iNCkey2K5JJMu67WedyW+0\n" + "gu1DYco5pkbUlXLFig4T83lyTNYiwYHMjX0/WivbGJA0kuiGcxHVGRAdVMlUqW/F\n" + "IPURJFJ2Qjgb8b9cJ5kSoSabzK61t5W/i5Nrn4r42ReoxiyJYKCxf83VSSsyEM5F\n" + "9C+qcjux8+tkF7NlBWXwl2/qqcbuqDuhsTFNmq5Fngx6Xwv7hk1gU0Ibglyo7KmO\n" + "75EGcN3T2QWMPMc9C027h3260ROlNOWMNexJZ4vtrWR8GNFi0wOnjaHUW2eilrrg\n" + "XGhuzzYFO2ikAPXsJo/+fqfhrqms8ujRlExYqECMkDkCgYEA8xbkZ5UvnXT9uthL\n" + "/nJe2Ax1rYOOK7VLsYHciNxkoDmJwRufIK7MUw2qGVKpBB5jAjXBDiYf5cVlyLDJ\n" + "tB/5Qh7PkTWTTOMlcY9QsV+nYklf4IYvaURoSKqreotx0PsGQq0R6kpVy2MWn3xs\n" + "R4aWmMoCTzVMLVgE2Ibtuv14tIUCgYEAyQDyo7865bssHzFRN52Mq5Ls2WMC0H/q\n" + "Owk2NzJoSqNecyzpOvt3hM71IAdOo/xQQdJ7dNMh/B/etbKW3sJNhyVCb1XPVhKk\n" + "+ixd4slensXlJvHoXiugmkbIhoEYx8c+2fhxQSP7PXdCanFtCL/M4Ey9MkaUymyK\n" + "E/7kAafPpVcCgYEAiWg2QYrluFZqGhSrmC+kBvG8DxGe6nv3RmZGd6JEywDbKinn\n" + "3/yOiJ/ft6Ku4SIgCx7BerL4MtRK/Y9Y5JVyOvrZj5Y+Jib7gl5lWW3dWsRpCqwu\n" + "3o0JeZHnjkSGWH+cgVH9H3dXWbkwD4SwXBnqxIDjn0xcPAFV8+MJPDqM4VUCgYBz\n" + "cV/qPAKPvxhwMdr7njkUsaXmlL8hENZuYbQJr6HGfF3auIibn6HdXR/b7VZ1SIyv\n" + "wTu2tSxnqcY3hQKxndb5L6UgXKBgRwUJykGB5zW46t/ZpkZXD6eF8/Fnju20j/LB\n" + "LbeeOhQqETzL9akxxTbd/DUNkwwR1pTXNyWs7byMsQKBgGW6QkazJ5iFHZrwKlgS\n" + "lJG//bnbUU1ZPITh25RihWa78l5tnUAK4iJwBmOPwjtSC+4qG35xCt1TSBHPR/yy\n" + "hMlv7rBUPwS1UHzvVifQwE5D2NHyselZYdhxyqqJ2hlq4ykjZZJ+vc01FRl2Wfd/\n" + "SQO2OwXQkrajbcSXGpQg/aQs\n" + "-----END PRIVATE KEY-----\n"; - - public static final String CLIENT_P7B_CERTIFICATE_AS_STRING = "-----BEGIN PKCS7-----\n" + "MIIDyQYJKoZIhvcNAQcCoIIDujCCA7YCAQExADALBgkqhkiG9w0BBwGgggOcMIID\n" + "mDCCAoCgAwIBAgIUdWY83fnUuYRDmDnHi34wkeG+yA4wDQYJKoZIhvcNAQELBQAw\n" + "UjEPMA0GA1UEAwwGY2xpZW50MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFkcmlk\n" + "MQ8wDQYDVQQHDAZNYWRyaWQxEDAOBgNVBAoMB0phdmFsaW4wIBcNMjMwMTEwMTEw\n" + "NTE0WhgPMjEyMjEyMTcxMTA1MTRaMFIxDzANBgNVBAMMBmNsaWVudDELMAkGA1UE\n" + "BhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMRAwDgYDVQQK\n" + "DAdKYXZhbGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsCUtUOTW\n" + "xQ3zbVXK8FPFh4DsDCphoUt/k6v8n4miZWxfw45VqsSk5JktmFhqSsmerh0NcQZ7\n" + "rji69LK/dr4wfZJjWLGlyNDJnT1W/PP3HaGErJxDl/NqLjl+xULXsp7+/SP7Jz6Q\n" + "cEKmDYOyQND79MaYXlhkCLtt/RslfIP1YQ4AFCGcw4z/cGERuMtcLY8FFT+NU4OD\n" + "26AZX4fAQ+fQRAdALzp63wCnWiYyQ+0Nqeq4wDM+HYlAsUbwSwiJSseIVn2unn1k\n" + "Qq45TUcL8HUuVGr9CF8PyvkOLxbdzC0q43MfPDck7CgqR2YG9XHrca9cT6c+zE+B\n" + "GhjzOjlAxUCYqwIDAQABo2QwYjAdBgNVHQ4EFgQU3MAhBUHI6S9obysrX38vHiGL\n" + "mIcwHwYDVR0jBBgwFoAU3MAhBUHI6S9obysrX38vHiGLmIcwCwYDVR0PBAQDAgeA\n" + "MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBVjsXwP4Zb\n" + "Ut5XqN8Iy30YqBi90OtfcmWxVnuc7O/HU2ue+PLM3rRyKYVgwY6G/HoRAibqHsLn\n" + "Ga/2jT+qzri68CKfjE/wmBIAgzaKF4XmhOxIxQjTUuxbVd4BmrDmGqsMgWoQ5XnY\n" + "vyBQJ9LTnvvxPk4UvhUjm1/6RbPFqqxVenTUpYS4U3JTv1c9ddu9VSA8ebT4BGBV\n" + "q2iwgTm38xN9C6eS/xGCdLXGIaEQvmfgAPi1Nmw32KrLJfL3oz9bWdYhp9HgfZg2\n" + "Pug5bLDqy8ktyTDdM+q4d+wd3XpKzuLvCIr2q03vrT9j+dMIEOTaqxWQAYiHCYGX\n" + "rU6Ry61UJSeroQAxAA==\n" + "-----END PKCS7-----\n"; - - public static final String KEYSTORE_PASSWORD = "password"; - - public static final String CLIENT_PEM_FILE_NAME = "client/cert.pem"; - public static final String CLIENT_P7B_FILE_NAME = "client/cert.p7b"; - public static final String CLIENT_DER_FILE_NAME = "client/cert.der"; - public static final String CLIENT_P12_FILE_NAME = "client/cert.p12"; - public static final String CLIENT_JKS_FILE_NAME = "client/cert.jks"; - - public static final String CLIENT_PEM_PATH; - public static final String CLIENT_P7B_PATH; - public static final String CLIENT_DER_PATH; - public static final String CLIENT_P12_PATH; - public static final String CLIENT_JKS_PATH; - - - static { - try { - CLIENT_PEM_PATH = Path.of(ClassLoader.getSystemResource(Client.CLIENT_PEM_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - CLIENT_P7B_PATH = Path.of(ClassLoader.getSystemResource(Client.CLIENT_P7B_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - CLIENT_DER_PATH = Path.of(ClassLoader.getSystemResource(Client.CLIENT_DER_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - CLIENT_P12_PATH = Path.of(ClassLoader.getSystemResource(Client.CLIENT_P12_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - CLIENT_JKS_PATH = Path.of(ClassLoader.getSystemResource(Client.CLIENT_JKS_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - public static final Supplier CLIENT_PEM_INPUT_STREAM_SUPPLIER = () -> { - try { - return new FileInputStream(CLIENT_PEM_PATH); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - }; - public static final Supplier CLIENT_P7B_INPUT_STREAM_SUPPLIER = () -> { - try { - return new FileInputStream(CLIENT_P7B_PATH); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - }; - - public static final Supplier CLIENT_DER_INPUT_STREAM_SUPPLIER = () -> { - try { - return new FileInputStream(CLIENT_DER_PATH); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - }; - - public static final Supplier CLIENT_P12_INPUT_STREAM_SUPPLIER = () -> { - try { - return new FileInputStream(CLIENT_P12_PATH); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - }; - public static final Supplier CLIENT_JKS_INPUT_STREAM_SUPPLIER = () -> { - try { - return new FileInputStream(CLIENT_JKS_PATH); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - }; -} diff --git a/src/intTest/java/io/javalin/community/ssl/certs/Server.java b/src/intTest/java/io/javalin/community/ssl/certs/Server.java deleted file mode 100644 index 4380f30..0000000 --- a/src/intTest/java/io/javalin/community/ssl/certs/Server.java +++ /dev/null @@ -1,110 +0,0 @@ -package io.javalin.community.ssl.certs; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.util.function.Supplier; - -public class Server { - public static final String CERTIFICATE_AS_STRING = "-----BEGIN CERTIFICATE-----\n" + "MIIDpjCCAo6gAwIBAgIUKK29nJVFCs8SjBqcvxrg7boyem8wDQYJKoZIhvcNAQEL\n" + "BQAwQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4GA1UECAwH\n" + "R2FsaWNpYTENMAsGA1UEBwwEVmlnbzAgFw0yMjA3MDYxMTQyMDdaGA80MDA1MDMx\n" + "MjExNDIwN1owQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4G\n" + "A1UECAwHR2FsaWNpYTENMAsGA1UEBwwEVmlnbzCCASIwDQYJKoZIhvcNAQEBBQAD\n" + "ggEPADCCAQoCggEBALtW247iPVAuCcQByuqgj8tSzJcwVqCmheT6ld0Xe7DYoLOL\n" + "EsjilB/jgG9aBEBfYJ2h74K7SIdqiSDz4rgUuJUzhZnJo5d3n3wT9Wb2AZcsqFce\n" + "JK0UNBKe2/1b01dFWtQFW4zHC/JM/Gp0dMTy1Vt1Zf/3SmQjSD/KzgJf4m2O/GOP\n" + "3iRFsCSPC4CU3TZCDmI5/qRr4icJCY5s3gJ+RT+edfsvtdkfAO4hK/p+37RrwHax\n" + "nyFLoAzYdJMcnDX/+V7Ez2y7jkTkcUk2gKG+3dpio2XqAE9pXcXa4kYk0NL9Vw6L\n" + "C2QMefFKHLDqLWx/bfQXpbULFawldETDbuLVe7UCAwEAAaOBkTCBjjAdBgNVHQ4E\n" + "FgQUiiPTBoFstcGbb0zYWsM/ZwupRRYwHwYDVR0jBBgwFoAUiiPTBoFstcGbb0zY\n" + "WsM/ZwupRRYwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr\n" + "BgEFBQcDAjAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcN\n" + "AQELBQADggEBAGvqUrtYWZpKBJNYL4UVLnm2+dQl33l8BH7PhU6YvMufThDCVjOw\n" + "IJ7ezOReDlCAmytQD7ChKpsJrAOBzKRdrifL0f88psbE83+6Ys/s/1rHMq282p/S\n" + "WPRiZDVO8Mw2ra9v9b6cprW5phHJkp7TiIBP82A+v19lt3R+vE4HZ91ZyioNqMzf\n" + "Aqvd5gfxHexpilgil0osF0o/8ajSnLiBfWI82Lz/1JB+xUMYW91ahRgt13/54h13\n" + "eL70steoAmx55he3pQaaeRZKzI1nLxsrTkjs055jDn0G/yj1L6kY3OeVFg3AhETJ\n" + "sg+yATMTef2Qskr4dgzb1LJkC9meaU2TFwk=\n" + "-----END CERTIFICATE-----"; - public static final Supplier CERTIFICATE_INPUT_STREAM_SUPPLIER = () -> new ByteArrayInputStream(CERTIFICATE_AS_STRING.getBytes(StandardCharsets.UTF_8)); - public static final String NORWAY_CERTIFICATE_AS_STRING = "-----BEGIN CERTIFICATE-----\n" + "MIIDrjCCApagAwIBAgIUZ8z5/me7+2mTkDnIYyx1dKjwYMQwDQYJKoZIhvcNAQEL\n" + "BQAwRjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJOTzESMBAGA1UECAwJ\n" + "SG9yZGFsYW5kMQ8wDQYDVQQHDAZCZXJnZW4wIBcNMjIxMTI2MTIyNjI1WhgPMzAy\n" + "MjAzMjkxMjI2MjVaMEYxEjAQBgNVBAMMCWxvY2FsaG9zdDELMAkGA1UEBhMCTk8x\n" + "EjAQBgNVBAgMCUhvcmRhbGFuZDEPMA0GA1UEBwwGQmVyZ2VuMIIBIjANBgkqhkiG\n" + "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1bbjuI9UC4JxAHK6qCPy1LMlzBWoKaF5PqV\n" + "3Rd7sNigs4sSyOKUH+OAb1oEQF9gnaHvgrtIh2qJIPPiuBS4lTOFmcmjl3effBP1\n" + "ZvYBlyyoVx4krRQ0Ep7b/VvTV0Va1AVbjMcL8kz8anR0xPLVW3Vl//dKZCNIP8rO\n" + "Al/ibY78Y4/eJEWwJI8LgJTdNkIOYjn+pGviJwkJjmzeAn5FP551+y+12R8A7iEr\n" + "+n7ftGvAdrGfIUugDNh0kxycNf/5XsTPbLuORORxSTaAob7d2mKjZeoAT2ldxdri\n" + "RiTQ0v1XDosLZAx58UocsOotbH9t9BeltQsVrCV0RMNu4tV7tQIDAQABo4GRMIGO\n" + "MB0GA1UdDgQWBBSKI9MGgWy1wZtvTNhawz9nC6lFFjAfBgNVHSMEGDAWgBSKI9MG\n" + "gWy1wZtvTNhawz9nC6lFFjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB\n" + "BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocEfwAAATAN\n" + "BgkqhkiG9w0BAQsFAAOCAQEAd+wmmJf/LYXPWhIAf/qBa7HOH1gbzOQ1CMH6qyxm\n" + "ueH4MdUr2tewC53M0eYVfEMwOc4NVBmXXKLANkDByjBKGObr4N5cHjyUI1zk0Eew\n" + "tkkv8IoHl9pJTewNCdDkgCPADRoQ//gOkqpzxh3uEdhjHAB/KlyylUkxnbAJH9ap\n" + "v7V2Ju/yOjXtA0Pl+fdaeYg1Y8TVKLJSUqSR2CFHQgodxMsSG/l2QbpHXVIt50sm\n" + "fQxiURVXr/qqP7KubKb2MzRQWVIQemlK3FJgVoY4Mp3zCmSBBq/N2Dioudu/XQQu\n" + "tkLrsE6joYC2ST27wJDgYgAY7CmFelbIRZZl94FzR4Jp5A==\n" + "-----END CERTIFICATE-----"; - public static final String NON_ENCRYPTED_KEY_AS_STRING = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VtuO4j1QLgnE\n" + "AcrqoI/LUsyXMFagpoXk+pXdF3uw2KCzixLI4pQf44BvWgRAX2Cdoe+Cu0iHaokg\n" + "8+K4FLiVM4WZyaOXd598E/Vm9gGXLKhXHiStFDQSntv9W9NXRVrUBVuMxwvyTPxq\n" + "dHTE8tVbdWX/90pkI0g/ys4CX+Jtjvxjj94kRbAkjwuAlN02Qg5iOf6ka+InCQmO\n" + "bN4CfkU/nnX7L7XZHwDuISv6ft+0a8B2sZ8hS6AM2HSTHJw1//lexM9su45E5HFJ\n" + "NoChvt3aYqNl6gBPaV3F2uJGJNDS/VcOiwtkDHnxShyw6i1sf230F6W1CxWsJXRE\n" + "w27i1Xu1AgMBAAECggEAfPI7UZr3BckO3lnLup0ICrXYmmW1AUTPPJ8c4O7Oom55\n" + "EAaLqsvjuzkC6kGBYGW8jKX6lpjOkPKvLvk6l0fKrEhGrQFdSKKSDjFJlTgya19v\n" + "j1sdXwqAiILHer2JwUUShSJlowkGoL5UA7RURR8oye0M8KFATnVxtIpQyCinXiW/\n" + "LkDuqUr8MIbu6V/KcoSOLfJyTWyuwSRPHuFKhv154UAqaTkSPbf2mCTa9hH5Tb4f\n" + "Lfzy9o3Ux4ieZceG28De+SmC7uMzbBs1stowOuDmFg3znI/1Br/sQEAXPFngDe3s\n" + "soDD2PbLo7/4SPBNgl5vygf7jhvxHPY3DTUXOxLSgQKBgQD4EzKVTx/GpF7Yswma\n" + "oixidzSi/KnHJiMjIERF4QPVfDNnggRORNMbPnRhNWSRhS7r+INYbN4yB/vBZO5I\n" + "IIqowdJbLjGbmq91equP0zzrP2wCjqtFK6gRElX7acAWY5xTesIT5Fa1Ug++dFLS\n" + "MxCZKL6JMZaHJzZVzXugaltMsQKBgQDBUvPSaDnIBrZGdNtAyNMxZyVbp/ObIKW1\n" + "TvCDX2hqf+yiTVclbZr5QkwCE3MHErfsKlWU01K9CtzsQh4u9L5tPaeFlvm6iZq6\n" + "ktbflNvI+z+qEW3JbROR4WwwbmWFvKRLBA0OQom7tGuNnNyRtkDFxlkFJPcD6Eff\n" + "ZEq+ewrQRQKBgQCV7URM6J0TuJN58/qB8jFQ8Spmtr0FFw91UzLv6KYgiAepLvLb\n" + "Os07UeuUNGiragqJoo//CQzgv+JvZ0h7Xu9uPnWblbd1i28vWQwGyGuw4Yutn/vy\n" + "ugfBCYvdfnQRE/KOoUpaK04cF5RcToEfeK03Y2CEGewXkqNMB/wHXz/+gQKBgE8Y\n" + "34WQ+0Mp69375dEl2bL23sQXfYZU3zfFaoZ1vMUGPg1R03wO0j91rp+S0ZdtQy8v\n" + "SwCvTcTm8uj/TFYt8NPFTAtOcDKwJkx708p6n0ol8jBlHSQyqrUfJCLUqFkFi7rd\n" + "l3HkK3JPKUoxidVcWjgRJU8DhsVkfjOaVzKEKTJ5AoGARBwn7gt2H35urQ6/U3nJ\n" + "hFjOVn01F5uV0NvRtRDCsAIUMeA2T4pwALUUIqlA9HmpwYgLeG4bZ+SkhNpy70N/\n" + "qcufT1DeM+q3H5zFPANyjcqVaqa6KUnttvi/lhxMdRb6GsA9TzzHzY1P9ovpIOCK\n" + "IS639NPzxpI0Ka+v6t+nFEM=\n" + "-----END PRIVATE KEY-----\n"; - public static final Supplier NON_ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER = () -> new ByteArrayInputStream(NON_ENCRYPTED_KEY_AS_STRING.getBytes(StandardCharsets.UTF_8)); - public static final String ENCRYPTED_KEY_AS_STRING = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIMDP+/JKdUc4CAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBRfpz0ZTscSvALfUISuNYIBIIE\n" + "0I8rVF2h/qQANJ3WvXFcmm7dFqEiQwUm8cDxaDpPd8RRweclTesEj70yg+3xcGLh\n" + "rhSFrNSB2wmy/jB6lFcN02KcSU3p6H7aSuLRffbYYAQ3LGU6Ie79NW68x189zB/b\n" + "sDi6gWkxHCrGzBydKR4a6ZvF9TMnP743hCw3t3NrO/4xdoZ9+YaxmBaBjt4E1Bns\n" + "J2yCHHV5kXXsWOZJvTTWxf+fIEQNe1cjidBxcpvQxreZpOsday2KM8tctom+p9lw\n" + "DEF0mhUi/FHZZnmfgr1Cz4+PmspjOTykX+0RWD1wi0kMJwqo6aRHwlEbEE+f83Df\n" + "kazqIXOfD0VrzSXTwr1kIzjQI+DK8sKyfg5lfTby1AFy5cvtJxL7cK6As9Cq05XI\n" + "i2fX5PWUj1sHplMOI2+qh31R7w6qb0DygXC22ZFNLlFYwP0QKPp9XzZZLIvPI662\n" + "9xlF4VgtcS9JV7hztrg6Bbc23l1cSsBXPqreWd39NM/Kggf6J3GV/P0AacYYp0OY\n" + "A3Pt9i+RV+HHv8OwfZ+v4RH8hVhtDkPWyBJX581zwF5OQLqjksKa1FNC8qB/VlE4\n" + "Ponm33vn1gWtKY962sYoJxDVHbgWwpWP7bSqtO66jiwlTEyQwltd3LbZGcJ4yJNd\n" + "eEJUE8vFHyXmEx1KoDHUh2/v9l6pdo59PgBlY8mxl95AuTNds+dtuqZQE6ZNZvDZ\n" + "lw7dOp+BATb/3YWCF5O6jQjm11ji9kZxgnPBTSiaegFFIa4OxdQwP2pxbyVk4rGH\n" + "/gs6olWtc3hqqQspMJDqT2cJeaE61us7hUya1w1LjivOvofR3Zt2v2Wtxmm+ey3e\n" + "/mPBZH1LIRdP9vEuHxKkjjppXVRWL5TdHeN9Ai6jG3/WCp/NgBnjOi9/5GVX7j+T\n" + "dUzGBaxwUGt10QZ8Yvo9qT8Cg2tiUD770EzaD09aiRfoAs7YwLsVi35gul+JyNrD\n" + "CzqZ8I2NZ6Uo/r9I9Xda9qkoxbS2hNg+53whm5L2fT4SrJ69MOY/tM3mR8q1Ta18\n" + "W+dXuFSD+3nAU7Aqug4LlKcOS9/RW18kRtHRVatXZscxITO5dlgmw7zEVzrkwa+q\n" + "r1y4YG488XZZ3KCXPJthnmP4nIYERW6hn62P8EKzM8wfxxT39O96QNzMgszor/WA\n" + "TG8o0JDRvG5WW/OfVA4Ls8QK6lx3E2cPhyqnvM+HirtP2xL4Gd0SibGIK7QvewSf\n" + "9a5TnbQsuoWvTqtzX7PEb9snLxQjaxTLZEbTyimwEyaaZ1Ev72did2EdmA3UEtzq\n" + "e+X86mvYOZrAJIWNGIfoMI3QPtxlC2MbDjUcLB5crk90T2dCcIdwpr6cKGdnEqP+\n" + "DmkkwTl84MSV2tVQ2qCJPtiwsR8V2xkwqesD1p0G5whR2SxsUQDoG48l1zRkLrA5\n" + "PbwUii9Xapp5+R08t+dIt19cRJyewjAKxpWkKHNjtXmBMJpvJ65A4wAAV5vqcTdY\n" + "FIrJEMySqFDrodCwkAs9s8FKIWvEnWKkaX2NvjoTWdQEGmKpiEazUsknd4wNX8js\n" + "MjjY/VHqWNYR6cF84H+WuFS86S37Vt3nBEpos0vp9n8epNNC+ETcewKMgovLJMnt\n" + "na5mQXa7ctzrJ+bqW9B+QLBX6KZk3tRnigYO6Fum0t7I\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; - public static final Supplier ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER = () -> new ByteArrayInputStream(ENCRYPTED_KEY_AS_STRING.getBytes(StandardCharsets.UTF_8)); - public static final String GOOGLE_CERTIFICATE_AS_STRING = "-----BEGIN CERTIFICATE-----\n" + "MIIDXTCCAkWgAwIBAgIULxRctoyJitKoVft6Dn6F458+uK8wDQYJKoZIhvcNAQEL\n" + "BQAwJzETMBEGA1UEAwwKZ29vZ2xlLmNvbTEQMA4GA1UECgwHSmF2YWxpbjAgFw0y\n" + "MjA4MDgwOTE4NDRaGA8yMTIyMDcxNTA5MTg0NFowJzETMBEGA1UEAwwKZ29vZ2xl\n" + "LmNvbTEQMA4GA1UECgwHSmF2YWxpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" + "AQoCggEBANgf9ywzZUgab2y9WIJTf4H6rxMITbRDC3xYJRVEHc8WgegrlPwXw4V/\n" + "Qr92xChowUKZBQX98yvLXMc0YziRmqY+m5IriaWY7Jvr82pkHkLKWyjzFmHZBMAd\n" + "lWWjDKDD5abKPhsPu4tJg3YedRm7SjZIm7rpj+rEau4ALGRonM8L4jpqZJ+Jg8Sq\n" + "DDKTnVGLiYuhxtmby+/PRV/mhmJJ6dPOOcdIQlIn1PCrUDJbt2zMkuflrVzl+6eY\n" + "7GYq5h+QqCgO2XK+6q5RQBxtMUIp8Bi0AR6j+g3yAE6uiAPXhDQOj+fzKRJhoYcf\n" + "cQ84yxzN4benHPPPfLw42rquA7qB/BECAwEAAaN/MH0wHQYDVR0OBBYEFCIswSAR\n" + "oq1RbOP1ygckApt9wHPKMB8GA1UdIwQYMBaAFCIswSARoq1RbOP1ygckApt9wHPK\n" + "MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\n" + "DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAi4zGlp2DmaH9sMiRgqA5\n" + "dDO8UbrW1TNNES7Dwo1519sERiosCzGVBqjVOzUzFYyrW/jF8kKd8NoIZSlWvoeQ\n" + "A0+6dxYy7oNY0UTJbW25hSRXMF1FCnxBfLLZ1J9lowzhts3yx5REJZVWEvsF0Agy\n" + "qgNEkKYSaeUtuSzUhMVPGs9AuMwl/M0M1q+2WBMeDLaGXhAXJC5jZ47BkEVgnz5+\n" + "/IIWbFJQ+eGEgL+GVFCxgebvJwncPruDipaS7i486kYyoymBKiSXeUN+z+gdaIHk\n" + "YHuSkRVAU4BiSGd72UK+KWIYBttkeINcYLRyZbdYkY5sgBGTJnj2ke0vnM13o7UM\n" + "jw==\n" + "-----END CERTIFICATE-----\n"; - public static final String GOOGLE_KEY_AS_STRING = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYH/csM2VIGm9s\n" + "vViCU3+B+q8TCE20Qwt8WCUVRB3PFoHoK5T8F8OFf0K/dsQoaMFCmQUF/fMry1zH\n" + "NGM4kZqmPpuSK4mlmOyb6/NqZB5Cylso8xZh2QTAHZVlowygw+Wmyj4bD7uLSYN2\n" + "HnUZu0o2SJu66Y/qxGruACxkaJzPC+I6amSfiYPEqgwyk51Ri4mLocbZm8vvz0Vf\n" + "5oZiSenTzjnHSEJSJ9Twq1AyW7dszJLn5a1c5funmOxmKuYfkKgoDtlyvuquUUAc\n" + "bTFCKfAYtAEeo/oN8gBOrogD14Q0Do/n8ykSYaGHH3EPOMsczeG3pxzzz3y8ONq6\n" + "rgO6gfwRAgMBAAECggEAOCrWid4xjDOSkagDwJsCoD0OEtwtlZN3ALHHsWcqeA9Z\n" + "Y4UwCvQCFEemiSvMftP6pdwuugftkowfaIXs416z2lCbDbnS4/6CP2Nqt1Odqa39\n" + "Uv8Z6gQEgAkwMmHVflJq9JXK3i2Qh/pq99+ifzV1a/YiwsjAZjr1rzTMVKv7VLM/\n" + "cRwEffg/kus8b/RDUVDcTFlX2drypfAI9T2P7kaiMLVKI7tcWnn1RPUdUqlG8LjB\n" + "4ZyyZXNGNrd93zYWWe1DflUwP187SyoGbsn0vQe15n0U7cVOE8xLOBNFJiKSUJGi\n" + "hdUUeIgXZS9MGTfQN6QldRsFNlv6DGDvSzocqtl7gQKBgQD6P96IbtQeD/UwVKb1\n" + "1KzvDzdzlxT4XG7e2ZPj71tG9d84V72PDPAYnzftPJRkD08lDJLvAvKqqKCPTX3V\n" + "QgauF2njX1XLa649E4/hb5VHnJQj9Fwlx3/KUaNWI8zu3PR+5gKp/dlJiJz0v84T\n" + "gNw/VYhzfXyjPtbJXYrij8QpRQKBgQDdF1pbBKDAkBwoiOa2jgBtjnzHm7SxLCqq\n" + "/BBsKT54q8yxdTsY6MZ4J+497Z+AL65zHKMgcDsMEOvKDqLhurvqvWzUGTCv/5St\n" + "xOuwJ0k4kIVrQEBvvAJmBT44WOQZM3M9vxbwoG2mjfsSPz7OdrkK+hnwBlu91AjK\n" + "mP9rKa/mXQKBgQDYbfSgOnnpphOAQTZE1jLabmae6cORKSAaTELDl3dx36O2ruua\n" + "lK3yHYHZA9Oy1iq0+DL706jcQArc5UA2+GuelVFW/FTPIcoHuKtvZXnN/XWBww0O\n" + "/4NeD00casoKq74pIfSb4JfUKPrWEizAYWoavHbOq3DoHqjUbrp3R693oQKBgA7i\n" + "T5bpDNlp2jtwW/fWP3kgqo3VkaiLzKOOLJzbefUtu64Gsl/O6+2S4psQsDg0/Y2K\n" + "VAEPDSqWyQjlS1ne9F+tOPJeb8SpdBzusN8/BdLlB9ZckPn0skSj/bhVY6W+rPdv\n" + "MeApLLiVvl1QHK5Rl8uBYtWh1/NDnwPkoO1Z9RmRAoGALzIURD5Dg9FCpd9ym+UZ\n" + "JWI9lPvbL3uBzD53ys0dtzoSaTayishooeggYYJEbYpAxNH0WE1M7dqZH/OjTaAO\n" + "Kp7LluqrRTUGMYHogBX485sCWhZ91r4RqPa90UcUcpjXUnVu7Absn7/FOcT1z++M\n" + "6HNWxu22y49Nc0iAEtqCOVk=\n" + "-----END PRIVATE KEY-----\n"; - public static final String CERTIFICATE_FILE_NAME = "server/cert.crt"; - public static final String ENCRYPTED_KEY_FILE_NAME = "server/encrypted.key"; - public static final String NON_ENCRYPTED_KEY_FILE_NAME = "server/passwordless.key"; - public static final String CERTIFICATE_PATH; - public static final String ENCRYPTED_KEY_PATH; - public static final String NON_ENCRYPTED_KEY_PATH; - public static final String KEY_PASSWORD = "password"; - public static final String JKS_KEY_STORE_NAME = "server/keystore.jks"; - public static final Supplier JKS_KEY_STORE_INPUT_STREAM_SUPPLIER = () -> { - try { - return ClassLoader.getSystemResource(JKS_KEY_STORE_NAME).openStream(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }; - public static final String P12_KEY_STORE_NAME = "server/keystore.p12"; - public static final Supplier P12_KEY_STORE_INPUT_STREAM_SUPPLIER = () -> { - try { - return ClassLoader.getSystemResource(P12_KEY_STORE_NAME).openStream(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }; - public static final String NORWAY_JKS_KEY_STORE_NAME = "server/norway.jks"; - public static final String NORWAY_P12_KEY_STORE_NAME = "server/norway.p12"; - public static final String JKS_KEY_STORE_PATH; - public static final String P12_KEY_STORE_PATH; - public static final String NORWAY_JKS_KEY_STORE_PATH; - public static final String NORWAY_P12_KEY_STORE_PATH; - public static final String KEY_STORE_PASSWORD = "password"; - - static { - try { - CERTIFICATE_PATH = Path.of(ClassLoader.getSystemResource(CERTIFICATE_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - ENCRYPTED_KEY_PATH = Path.of(ClassLoader.getSystemResource(ENCRYPTED_KEY_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - NON_ENCRYPTED_KEY_PATH = Path.of(ClassLoader.getSystemResource(NON_ENCRYPTED_KEY_FILE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - - static { - try { - JKS_KEY_STORE_PATH = Path.of(ClassLoader.getSystemResource(JKS_KEY_STORE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - P12_KEY_STORE_PATH = Path.of(ClassLoader.getSystemResource(P12_KEY_STORE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - NORWAY_JKS_KEY_STORE_PATH = Path.of(ClassLoader.getSystemResource(NORWAY_JKS_KEY_STORE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - - static { - try { - NORWAY_P12_KEY_STORE_PATH = Path.of(ClassLoader.getSystemResource(NORWAY_P12_KEY_STORE_NAME).toURI()).toAbsolutePath().toString(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } -} - - diff --git a/src/main/java/io/javalin/community/ssl/SSLConfig.java b/src/main/java/io/javalin/community/ssl/SSLConfig.java deleted file mode 100644 index 5ac2a74..0000000 --- a/src/main/java/io/javalin/community/ssl/SSLConfig.java +++ /dev/null @@ -1,396 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.community.ssl.util.SSLUtils; -import lombok.Getter; -import org.eclipse.jetty.server.ServerConnector; -import org.jetbrains.annotations.Nullable; - -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.Provider; -import java.util.function.Consumer; - -/** - * Data class to hold the configuration for the plugin. - */ -@SuppressWarnings("unused") -public class SSLConfig { - - /** - * Host to bind to. - */ - public String host = null; - - /** - * Toggle the default http (insecure) connector. - */ - public boolean insecure = true; - - /** - * Toggle the default https (secure) connector. - */ - public boolean secure = true; - - /** - * Port to use on the SSL (secure) connector. - */ - public int securePort = 443; - - /** - * Port to use on the http (insecure) connector. - */ - public int insecurePort = 80; - - /** - * Enable http to https redirection. - */ - public boolean redirect = false; - - /** - * Toggle HTTP/2 Support - */ - public boolean http2 = true; - - /** - * Disable SNI checks. - * @see Configuring SNI - */ - public boolean sniHostCheck = true; - - /** - * TLS Security configuration - */ - public TLSConfig tlsConfig = TLSConfig.INTERMEDIATE; - - /** - * Enables HTTP/3 Support. - * Disabled by default because it is not yet working on Jetty. - */ - public final boolean enableHttp3 = false; - - /** - * Disables the handler that adds an "Alt-Svc" header to any non HTTP/3 response. - * Disabled by default because it is not yet working on Jetty. - */ - public final boolean disableHttp3Upgrade = false; - - /** - * UDP Port to use on the HTTP/3 connector. - * Disabled by default because it is not yet working on Jetty. - */ - public final int http3Port = 443; - - public InnerConfig inner = new InnerConfig(); - - /** - * Configuration for the SSL (secure) connector, meant to be accessed using its setters. - */ - public static class InnerConfig { - - /** - * Type of identity loading types. - */ - public enum IdentityLoadingType { - NONE, - PEM_CLASS_PATH, - PEM_FILE_PATH, - PEM_STRING, - PEM_INPUT_STREAM, - KEY_STORE_CLASS_PATH, - KEY_STORE_FILE_PATH, - KEY_STORE_INPUT_STREAM - } - - @Getter - IdentityLoadingType identityLoadingType = IdentityLoadingType.NONE; - - /** - * Name of the certificate chain PEM file in the classpath. - */ - @Nullable - public String pemCertificatesFile = null; - - /** - * Name of the private key PEM file in the classpath. - */ - @Nullable - public String pemPrivateKeyFile = null; - - /** - * Input stream to the certificate chain PEM file. - */ - @Nullable - public InputStream pemCertificatesInputStream = null; - - /** - * Input stream to the private key PEM file. - */ - @Nullable - public InputStream pemPrivateKeyInputStream = null; - - /** - * Path to the PEM certificate chain PEM file. - */ - @Nullable - public Path pemCertificatesPath = null; - - /** - * Path to the private key PEM file. - */ - @Nullable - public Path pemPrivateKeyPath = null; - - /** - * Certificate chain as a PEM encoded string. - */ - @Nullable - public String pemCertificatesString = null; - - /** - * Private key as a PEM encoded string. - */ - @Nullable - public String pemPrivateKeyString = null; - - /** - * Password for the private key. - */ - @Nullable - public String privateKeyPassword = null; - - /** - * Path to the key store file. - */ - @Nullable - public Path keyStorePath = null; - - - /** - * Name of the key store file in the classpath. - */ - @Nullable - public String keyStoreFile = null; - - /** - * Input stream to the key store file. - */ - @Nullable - public InputStream keyStoreInputStream = null; - - /** - * Password for the key store. - */ - @Nullable - public String keyStorePassword = null; - } - - /////////////////////////////////////////////////////////////// - // PEM Loading Methods - /////////////////////////////////////////////////////////////// - - /** - * Load pem formatted identity data from a given path in the system. - * - * @param certificatePath path to the certificate chain PEM file. - * @param privateKeyPath path to the private key PEM file. - */ - public void pemFromPath(String certificatePath, String privateKeyPath) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.pemCertificatesPath = Paths.get(certificatePath); - inner.pemPrivateKeyPath = Paths.get(privateKeyPath); - inner.identityLoadingType = InnerConfig.IdentityLoadingType.PEM_FILE_PATH; - } - - /** - * Load pem formatted identity data from a given path in the system. - * - * @param certificatePath path to the certificate chain PEM file. - * @param privateKeyPath path to the private key PEM file. - * @param privateKeyPassword password for the private key. - */ - public void pemFromPath(String certificatePath, String privateKeyPath, String privateKeyPassword) { - pemFromPath(certificatePath, privateKeyPath); - inner.privateKeyPassword = privateKeyPassword; - } - - - /** - * Load pem formatted identity data from the classpath. - * - * @param certificateFile The name of the pem certificate file in the classpath. - * @param privateKeyFile The name of the pem private key file in the classpath. - */ - public void pemFromClasspath(String certificateFile, String privateKeyFile) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.pemCertificatesFile = certificateFile; - inner.pemPrivateKeyFile = privateKeyFile; - inner.identityLoadingType = InnerConfig.IdentityLoadingType.PEM_CLASS_PATH; - } - - /** - * Load pem formatted identity data from the classpath. - * - * @param certificateFile The name of the pem certificate file in the classpath. - * @param privateKeyFile The name of the pem private key file in the classpath. - * @param privateKeyPassword password for the private key. - */ - public void pemFromClasspath(String certificateFile, String privateKeyFile, String privateKeyPassword) { - pemFromClasspath(certificateFile, privateKeyFile); - inner.privateKeyPassword = privateKeyPassword; - } - - - /** - * Load pem formatted identity data from a given input stream. - * - * @param certificateInputStream input stream to the certificate chain PEM file. - * @param privateKeyInputStream input stream to the private key PEM file. - */ - public void pemFromInputStream(InputStream certificateInputStream, InputStream privateKeyInputStream) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.pemCertificatesInputStream = certificateInputStream; - inner.pemPrivateKeyInputStream = privateKeyInputStream; - inner.identityLoadingType = InnerConfig.IdentityLoadingType.PEM_INPUT_STREAM; - } - - /** - * Load pem formatted identity data from a given input stream. - * - * @param certificateInputStream input stream to the certificate chain PEM file. - * @param privateKeyInputStream input stream to the private key PEM file. - * @param privateKeyPassword password for the private key. - */ - public void pemFromInputStream(InputStream certificateInputStream, InputStream privateKeyInputStream, String privateKeyPassword) { - pemFromInputStream(certificateInputStream, privateKeyInputStream); - inner.privateKeyPassword = privateKeyPassword; - } - - - /** - * Load pem formatted identity data from a given string. - * - * @param certificateString PEM encoded certificate chain. - * @param privateKeyString PEM encoded private key. - */ - public void pemFromString(String certificateString, String privateKeyString) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.pemCertificatesString = certificateString; - inner.pemPrivateKeyString = privateKeyString; - inner.identityLoadingType = InnerConfig.IdentityLoadingType.PEM_STRING; - } - - /** - * Load pem formatted identity data from a given string. - * - * @param certificateString PEM encoded certificate chain. - * @param privateKeyString PEM encoded private key. - * @param privateKeyPassword password for the private key. - */ - public void pemFromString(String certificateString, String privateKeyString, String privateKeyPassword) { - pemFromString(certificateString, privateKeyString); - inner.privateKeyPassword = privateKeyPassword; - } - - /////////////////////////////////////////////////////////////// - // Key Store Loading Methods - /////////////////////////////////////////////////////////////// - - - /** - * Load a key store from a given path in the system. - * @param keyStorePath path to the key store file. - * @param keyStorePassword password for the key store. - */ - public void keystoreFromPath(String keyStorePath, String keyStorePassword) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.keyStorePath = Paths.get(keyStorePath); - inner.identityLoadingType = InnerConfig.IdentityLoadingType.KEY_STORE_FILE_PATH; - inner.keyStorePassword = keyStorePassword; - } - - /** - * Load a key store from a given input stream. - * @param keyStoreInputStream input stream to the key store file. - * @param keyStorePassword password for the key store. - */ - public void keystoreFromInputStream(InputStream keyStoreInputStream, String keyStorePassword) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.keyStoreInputStream = keyStoreInputStream; - inner.identityLoadingType = InnerConfig.IdentityLoadingType.KEY_STORE_INPUT_STREAM; - inner.keyStorePassword = keyStorePassword; - } - - /** - * Load a key store from the classpath. - * @param keyStoreFile name of the key store file in the classpath. - * @param keyStorePassword password for the key store. - */ - public void keystoreFromClasspath(String keyStoreFile, String keyStorePassword) { - if (inner.identityLoadingType != InnerConfig.IdentityLoadingType.NONE) { - throw new SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS); - } - inner.keyStoreFile = keyStoreFile; - inner.identityLoadingType = InnerConfig.IdentityLoadingType.KEY_STORE_CLASS_PATH; - inner.keyStorePassword = keyStorePassword; - } - - /////////////////////////////////////////////////////////////// - // Advanced Options - /////////////////////////////////////////////////////////////// - - /** - * Consumer to configure the different {@link ServerConnector} that will be created. - * This consumer will be called as the last config step for each connector, - * allowing to override any previous configuration. - * @deprecated Use {@link #configConnectors(Consumer)} instead, access modifier will be changed - * to private in the next major release. - */ - @Getter - @Deprecated(forRemoval = true, since = "5.3.2") - public Consumer configConnectors = null; - - /** - * Consumer to configure the different {@link ServerConnector} that will be created. - * This consumer will be called as the last config step for each connector, allowing to override any previous configuration. - */ - public void configConnectors(Consumer configConnectors) { - this.configConnectors = configConnectors; - } - - /** - * Security provider to use for the SSLContext. - */ - public Provider securityProvider = null; - - /////////////////////////////////////////////////////////////// - // Trust Store - /////////////////////////////////////////////////////////////// - - /** - * Trust store configuration for the server, if not set, every client will be accepted. - */ - @Getter - private TrustConfig trustConfig = null; - - /** - * Trust configuration as a consumer. - * @param trustConfigConsumer consumer to configure the trust configuration. - */ - public void withTrustConfig(Consumer trustConfigConsumer) { - trustConfig = new TrustConfig(); - trustConfigConsumer.accept(trustConfig); - } -} diff --git a/src/main/java/io/javalin/community/ssl/SSLConfigException.java b/src/main/java/io/javalin/community/ssl/SSLConfigException.java deleted file mode 100644 index 8c4eb84..0000000 --- a/src/main/java/io/javalin/community/ssl/SSLConfigException.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.javalin.community.ssl; - -/** - * Exception thrown when the SSLConfig is invalid. - */ -public class SSLConfigException extends RuntimeException { - public SSLConfigException(String message) { - super(message); - } - - public SSLConfigException(Types type) { - super(type.getMessage()); - } - public SSLConfigException(Types type, String extraInformation) { - super(type.getMessage() + ": " + extraInformation); - } - - /** - * Types of errors that can occur when configuring SSL. - */ - public enum Types { - INVALID_HOST("Invalid host provided"), - INVALID_SSL_PORT("Invalid SSL port provided"), - INVALID_INSECURE_PORT("Invalid insecure port provided"), - INVALID_HTTP3_PORT("Invalid HTTP3 port provided"), - MISSING_CERT_AND_KEY_FILE("There is no certificate or key file provided"), - MULTIPLE_IDENTITY_LOADING_OPTIONS("Both the certificate and key must be provided using the same method"); - - private final String message; - - Types(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - } -} diff --git a/src/main/java/io/javalin/community/ssl/SSLPlugin.java b/src/main/java/io/javalin/community/ssl/SSLPlugin.java deleted file mode 100644 index ebf96ed..0000000 --- a/src/main/java/io/javalin/community/ssl/SSLPlugin.java +++ /dev/null @@ -1,192 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.Javalin; -import io.javalin.community.ssl.util.ConnectorFactory; -import io.javalin.jetty.JettyUtil; -import io.javalin.plugin.Plugin; -import nl.altindag.ssl.SSLFactory; -import nl.altindag.ssl.util.SSLFactoryUtils; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.SecuredRedirectHandler; -import org.eclipse.jetty.util.ssl.SslContextFactory; -import org.jetbrains.annotations.NotNull; - -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; - -import static io.javalin.community.ssl.util.SSLUtils.createSslContextFactory; -import static io.javalin.community.ssl.util.SSLUtils.getSslFactory; - -/** - * A plugin to easily enable SSL on a Javalin server. - *

- * The intended configuration pattern is to use the {@link SSLPlugin#SSLPlugin(Consumer)} constructor to configure the - * plugin, either though a lambda or a Consumer. This allows for a more fluent configuration pattern. - *

- * Hot reloading of the SSL certificates is supported. This means that the plugin will replace the SSL certificates without restarting the server, allowing for a seamless transition. This is done by using the {@link SSLPlugin#reload(Consumer)} method. - * - * @author Alberto Zugazagoitia - * @see SSLConfig - */ -public class SSLPlugin implements Plugin { - - private final SSLConfig config; - - private SSLFactory sslFactory = null; - - /** - * Creates a new SSLPlugin with the default configuration. - */ - public SSLPlugin() { - config = new SSLConfig(); - } - - /** - * Creates a new SSLPlugin with the given configuration. - * - * @param config The configuration to use. - */ - public SSLPlugin(SSLConfig config) { - this.config = config; - } - - /** - * Creates a new SSLPlugin with the given configuration lambda/supplier. - * This is useful if you want to use the default configuration and only change a few values. - * Recommended way to create a new SSLPlugin with the given configuration. - * - * @param config The configuration to use. - */ - public SSLPlugin(Consumer config) { - this(); - config.accept(this.config); - } - - - /** - * Method to apply the plugin to a Javalin instance - * - * @param javalin Javalin instance - */ - @Override - public void apply(@NotNull Javalin javalin) { - - Consumer patcher = createJettyServerPatcher(config); - - javalin.cfg.jetty.server(() -> { - - //Check if the server has been manually configured - Server server = Objects.requireNonNullElseGet( - javalin.cfg.pvt.server, - SSLPlugin::getServer); - - //parseConfig returns a consumer configuring the server. - patcher.accept(server); - - return server; - }); - - } - - /** - * Method to apply the SSLConfig to a given Jetty Server. - * Can be used to patch pre-existing or custom servers. - * - * @param server The Jetty Server to patch. - */ - public void patch(@NotNull Server server) { - Consumer patcher = createJettyServerPatcher(config); - patcher.accept(server); - } - - - /** - * Method to hot-swap the certificate and key material of the plugin. - * Any configuration changes will be ignored, only the certificate and key material will be updated. - * - * @param newConfig A config containing the new certificate and key material. - * @deprecated Use {@link #reload(Consumer)} instead. - */ - @Deprecated(forRemoval = true,since = "5.3.2") - public void reload(SSLConfig newConfig) { - if(sslFactory == null) - throw new IllegalStateException("Cannot reload before the plugin has been applied to a Javalin instance, a server has been patched or if the ssl connector is disabled."); - - SSLFactory newFactory = getSslFactory(newConfig,true); - SSLFactoryUtils.reload(sslFactory, newFactory); - } - - /** - * Method to hot-swap the certificate and key material of the plugin. - * Any configuration changes will be ignored, only the certificate and key material will be updated. - * - * @param newConfig A consumer providing the new certificate and key material. - */ - public void reload(Consumer newConfig) { - SSLConfig conf = new SSLConfig(); - newConfig.accept(conf); - if(sslFactory == null) - throw new IllegalStateException("Cannot reload before the plugin has been applied to a Javalin instance, a server has been patched or if the ssl connector is disabled."); - - SSLFactory newFactory = getSslFactory(conf,true); - SSLFactoryUtils.reload(sslFactory, newFactory); - } - - /** - * Method to parse the config and return a consumer that can be used to configure the server. - * - * @param config The config to parse. - * @return A {@link Consumer} that can be used to configure the server. - */ - private Consumer createJettyServerPatcher(SSLConfig config) { - - //Created outside the lambda to have exceptions thrown in the current scope - SslContextFactory.Server sslContextFactory; - - if (config.secure || config.enableHttp3) { - sslFactory = getSslFactory(config); - sslContextFactory = - createSslContextFactory(sslFactory, config); - } else { - sslContextFactory = null; - } - - return (server) -> { - - List connectorList = new LinkedList<>(); - ConnectorFactory connectorFactory = new ConnectorFactory(config, server, sslContextFactory); - - if (config.insecure) { - connectorList.add(connectorFactory.createInsecureConnector()); - } - - if (config.secure) { - connectorList.add(connectorFactory.createSecureConnector()); - } - - if (config.enableHttp3) { - throw new UnsupportedOperationException("HTTP/3 is not supported yet"); - } - - connectorList.forEach(server::addConnector); - - if(config.redirect) - server.setHandler(new SecuredRedirectHandler()); - }; - } - - /** - * Method to create a new Jetty Server instance using Javalin's default configuration. - * - * @return A new Jetty Server instance. - */ - private static Server getServer() { - return JettyUtil.getOrDefault(null); - } - - -} - diff --git a/src/main/java/io/javalin/community/ssl/TLSConfig.java b/src/main/java/io/javalin/community/ssl/TLSConfig.java deleted file mode 100644 index 347cd7f..0000000 --- a/src/main/java/io/javalin/community/ssl/TLSConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.javalin.community.ssl; - - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Value; - -/** - * Data class for the SSL configuration. - * - * @see Security/Server Side TLS - */ -@Value -public class TLSConfig { - - private static final String GUIDELINES_VERSION = "5.5"; - - /** - * For modern clients that support TLS 1.3, with no need for backwards compatibility - */ - @Getter(AccessLevel.NONE) - public static TLSConfig MODERN = new TLSConfig( - new String[]{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"}, - new String[]{"TLSv1.3"}); - - /** - * Recommended configuration for a general-purpose server - */ - @Getter(AccessLevel.NONE) - public static TLSConfig INTERMEDIATE = new TLSConfig( - new String[]{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"}, - new String[]{"TLSv1.2", "TLSv1.3"}); - - /** - * For services accessed by very old clients or libraries, such as Internet Explorer 8 (Windows XP), Java 6, or OpenSSL 0.9.8 - */ - @Getter(AccessLevel.NONE) - public static TLSConfig OLD = new TLSConfig( - new String[]{"TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256","TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "TLS_RSA_WITH_AES_256_GCM_SHA384", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA"}, - new String[]{"TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"}); - - - /** - * String array of cipher suites to use, following the guidelines in the Jetty documentation. - */ - String[] cipherSuites; - - /** - * String array of protocols to use, following the guidelines in the Jetty documentation. - */ - String[] protocols; -} diff --git a/src/main/java/io/javalin/community/ssl/util/ConnectorFactory.java b/src/main/java/io/javalin/community/ssl/util/ConnectorFactory.java deleted file mode 100644 index e724a1a..0000000 --- a/src/main/java/io/javalin/community/ssl/util/ConnectorFactory.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.javalin.community.ssl.util; - -import io.javalin.community.ssl.SSLConfig; -import lombok.AllArgsConstructor; -import lombok.RequiredArgsConstructor; -import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; -import org.eclipse.jetty.http.UriCompliance; -import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; -import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; -import org.eclipse.jetty.server.*; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -/** - * Helper class to create the requested {@link ServerConnector}s from the given config. - */ -@AllArgsConstructor -@RequiredArgsConstructor -public class ConnectorFactory { - - private SSLConfig config; - private Server server; - - private SslContextFactory.Server sslContextFactory = null; - - /** - * Create and return an insecure connector to the server. - * - * @return The created {@link Connector}. - */ - public ServerConnector createInsecureConnector() { - ServerConnector connector; - - //The http configuration object - HttpConfiguration httpConfiguration = new HttpConfiguration(); - httpConfiguration.setUriCompliance(UriCompliance.RFC3986); // accept ambiguous values in path and let Javalin handle them - httpConfiguration.setSendServerVersion(false); - httpConfiguration.setSecurePort(config.securePort); - - //The factory for HTTP/1.1 connections. - HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfiguration); - - if (config.http2) { - //The factory for HTTP/2 connections. - HTTP2CServerConnectionFactory http2 = new HTTP2CServerConnectionFactory(httpConfiguration); - connector = new ServerConnector(server, http11, http2); - } else { - connector = new ServerConnector(server, http11); - } - - connector.setPort(config.insecurePort); - - if (config.host != null) { - connector.setHost(config.host); - } - - if (config.getConfigConnectors() != null) { - config.getConfigConnectors().accept(connector); - } - - return connector; - } - - /** - * Create and apply an SSL connector to the server. - * - * @return The created {@link Connector}. - */ - public ServerConnector createSecureConnector() { - - ServerConnector connector; - - //The http configuration object - HttpConfiguration httpConfiguration = new HttpConfiguration(); - httpConfiguration.setUriCompliance(UriCompliance.RFC3986); // accept ambiguous values in path and let Javalin handle them - httpConfiguration.setSendServerVersion(false); - httpConfiguration.addCustomizer(new SecureRequestCustomizer(config.sniHostCheck)); - - //The factory for HTTP/1.1 connections - HttpConnectionFactory http11 = new HttpConnectionFactory(httpConfiguration); - - if (config.http2) { - //The factory for HTTP/2 connections. - HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(httpConfiguration); - // The ALPN ConnectionFactory. - ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); - // The default protocol to use in case there is no negotiation. - alpn.setDefaultProtocol(http11.getProtocol()); - - SslConnectionFactory tlsHttp2 = new SslConnectionFactory(sslContextFactory, alpn.getProtocol()); - - connector = new ServerConnector(server, tlsHttp2, alpn, http2, http11); - } else { - SslConnectionFactory tls = new SslConnectionFactory(sslContextFactory, http11.getProtocol()); - - connector = new ServerConnector(server, tls, http11); - } - - connector.setPort(config.securePort); - - if (config.host != null) { - connector.setHost(config.host); - } - - if (config.getConfigConnectors() != null) { - config.getConfigConnectors().accept(connector); - } - - return connector; - - } - - -} diff --git a/src/main/java/io/javalin/community/ssl/util/SSLUtils.java b/src/main/java/io/javalin/community/ssl/util/SSLUtils.java deleted file mode 100644 index 4ff4cdf..0000000 --- a/src/main/java/io/javalin/community/ssl/util/SSLUtils.java +++ /dev/null @@ -1,157 +0,0 @@ -package io.javalin.community.ssl.util; - -import io.javalin.community.ssl.SSLConfig; -import io.javalin.community.ssl.SSLConfigException; -import io.javalin.community.ssl.TrustConfig; -import nl.altindag.ssl.SSLFactory; -import nl.altindag.ssl.jetty.util.JettySslUtils; -import nl.altindag.ssl.pem.util.PemUtils; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import javax.net.ssl.X509ExtendedKeyManager; - -/** - * Utility class for SSL related tasks. - */ -public class SSLUtils { - - /** - * Helper method to create a {@link SslContextFactory} from the SSLFactory. - * This method is used to create the SSLContextFactory for the Jetty server as well as - * configure the resulting factory. - * - * @param sslFactory The {@link SSLFactory} to use. - * @param config The {@link SSLConfig} to use. - * @return The created {@link SslContextFactory}. - */ - public static SslContextFactory.Server createSslContextFactory(SSLFactory sslFactory, SSLConfig config) { - return JettySslUtils.forServer(sslFactory); - } - - /** - * Helper method to create a {@link SSLFactory} from the given config. - * - * @param config The config to use. - * @return The created {@link SSLFactory}. - */ - public static SSLFactory getSslFactory(SSLConfig config) { - return getSslFactory(config, false); - } - - /** - * Helper method to create a {@link SSLFactory} from the given config. - * - * @param config The config to use. - * @param reloading Whether the SSLFactory is being reloaded or is the first time. - * @return The created {@link SSLFactory}. - */ - public static SSLFactory getSslFactory(SSLConfig config, boolean reloading) { - SSLFactory.Builder builder = SSLFactory.builder(); - - //Add the identity information - parseIdentity(config, builder); - - //Add the trust information - if(config.getTrustConfig() != null) { - parseTrust(config.getTrustConfig(), builder); - builder.withNeedClientAuthentication(); - } - - if (!reloading) { - builder.withSwappableIdentityMaterial(); - builder.withSwappableTrustMaterial(); - - if(config.securityProvider != null) - builder.withSecurityProvider(config.securityProvider); - - builder.withCiphers(config.tlsConfig.getCipherSuites()); - builder.withProtocols(config.tlsConfig.getProtocols()); - } - - - return builder.build(); - } - - - /** - * Helper method to parse the given config and add Identity Material to the given builder. - * - * @param config The config to use. - * @throws SSLConfigException if the key configuration is invalid. - */ - public static void parseIdentity(SSLConfig config, SSLFactory.Builder builder) throws SSLConfigException { - X509ExtendedKeyManager keyManager; - - SSLConfig.InnerConfig.IdentityLoadingType identityLoadingType = config.inner.getIdentityLoadingType(); - - boolean passwordProtectedPem = config.inner.privateKeyPassword != null; - - switch (identityLoadingType) { - case PEM_FILE_PATH: - keyManager = passwordProtectedPem ? - PemUtils.loadIdentityMaterial(config.inner.pemCertificatesPath, - config.inner.pemPrivateKeyPath, - config.inner.privateKeyPassword.toCharArray()) : - PemUtils.loadIdentityMaterial(config.inner.pemCertificatesPath, - config.inner.pemPrivateKeyPath); - break; - case PEM_CLASS_PATH: - keyManager = passwordProtectedPem ? - PemUtils.loadIdentityMaterial(config.inner.pemCertificatesFile, - config.inner.pemPrivateKeyFile, - config.inner.privateKeyPassword.toCharArray()) : - PemUtils.loadIdentityMaterial(config.inner.pemCertificatesFile, - config.inner.pemPrivateKeyFile); - break; - case PEM_STRING: - keyManager = passwordProtectedPem ? - PemUtils.parseIdentityMaterial(config.inner.pemCertificatesString, - config.inner.pemPrivateKeyString, - config.inner.privateKeyPassword.toCharArray()) : - PemUtils.parseIdentityMaterial(config.inner.pemCertificatesString, - config.inner.pemPrivateKeyString, - null); - break; - case PEM_INPUT_STREAM: - keyManager = passwordProtectedPem ? - PemUtils.loadIdentityMaterial(config.inner.pemCertificatesInputStream, - config.inner.pemPrivateKeyInputStream, - config.inner.privateKeyPassword.toCharArray()) : - PemUtils.loadIdentityMaterial(config.inner.pemCertificatesInputStream, - config.inner.pemPrivateKeyInputStream); - break; - case KEY_STORE_CLASS_PATH: - builder.withIdentityMaterial(config.inner.keyStoreFile, config.inner.keyStorePassword.toCharArray()); - return; - case KEY_STORE_FILE_PATH: - builder.withIdentityMaterial(config.inner.keyStorePath, config.inner.keyStorePassword.toCharArray()); - return; - case KEY_STORE_INPUT_STREAM: - builder.withIdentityMaterial(config.inner.keyStoreInputStream, config.inner.keyStorePassword.toCharArray()); - return; - case NONE: - default: - throw new SSLConfigException(SSLConfigException.Types.MISSING_CERT_AND_KEY_FILE); - } - - builder.withIdentityMaterial(keyManager); - } - - - /** - * Helper method to parse the given config and add Trust Material to the given builder. - * - * @param config The config to use. - */ - public static void parseTrust(TrustConfig config, SSLFactory.Builder builder) { - if (!config.certificates.isEmpty()) { - builder.withTrustMaterial(config.certificates); - } - - if (!config.keyStores.isEmpty()) { - config.keyStores.forEach(builder::withTrustMaterial); - } - } - - -} diff --git a/src/main/kotlin/io/javalin/community/ssl/SSLConfig.kt b/src/main/kotlin/io/javalin/community/ssl/SSLConfig.kt new file mode 100644 index 0000000..9f5cea5 --- /dev/null +++ b/src/main/kotlin/io/javalin/community/ssl/SSLConfig.kt @@ -0,0 +1,265 @@ +package io.javalin.community.ssl + +import nl.altindag.ssl.pem.util.PemUtils +import nl.altindag.ssl.util.KeyStoreUtils +import org.eclipse.jetty.server.ServerConnector +import java.io.InputStream +import java.nio.file.Paths +import java.security.KeyStore +import java.security.Provider +import java.util.function.Consumer +import javax.net.ssl.X509ExtendedKeyManager + +/** + * Data class to hold the configuration for the plugin. + */ + +class SSLConfig { + /** + * Host to bind to. + */ + @JvmField + var host: String? = null + + /** + * Toggle the default http (insecure) connector. + */ + @JvmField + var insecure = true + + /** + * Toggle the default https (secure) connector. + */ + @JvmField + var secure = true + + /** + * Port to use on the SSL (secure) connector. + */ + @JvmField + var securePort = 443 + + /** + * Port to use on the http (insecure) connector. + */ + @JvmField + var insecurePort = 80 + + /** + * Enable http to https redirection. + */ + @JvmField + var redirect = false + + /** + * Toggle HTTP/2 Support + */ + @JvmField + var http2 = true + + /** + * Disable SNI checks. + * + * @see [Configuring SNI](https://www.eclipse.org/jetty/documentation/jetty-11/operations-guide/index.html.og-protocols-ssl-sni) + */ + @JvmField + var sniHostCheck = true + + /** + * TLS Security configuration + */ + @JvmField + var tlsConfig: TLSConfig = TLSConfig.INTERMEDIATE + + enum class LoadedIdentity { + NONE, KEY_MANAGER, KEY_STORE + } + + /** + * Internal configuration holder for the identity. DO NOT USE DIRECTLY. + */ + val pvt = PrivateConfig() + + /** + * Internal data class to hold the identity configuration. DO NOT USE DIRECTLY. + */ + class PrivateConfig { + + /** + * Identity manager to use for the SSLContext. + * It's meant to be configured using the different loading methods but can be set directly. + * Exclusive with [keyStore]. + */ + var keyManager : X509ExtendedKeyManager? = null + set(value) { + if (loadedIdentity != LoadedIdentity.NONE) { + throw SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS) + } else if (value != null) { + loadedIdentity = LoadedIdentity.KEY_MANAGER + field = value + } + } + + /** + * Key store to use for the SSLContext. + * It's meant to be configured using the different loading methods but can be set directly. + * Exclusive with [keyManager]. + */ + var keyStore : KeyStore? = null + set(value) { + if (loadedIdentity != LoadedIdentity.NONE) { + throw SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS) + } else if (value != null) { + loadedIdentity = LoadedIdentity.KEY_STORE + field = value + } + } + + var identityPassword: String? = null + + var loadedIdentity: LoadedIdentity = LoadedIdentity.NONE + private set + + } + + /////////////////////////////////////////////////////////////// + // PEM Loading Methods + /////////////////////////////////////////////////////////////// + /** + * Load pem formatted identity data from a given path in the system. + * + * @param certificatePath path to the certificate chain PEM file. + * @param privateKeyPath path to the private key PEM file. + * @param password optional password for the private key. + */ + @JvmOverloads + fun pemFromPath(certificatePath: String, privateKeyPath: String, password: String? = null) { + val certPath = Paths.get(certificatePath) + val keyPath = Paths.get(privateKeyPath) + pvt.keyManager = password?.let { + PemUtils.loadIdentityMaterial(certPath, keyPath, password.toCharArray()) + } ?: PemUtils.loadIdentityMaterial(certPath, keyPath) + } + + /** + * Load pem formatted identity data from the classpath. + * + * @param certificateFile The name of the pem certificate file in the classpath. + * @param privateKeyFile The name of the pem private key file in the classpath. + * @param password optional password for the private key. + */ + @JvmOverloads + fun pemFromClasspath(certificateFile: String, privateKeyFile: String, password: String? = null) { + pvt.keyManager = password?.let { + PemUtils.loadIdentityMaterial(certificateFile, privateKeyFile, password.toCharArray()) + } ?: PemUtils.loadIdentityMaterial(certificateFile, privateKeyFile) + } + + + /** + * Load pem formatted identity data from a given input stream. + * + * @param certificateInputStream input stream to the certificate chain PEM file. + * @param privateKeyInputStream input stream to the private key PEM file. + * @param password optional password for the private key. + */ + @JvmOverloads + fun pemFromInputStream(certificateInputStream: InputStream, privateKeyInputStream: InputStream, password: String? = null) { + pvt.keyManager = password?.let { + PemUtils.loadIdentityMaterial(certificateInputStream, privateKeyInputStream, password.toCharArray()) + } ?: PemUtils.loadIdentityMaterial(certificateInputStream, privateKeyInputStream) + } + + /** + * Load pem formatted identity data from a given string. + * + * @param certificateString PEM encoded certificate chain. + * @param privateKeyString PEM encoded private key. + * @param password optional password for the private key. + */ + @JvmOverloads + fun pemFromString(certificateString: String, privateKeyString: String, password: String? = null) { + pvt.keyManager = PemUtils.parseIdentityMaterial(certificateString, privateKeyString, password?.toCharArray() ) + } + + /////////////////////////////////////////////////////////////// + // Key Store Loading Methods + /////////////////////////////////////////////////////////////// + /** + * Load a key store from a given path in the system. + * + * @param keyStorePath path to the key store file. + * @param keyStorePassword password for the key store. + * @param identityPassword password for the identity, if different from the key store password. + */ + @JvmOverloads + fun keystoreFromPath(keyStorePath: String, keyStorePassword: String, identityPassword: String? = null) { + pvt.keyStore = KeyStoreUtils.loadKeyStore(Paths.get(keyStorePath), keyStorePassword.toCharArray()) + pvt.identityPassword = identityPassword ?: keyStorePassword + } + + /** + * Load a key store from a given input stream. + * + * @param keyStoreInputStream input stream to the key store file. + * @param keyStorePassword password for the key store + * @param identityPassword password for the identity, if different from the key store password. + */ + @JvmOverloads + fun keystoreFromInputStream(keyStoreInputStream: InputStream, keyStorePassword: String, identityPassword: String? = null) { + pvt.keyStore = KeyStoreUtils.loadKeyStore(keyStoreInputStream, keyStorePassword.toCharArray()) + pvt.identityPassword = identityPassword ?: keyStorePassword + } + + /** + * Load a key store from the classpath. + * + * @param keyStoreFile name of the key store file in the classpath. + * @param keyStorePassword password for the key store. + * @param identityPassword password for the identity, if different from the key store password. + */ + @JvmOverloads + fun keystoreFromClasspath(keyStoreFile: String, keyStorePassword: String, identityPassword: String? = null) { + pvt.keyStore = KeyStoreUtils.loadKeyStore(keyStoreFile, keyStorePassword.toCharArray()) + pvt.identityPassword = identityPassword ?: keyStorePassword + } + + /////////////////////////////////////////////////////////////// + // Advanced Options + /////////////////////////////////////////////////////////////// + var configConnectors: Consumer? = null + private set + + /** + * Consumer to configure the different [ServerConnector] that will be created. + * This consumer will be called as the last config step for each connector, allowing to override any previous configuration. + */ + fun configConnectors(configConnectors: Consumer?) { + this.configConnectors = configConnectors + } + + /** + * Security provider to use for the SSLContext. + */ + @JvmField + var securityProvider: Provider? = null + + /////////////////////////////////////////////////////////////// + // Trust Store + /////////////////////////////////////////////////////////////// + /** + * Trust store configuration for the server, if not set, every client will be accepted. + */ + var trustConfig: TrustConfig? = null + private set + + /** + * Configure the trust configuration for the server. + * @param trustConfigConsumer consumer to configure the trust configuration. + */ + fun withTrustConfig(trustConfigConsumer: Consumer) { + val temp = TrustConfig() + trustConfigConsumer.accept(temp) + trustConfig = temp + } +} diff --git a/src/main/kotlin/io/javalin/community/ssl/SSLConfigException.kt b/src/main/kotlin/io/javalin/community/ssl/SSLConfigException.kt new file mode 100644 index 0000000..f4a0798 --- /dev/null +++ b/src/main/kotlin/io/javalin/community/ssl/SSLConfigException.kt @@ -0,0 +1,16 @@ +package io.javalin.community.ssl + +/** + * Exception thrown when the SSLConfig is invalid. + */ +class SSLConfigException : RuntimeException { + constructor(type: Types) : super(type.message) + + /** + * Types of errors that can occur when configuring SSL. + */ + enum class Types(val message: String) { + MISSING_CERT_AND_KEY_FILE("There is no certificate or key file provided"), + MULTIPLE_IDENTITY_LOADING_OPTIONS("Both the certificate and key must be provided using the same method") + } +} diff --git a/src/main/kotlin/io/javalin/community/ssl/SSLPlugin.kt b/src/main/kotlin/io/javalin/community/ssl/SSLPlugin.kt new file mode 100644 index 0000000..bf4d8b7 --- /dev/null +++ b/src/main/kotlin/io/javalin/community/ssl/SSLPlugin.kt @@ -0,0 +1,79 @@ +package io.javalin.community.ssl + +import io.javalin.community.ssl.util.ConnectorFactory +import io.javalin.community.ssl.util.SSLUtils +import io.javalin.config.JavalinConfig +import io.javalin.plugin.Plugin +import nl.altindag.ssl.SSLFactory +import nl.altindag.ssl.util.SSLFactoryUtils +import org.eclipse.jetty.server.Connector +import org.eclipse.jetty.server.HttpConfiguration +import org.eclipse.jetty.server.Server +import org.eclipse.jetty.server.handler.SecuredRedirectHandler +import org.eclipse.jetty.util.ssl.SslContextFactory +import java.util.function.BiFunction +import java.util.function.Consumer + +/** + * Plugin to add SSL support to Javalin. + * The configuration is done via the Consumer passed to the constructor. + * The plugin will add the connectors to the server and apply the necessary handlers. + * + * If you want to reload the SSLContextFactory, you can call the reload method, by keeping a reference to the plugin instance. + */ +class SSLPlugin (userConfig: Consumer) : Plugin(userConfig,SSLConfig()) { + + private var sslFactory: SSLFactory? = null + + override fun onStart(config: JavalinConfig) { + //Add the connectors to the server + createConnectors(pluginConfig).forEach(config.jetty::addConnector) + + if(pluginConfig.redirect && pluginConfig.secure) { + config.jetty.modifyServer{ + it.handler = SecuredRedirectHandler() + } + } + + } + + override fun name(): String = "SSL Plugin" + + /** + * Reload the SSL configuration with the new certificates and/or keys. + * @param newConfig The new configuration. + */ + fun reload(newConfig: Consumer) { + val conf = SSLConfig() + newConfig.accept(conf) + checkNotNull(sslFactory) { "Cannot reload before the plugin has been applied to a Javalin instance, a server has been patched or if the ssl connector is disabled." } + val newFactory = SSLUtils.getSslFactory(conf, true) + SSLFactoryUtils.reload(sslFactory, newFactory) + } + + + private fun createConnectors(config: SSLConfig): List> { + + val sslContextFactory: SslContextFactory.Server? + if (config.secure) { + sslFactory = SSLUtils.getSslFactory(config) + sslContextFactory = SSLUtils.createSslContextFactory(sslFactory) + } else { + sslContextFactory = null + } + val connectorList = ArrayList>() + + val connectorFactory = + ConnectorFactory(config, sslContextFactory) + + if (config.insecure) { + connectorList.add(connectorFactory::createInsecureConnector) + } + if (config.secure) { + connectorList.add(connectorFactory::createSecureConnector) + } + return connectorList + + + } +} diff --git a/src/main/kotlin/io/javalin/community/ssl/TLSConfig.kt b/src/main/kotlin/io/javalin/community/ssl/TLSConfig.kt new file mode 100644 index 0000000..90b4c28 --- /dev/null +++ b/src/main/kotlin/io/javalin/community/ssl/TLSConfig.kt @@ -0,0 +1,88 @@ +package io.javalin.community.ssl + +/** + * Data class for the SSL configuration. + * + * @see [Security/Server Side TLS](https://wiki.mozilla.org/Security/Server_Side_TLS) + */ +class TLSConfig( + /** + * String array of cipher suites to use, following the guidelines in the [ Jetty documentation](https://www.eclipse.org/jetty/documentation/jetty-11/operations-guide/index.html#og-protocols-ssl-customize-ciphers). + */ + val cipherSuites: Array, + /** + * String array of protocols to use, following the guidelines in the [ Jetty documentation](https://www.eclipse.org/jetty/documentation/jetty-11/operations-guide/index.html#og-protocols-ssl-customize-versions). + */ + val protocols: Array +) { + + override fun toString(): String { + return "TLSConfig(cipherSuites=" + cipherSuites.contentDeepToString() + ", protocols=" + protocols.contentDeepToString() + ")" + } + + companion object { + private const val GUIDELINES_VERSION = "5.7" + + /** + * For modern clients that support TLS 1.3, with no need for backwards compatibility + */ + val MODERN = TLSConfig( + arrayOf( + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256"), + arrayOf("TLSv1.3") + ) + + /** + * Recommended configuration for a general-purpose server + */ + val INTERMEDIATE = TLSConfig( + arrayOf( + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" + ), arrayOf("TLSv1.3","TLSv1.2") + ) + + /** + * For services accessed by very old clients or libraries, such as Internet Explorer 8 (Windows XP), Java 6, or OpenSSL 0.9.8 + */ + val OLD = TLSConfig( + arrayOf( + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA" + ), arrayOf("TLSv1.3","TLSv1.2", "TLSv1.1", "TLSv1") + ) + } +} diff --git a/src/main/java/io/javalin/community/ssl/TrustConfig.java b/src/main/kotlin/io/javalin/community/ssl/TrustConfig.kt similarity index 58% rename from src/main/java/io/javalin/community/ssl/TrustConfig.java rename to src/main/kotlin/io/javalin/community/ssl/TrustConfig.kt index be5c83b..1664ebb 100644 --- a/src/main/java/io/javalin/community/ssl/TrustConfig.java +++ b/src/main/kotlin/io/javalin/community/ssl/TrustConfig.kt @@ -1,48 +1,45 @@ -package io.javalin.community.ssl; +package io.javalin.community.ssl -import nl.altindag.ssl.util.CertificateUtils; -import nl.altindag.ssl.util.KeyStoreUtils; - -import java.io.InputStream; -import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.util.ArrayList; -import java.util.List; +import nl.altindag.ssl.util.CertificateUtils +import nl.altindag.ssl.util.KeyStoreUtils +import java.io.InputStream +import java.nio.file.Paths +import java.security.KeyStore +import java.security.cert.Certificate /** * Configuration for the trust store, used to verify the identity of the clients. * Using this configuration, the server will only accept connections from clients that are trusted. * If no trust store is configured, the server will accept any client. */ +class TrustConfig { -public class TrustConfig { - + private var certificateMutableList: MutableList = ArrayList() /** * List of certificates to be trusted, can be loaded using the helper methods or directly. * This list is complementary to the keys */ - public List certificates = new ArrayList<>(); + val certificates: List = certificateMutableList + + var keyStoreMutableList: MutableList = ArrayList() /** * List of KeyStores to be trusted, can be loaded using the helper methods or directly. */ - public List keyStores = new ArrayList<>(); - + var keyStore: List = keyStoreMutableList /////////////////////////////////////////////////////////////// // Certificate Loading Methods (PEM, P7B and DER) /////////////////////////////////////////////////////////////// - /** * Load certificate data from a given path in the system. * The certificate can be in PEM, P7B/PKCS#7 or DER format. * * @param certificatePath path to the certificate file. */ - public void certificateFromPath(String certificatePath) { - certificates.addAll(CertificateUtils.loadCertificate(Paths.get(certificatePath))); + fun certificateFromPath(certificatePath: String) { + certificateMutableList.addAll(CertificateUtils.loadCertificate(Paths.get(certificatePath))) } /** @@ -51,8 +48,8 @@ public void certificateFromPath(String certificatePath) { * * @param certificateFile The name of the certificate file in the classpath. */ - public void certificateFromClasspath(String certificateFile) { - certificates.addAll(CertificateUtils.loadCertificate(certificateFile)); + fun certificateFromClasspath(certificateFile: String) { + certificateMutableList.addAll(CertificateUtils.loadCertificate(certificateFile)) } /** @@ -61,8 +58,8 @@ public void certificateFromClasspath(String certificateFile) { * * @param certificateInputStream input stream to the certificate file. */ - public void certificateFromInputStream(InputStream certificateInputStream) { - certificates.addAll(CertificateUtils.loadCertificate(certificateInputStream)); + fun certificateFromInputStream(certificateInputStream: InputStream) { + certificateMutableList.addAll(CertificateUtils.loadCertificate(certificateInputStream)) } /** @@ -71,8 +68,8 @@ public void certificateFromInputStream(InputStream certificateInputStream) { * * @param certificateString P7B encoded certificate. */ - public void p7bCertificateFromString(String certificateString) { - certificates.addAll(CertificateUtils.parseP7bCertificate(certificateString)); + fun p7bCertificateFromString(certificateString: String) { + certificateMutableList.addAll(CertificateUtils.parseP7bCertificate(certificateString)) } /** @@ -80,16 +77,12 @@ public void p7bCertificateFromString(String certificateString) { * The certificate must be in PEM format. * @param certificateString PEM encoded certificate. */ - public void pemFromString(String certificateString) { - certificates.addAll(CertificateUtils.parsePemCertificate(certificateString)); + fun pemFromString(certificateString: String) { + certificateMutableList.addAll(CertificateUtils.parsePemCertificate(certificateString)) } - - /////////////////////////////////////////////////////////////// // Trust Store Loading Methods (JKS, PKCS12) /////////////////////////////////////////////////////////////// - - /** * Load a trust store from a given path in the system. * The trust store can be in JKS or PKCS12 format. @@ -97,8 +90,8 @@ public void pemFromString(String certificateString) { * @param trustStorePath path to the trust store file. * @param trustStorePassword password for the trust store. */ - public void trustStoreFromPath(String trustStorePath, String trustStorePassword) { - keyStores.add(KeyStoreUtils.loadKeyStore(Paths.get(trustStorePath), trustStorePassword.toCharArray())); + fun trustStoreFromPath(trustStorePath: String, trustStorePassword: String) { + keyStoreMutableList.add(KeyStoreUtils.loadKeyStore(Paths.get(trustStorePath), trustStorePassword.toCharArray())) } /** @@ -108,8 +101,8 @@ public void trustStoreFromPath(String trustStorePath, String trustStorePassword) * @param trustStoreInputStream input stream to the trust store file. * @param trustStorePassword password for the trust store. */ - public void trustStoreFromInputStream(InputStream trustStoreInputStream, String trustStorePassword) { - keyStores.add(KeyStoreUtils.loadKeyStore(trustStoreInputStream, trustStorePassword.toCharArray())); + fun trustStoreFromInputStream(trustStoreInputStream: InputStream, trustStorePassword: String) { + keyStoreMutableList.add(KeyStoreUtils.loadKeyStore(trustStoreInputStream, trustStorePassword.toCharArray())) } /** @@ -117,7 +110,7 @@ public void trustStoreFromInputStream(InputStream trustStoreInputStream, String * @param trustStoreFile The name of the trust store file in the classpath. * @param trustStorePassword password for the trust store. */ - public void trustStoreFromClasspath(String trustStoreFile, String trustStorePassword) { - keyStores.add(KeyStoreUtils.loadKeyStore(trustStoreFile, trustStorePassword.toCharArray())); + fun trustStoreFromClasspath(trustStoreFile: String, trustStorePassword: String) { + keyStoreMutableList.add(KeyStoreUtils.loadKeyStore(trustStoreFile, trustStorePassword.toCharArray())) } } diff --git a/src/main/kotlin/io/javalin/community/ssl/util/ConnectorFactory.kt b/src/main/kotlin/io/javalin/community/ssl/util/ConnectorFactory.kt new file mode 100644 index 0000000..06c16eb --- /dev/null +++ b/src/main/kotlin/io/javalin/community/ssl/util/ConnectorFactory.kt @@ -0,0 +1,75 @@ +package io.javalin.community.ssl.util + +import io.javalin.community.ssl.SSLConfig +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory +import org.eclipse.jetty.server.* +import org.eclipse.jetty.util.ssl.SslContextFactory + +/** + * Helper class to create the requested [ServerConnector]s from the given config. + */ +class ConnectorFactory (private var config: SSLConfig, private var sslContextFactory: SslContextFactory.Server?) { + + /** + * Create and return an insecure connector to the server. + * + * @return The created [ServerConnector]. + */ + fun createInsecureConnector(server: Server?, httpConfiguration: HttpConfiguration): ServerConnector { + val connector: ServerConnector + + //The http configuration object + if(config.secure) httpConfiguration.securePort = config.securePort + + //The factory for HTTP/1.1 connections. + val http11 = HttpConnectionFactory(httpConfiguration) + connector = if (config.http2) { + //The factory for HTTP/2 connections. + val http2 = HTTP2CServerConnectionFactory(httpConfiguration) + ServerConnector(server, http11, http2) + } else { + ServerConnector(server, http11) + } + connector.port = config.insecurePort + + config.host?.let { connector.host = it } + config.configConnectors?.accept(connector) + + return connector + } + + /** + * Create and apply an SSL connector to the server. + * + * @return The created [ServerConnector]. + */ + fun createSecureConnector(server: Server?, httpConfiguration: HttpConfiguration): ServerConnector { + val connector: ServerConnector + + httpConfiguration.addCustomizer(SecureRequestCustomizer(config.sniHostCheck)) + + //The factory for HTTP/1.1 connections + val http11 = HttpConnectionFactory(httpConfiguration) + connector = if (config.http2) { + //The factory for HTTP/2 connections. + val http2 = HTTP2ServerConnectionFactory(httpConfiguration) + // The ALPN ConnectionFactory. + val alpn = ALPNServerConnectionFactory() + // The default protocol to use in case there is no negotiation. + alpn.setDefaultProtocol(http11.protocol) + val tlsHttp2 = SslConnectionFactory(sslContextFactory, alpn.protocol) + ServerConnector(server, tlsHttp2, alpn, http2, http11) + } else { + val tls = SslConnectionFactory(sslContextFactory, http11.protocol) + ServerConnector(server, tls, http11) + } + connector.port = config.securePort + + config.host?.let { connector.host = it } + config.configConnectors?.accept(connector) + + return connector + } +} diff --git a/src/main/kotlin/io/javalin/community/ssl/util/SSLUtils.kt b/src/main/kotlin/io/javalin/community/ssl/util/SSLUtils.kt new file mode 100644 index 0000000..2ee0225 --- /dev/null +++ b/src/main/kotlin/io/javalin/community/ssl/util/SSLUtils.kt @@ -0,0 +1,95 @@ +package io.javalin.community.ssl.util + +import io.javalin.community.ssl.SSLConfig +import io.javalin.community.ssl.SSLConfigException +import io.javalin.community.ssl.TrustConfig +import nl.altindag.ssl.SSLFactory +import nl.altindag.ssl.jetty.util.JettySslUtils +import org.eclipse.jetty.util.ssl.SslContextFactory +import java.security.KeyStore +import java.util.function.Consumer + +/** + * Utility class for SSL related tasks. + */ +object SSLUtils { + /** + * Helper method to create a [SslContextFactory] from the SSLFactory. + * This method is used to create the SSLContextFactory for the Jetty server as well as + * configure the resulting factory. + * + * @param sslFactory The [SSLFactory] to use. + * @return The created [SslContextFactory]. + */ + fun createSslContextFactory(sslFactory: SSLFactory?): SslContextFactory.Server { + return JettySslUtils.forServer(sslFactory) + } + + /** + * Helper method to create a [SSLFactory] from the given config. + * + * @param config The config to use. + * @return The created [SSLFactory]. + */ + fun getSslFactory(config: SSLConfig): SSLFactory { + return getSslFactory(config, false) + } + + /** + * Helper method to create a [SSLFactory] from the given config. + * + * @param config The config to use. + * @param reloading Whether the SSLFactory is being reloaded or is the first time. + * @return The created [SSLFactory]. + */ + fun getSslFactory(config: SSLConfig, reloading: Boolean): SSLFactory { + val builder = SSLFactory.builder() + + //Add the identity information + parseIdentity(config, builder) + + //Add the trust information + config.trustConfig?.let { + parseTrust(it, builder) + builder.withNeedClientAuthentication() + } + + if (!reloading) { + builder.withSwappableIdentityMaterial() + builder.withSwappableTrustMaterial() + if (config.securityProvider != null) builder.withSecurityProvider(config.securityProvider) + builder.withCiphers(*config.tlsConfig.cipherSuites) + builder.withProtocols(*config.tlsConfig.protocols) + } + return builder.build() + } + + /** + * Helper method to parse the given config and add Identity Material to the given builder. + * + * @param config The config to use. + * @throws SSLConfigException if the key configuration is invalid. + */ + @Throws(SSLConfigException::class) + private fun parseIdentity(config: SSLConfig, builder: SSLFactory.Builder) { + when(config.pvt.loadedIdentity){ + SSLConfig.LoadedIdentity.NONE -> throw SSLConfigException(SSLConfigException.Types.MISSING_CERT_AND_KEY_FILE) + SSLConfig.LoadedIdentity.KEY_MANAGER -> builder.withIdentityMaterial(config.pvt.keyManager) + SSLConfig.LoadedIdentity.KEY_STORE -> builder.withIdentityMaterial(config.pvt.keyStore, config.pvt.identityPassword!!.toCharArray()) + } + } + + /** + * Helper method to parse the given config and add Trust Material to the given builder. + * + * @param config The config to use. + */ + private fun parseTrust(config: TrustConfig, builder: SSLFactory.Builder) { + if (config.certificates.isNotEmpty()) { + builder.withTrustMaterial(config.certificates) + } + if (config.keyStore.isNotEmpty()) { + config.keyStore.forEach(Consumer { trustStore: KeyStore? -> builder.withTrustMaterial(trustStore) }) + } + } +} diff --git a/src/main/java/module-info.java b/src/main/kotlin/module-info.java similarity index 93% rename from src/main/java/module-info.java rename to src/main/kotlin/module-info.java index b0fa6ce..25dbbdc 100644 --- a/src/main/java/module-info.java +++ b/src/main/kotlin/module-info.java @@ -10,5 +10,5 @@ requires nl.altindag.ssl.jetty; requires nl.altindag.ssl.pem; - requires static lombok; + requires kotlin.stdlib; } diff --git a/src/test/java/io/javalin/community/ssl/JavaAPITests.java b/src/test/java/io/javalin/community/ssl/JavaAPITests.java new file mode 100644 index 0000000..09d84cb --- /dev/null +++ b/src/test/java/io/javalin/community/ssl/JavaAPITests.java @@ -0,0 +1,80 @@ +package io.javalin.community.ssl; + +import io.javalin.Javalin; + +import java.io.InputStream; + +public class JavaAPITests { + + static void javaApiCompiles(){ + InputStream certInputStream = null; + InputStream keyInputStream = null; + String certString = null; + String keyString = null; + InputStream keystoreInputStream = null; + String keyPassword = null; + + SSLPlugin plugin = new SSLPlugin(conf ->{ + // Connection options + conf.host = null; // Host to bind to, by default it will bind to all interfaces + conf.insecure = true; // Toggle the default http (insecure) connector + conf.secure = true; // Toggle the default https (secure) connector + conf.http2 = true; // Toggle HTTP/2 Support + + conf.securePort = 443; // Port to use on the SSL (secure) connector (TCP) + conf.insecurePort = 80; // Port to use on the http (insecure) connector (TCP) + conf.redirect = false; // Redirect all http requests to https + + conf.sniHostCheck = true; // Enable SNI hostname verification + conf.tlsConfig = TLSConfig.Companion.getINTERMEDIATE(); // Set the TLS configuration. (by default it uses Mozilla's intermediate configuration) + + // PEM loading options (mutually exclusive) + conf.pemFromPath("/path/to/cert.pem", "/path/to/key.pem"); // load from the given paths + conf.pemFromPath("/path/to/cert.pem", "/path/to/key.pem", "keyPassword"); // load from the given paths with the given key password + conf.pemFromClasspath("certName.pem", "keyName.pem"); // load from the given paths in the classpath + conf.pemFromClasspath("certName.pem", "keyName.pem", "keyPassword"); // load from the given paths in the classpath with the given key password + conf.pemFromInputStream(certInputStream, keyInputStream); // load from the given input streams + conf.pemFromInputStream(certInputStream, keyInputStream, "keyPassword"); // load from the given input streams with the given key password + conf.pemFromString(certString, keyString); // load from the given strings + conf.pemFromString(certString, keyString, "keyPassword"); // load from the given strings with the given key password + + // Keystore loading options (PKCS#12/JKS) (mutually exclusive) + conf.keystoreFromPath("/path/to/keystore.jks", "keystorePassword"); // load the keystore from the given path + conf.keystoreFromClasspath("keyStoreName.p12", "keystorePassword"); // load the keystore from the given path in the classpath + conf.keystoreFromInputStream(keystoreInputStream, "keystorePassword"); // load the keystore + + // Advanced options + conf.configConnectors(con -> con.dump()); // Set a Consumer to configure the connectors + conf.securityProvider = null; // Use a custom security provider + conf.withTrustConfig(trust -> trust.pemFromString("cert")); // Set the trust configuration, explained below. (by default all clients are trusted) + }); + + SSLPlugin trustPlugin = new SSLPlugin(conf ->{ + conf.withTrustConfig(trust ->{ + // Certificate loading options (PEM/DER/P7B) + trust.certificateFromPath("path/to/certificate.pem"); // load a PEM/DER/P7B cert from the given path + trust.certificateFromClasspath("certificateName.pem"); // load a PEM/DER/P7B cert from the given path in the classpath + trust.certificateFromInputStream(certInputStream); // load a PEM/DER/P7B cert from the given input stream + trust.p7bCertificateFromString("p7b encoded certificate"); // load a P7B cert from the given string + trust.pemFromString("pem encoded certificate"); // load a PEM cert from the given string + + // Trust store loading options (JKS/PKCS12) + trust.trustStoreFromPath("path/to/truststore.jks", "password"); // load a trust store from the given path + trust.trustStoreFromClasspath("truststore.jks", "password"); // load a trust store from the given path in the classpath + trust.trustStoreFromInputStream(certInputStream, "password"); // load a trust store from the given input stream + }); + }); + + Javalin.create(conf -> conf.registerPlugin(plugin)); + + plugin.reload(conf ->{ + // any options other than loading certificates/keys will be ignored. + conf.pemFromPath("/path/to/new/cert.pem", "/path/to/new/key.pem"); + + // you can also replace trust configuration + conf.withTrustConfig(trust ->{ + trust.certificateFromPath("path/to/new/certificate.pem"); + }); + }); + } +} diff --git a/src/test/java/io/javalin/community/ssl/SSLConfigTest.java b/src/test/java/io/javalin/community/ssl/SSLConfigTest.java deleted file mode 100644 index b053ab2..0000000 --- a/src/test/java/io/javalin/community/ssl/SSLConfigTest.java +++ /dev/null @@ -1,238 +0,0 @@ -package io.javalin.community.ssl; - -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; - -import static org.junit.jupiter.api.Assertions.*; - -@Tag("unitary") -class SSLConfigTest { - - final String absolutePathString = "/etc/sample/path"; - final Path absolutePath = Paths.get(absolutePathString); - - final String fileName = "sample.pem"; - - final InputStream inputStream = InputStream.nullInputStream(); - - final String pemString = - "----- START CERTIFICATE -----\n" + - "BLABLABLABLABLABLABLABLABLABLA" + - "----- END CERTIFICATE -----\n"; - - - ////////////////////////////// - // Pem loading tests // - ////////////////////////////// - - @Test - void loadPemFromPathCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromPath(absolutePathString,absolutePathString)); - assertEquals(absolutePath, config.inner.pemCertificatesPath); - assertEquals(absolutePath, config.inner.pemPrivateKeyPath); - } - - @Test - void loadPemFromPathDifferentMethod() { - SSLConfig config = new SSLConfig(); - config.pemFromString("", ""); // load empty strings - assertThrows(SSLConfigException.class,() -> config.pemFromPath(absolutePathString,absolutePathString)); - } - - @Test - void loadPemFromPathTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromPath(absolutePathString,absolutePathString); - assertThrows(SSLConfigException.class,() -> config.pemFromPath(absolutePathString,absolutePathString)); - } - - @Test - void loadPemFromPathWithPasswordCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromPath(absolutePathString,absolutePathString, "password")); - assertEquals(absolutePath, config.inner.pemCertificatesPath); - assertEquals(absolutePath, config.inner.pemPrivateKeyPath); - assertEquals("password", config.inner.privateKeyPassword); - } - - @Test - void loadPemFromPathWithPasswordTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromPath(absolutePathString,absolutePathString, "password"); - assertThrows(SSLConfigException.class,() -> config.pemFromPath(absolutePathString,absolutePathString, "password")); - } - - //Repeat the same tests with the method loadPemFromClasspath - @Test - void loadPemFromClasspathCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromClasspath(fileName,fileName)); - assertEquals(fileName, config.inner.pemCertificatesFile); - assertEquals(fileName, config.inner.pemPrivateKeyFile); - } - - @Test - void loadPemFromClasspathDifferentMethod() { - SSLConfig config = new SSLConfig(); - config.pemFromString("", ""); // load empty strings - assertThrows(SSLConfigException.class,() -> config.pemFromClasspath(fileName,fileName)); - } - - @Test - void loadPemFromClasspathTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromClasspath(fileName,fileName); - assertThrows(SSLConfigException.class,() -> config.pemFromClasspath(fileName,fileName)); - } - - @Test - void loadPemFromClasspathWithPasswordCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromClasspath(fileName,fileName, "password")); - assertEquals(fileName, config.inner.pemCertificatesFile); - assertEquals(fileName, config.inner.pemPrivateKeyFile); - assertEquals("password", config.inner.privateKeyPassword); - } - - @Test - void loadPemFromClasspathWithPasswordTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromClasspath(fileName,fileName, "password"); - assertThrows(SSLConfigException.class,() -> config.pemFromClasspath(fileName,fileName, "password")); - } - - @Test - void loadPemFromStringCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromString(pemString,pemString)); - assertEquals(pemString, config.inner.pemCertificatesString); - assertEquals(pemString, config.inner.pemPrivateKeyString); - } - - @Test - void loadPemFromStringDifferentMethod() { - SSLConfig config = new SSLConfig(); - config.pemFromPath(absolutePathString,absolutePathString); // load empty strings - assertThrows(SSLConfigException.class,() -> config.pemFromString(pemString,pemString)); - } - - @Test - void loadPemFromStringTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromString(pemString,pemString); - assertThrows(SSLConfigException.class,() -> config.pemFromString(pemString,pemString)); - } - - @Test - void loadPemFromStringWithPasswordCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromString(pemString,pemString, "password")); - assertEquals(pemString, config.inner.pemCertificatesString); - assertEquals(pemString, config.inner.pemPrivateKeyString); - assertEquals("password", config.inner.privateKeyPassword); - } - - @Test - void loadPemFromStringWithPasswordTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromString(pemString,pemString, "password"); - assertThrows(SSLConfigException.class,() -> config.pemFromString(pemString,pemString, "password")); - } - - - //Repeat the same tests with the method loadPemFromInputStream - @Test - void loadPemFromInputStreamCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromInputStream(inputStream,inputStream)); - assertNotNull(config.inner.pemCertificatesInputStream); - assertNotNull(config.inner.pemPrivateKeyInputStream); - } - - @Test - void loadPemFromInputStreamDifferentMethod() { - SSLConfig config = new SSLConfig(); - config.pemFromString("", ""); // load empty strings - assertThrows(SSLConfigException.class,() -> config.pemFromInputStream(inputStream,inputStream)); - } - - @Test - void loadPemFromInputStreamTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromInputStream(inputStream,inputStream); - assertThrows(SSLConfigException.class,() -> config.pemFromInputStream(inputStream,inputStream)); - } - - @Test - void loadPemFromInputStreamWithPasswordCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.pemFromInputStream(inputStream,inputStream, "password")); - assertNotNull(config.inner.pemCertificatesInputStream); - assertNotNull(config.inner.pemPrivateKeyInputStream); - assertEquals("password", config.inner.privateKeyPassword); - } - - @Test - void loadPemFromInputStreamWithPasswordTwice() { - SSLConfig config = new SSLConfig(); - config.pemFromInputStream(inputStream,inputStream, "password"); - assertThrows(SSLConfigException.class,() -> config.pemFromInputStream(inputStream,inputStream, "password")); - } - - - ////////////////////////////// - // Keystore loading tests // - ////////////////////////////// - - //Keystore loading tests are the same as the PEM loading tests, but with the keystore method instead of the pem method and always with a password - @Test - void loadKeystoreFromPathCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.keystoreFromPath(absolutePathString,"password")); - assertEquals(absolutePath, config.inner.keyStorePath); - assertEquals("password", config.inner.keyStorePassword); - } - - @Test - void loadKeystoreFromPathTwice() { - SSLConfig config = new SSLConfig(); - config.keystoreFromPath(absolutePathString,"password"); - assertThrows(SSLConfigException.class,() -> config.keystoreFromPath(absolutePathString,"password")); - } - - @Test - void loadKeystoreFromInputStreamCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.keystoreFromInputStream(inputStream,"password")); - assertNotNull(config.inner.keyStoreInputStream); - assertEquals("password", config.inner.keyStorePassword); - } - - @Test - void loadKeystoreFromInputStreamTwice() { - SSLConfig config = new SSLConfig(); - config.keystoreFromInputStream(inputStream,"password"); - assertThrows(SSLConfigException.class,() -> config.keystoreFromInputStream(inputStream,"password")); - } - - @Test - void loadKeystoreFromClasspathCorrectly() { - SSLConfig config = new SSLConfig(); - assertDoesNotThrow(() -> config.keystoreFromClasspath(fileName,"password")); - assertEquals(fileName, config.inner.keyStoreFile); - assertEquals("password", config.inner.keyStorePassword); - } - - @Test - void loadKeystoreFromClasspathTwice() { - SSLConfig config = new SSLConfig(); - config.keystoreFromClasspath(fileName,"password"); - assertThrows(SSLConfigException.class,() -> config.keystoreFromClasspath(fileName,"password")); - } - -} diff --git a/src/test/java/io/javalin/community/ssl/SSLUtilsTest.java b/src/test/java/io/javalin/community/ssl/SSLUtilsTest.java deleted file mode 100644 index 0b55538..0000000 --- a/src/test/java/io/javalin/community/ssl/SSLUtilsTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.javalin.community.ssl; - -import io.javalin.community.ssl.util.SSLUtils; -import nl.altindag.ssl.SSLFactory; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -@Tag("unitary") -class SSLUtilsTest { - - @Test - void createSslContextFactory() { - } - - @Test - void parseIdentityEmptyConfig() { - SSLConfig config = new SSLConfig(); - - try { - SSLUtils.parseIdentity(config,SSLFactory.builder()); - fail("Expected SSLConfigException"); - } catch (SSLConfigException e) { - assertEquals(SSLConfigException.Types.MISSING_CERT_AND_KEY_FILE.getMessage(), e.getMessage()); - } - } - -} diff --git a/src/test/kotlin/io/javalin/community/ssl/APITests.kt b/src/test/kotlin/io/javalin/community/ssl/APITests.kt new file mode 100644 index 0000000..dafe419 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/APITests.kt @@ -0,0 +1,104 @@ +package io.javalin.community.ssl + +import io.javalin.Javalin +import okhttp3.Request +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +@Tag("integration") +class APITests : IntegrationTestClass(){ + + @Test + fun `plugin can be loaded using factory method`() { + Javalin.create { config -> + config.registerPlugin(SSLPlugin{ + it.insecurePort = 9999 + it.secure = false + }) + } + .get("/") { ctx -> ctx.result("Hello World") } + .start() + + untrustedClient.newCall(Request.Builder().url("http://localhost:9999/").build()).execute().use { + assert(it.isSuccessful) + } + } + + + @Suppress("UNREACHABLE_CODE","UNUSED_VARIABLE") + fun `complete api compiles`(){ + //Dummy variables to make sure the code compiles + val secondCertInputStream: java.io.InputStream = TODO() + val keyInputStream: java.io.InputStream = TODO() + val certString: String = TODO() + val keyString: String = TODO() + val keystoreInputStream: java.io.InputStream = TODO() + val keyPassword: String = TODO() + + val plugin = SSLPlugin { + // Connection options + it.host=null // Host to bind to, by default it will bind to all interfaces + it.insecure=true // Toggle the default http (insecure) connector + it.secure=true // Toggle the default https (secure) connector + it.http2=true // Toggle HTTP/2 Support + + it.securePort=443 // Port to use on the SSL (secure) connector (TCP) + it.insecurePort=80 // Port to use on the http (insecure) connector (TCP) + it.redirect=false // Redirect all http requests to https + + it.sniHostCheck=true // Enable SNI hostname verification + it.tlsConfig=TLSConfig.INTERMEDIATE // Set the TLS configuration. (by default it uses Mozilla's intermediate configuration) + + // PEM loading options (mutually exclusive) + it.pemFromPath("/path/to/cert.pem","/path/to/key.pem") // load from the given paths + it.pemFromPath("/path/to/cert.pem","/path/to/key.pem","keyPassword") // load from the given paths with the given key password + it.pemFromClasspath("certName.pem","keyName.pem") // load from the given paths in the classpath + it.pemFromClasspath("certName.pem","keyName.pem","keyPassword") // load from the given paths in the classpath with the given key password + it.pemFromInputStream(secondCertInputStream,keyInputStream) // load from the given input streams + it.pemFromInputStream(secondCertInputStream,keyInputStream,"keyPassword") // load from the given input streams with the given key password + it.pemFromString(certString,keyString) // load from the given strings + it.pemFromString(certString,keyString,"keyPassword") // load from the given strings with the given key password + + // Keystore loading options (PKCS#12/JKS) (mutually exclusive) + it.keystoreFromPath("/path/to/keystore.jks","keystorePassword") // load the keystore from the given path + it.keystoreFromClasspath("keyStoreName.p12","keystorePassword") // load the keystore from the given path in the classpath + it.keystoreFromInputStream(keystoreInputStream,"keystorePassword") // load the keystore from the given input stream + + // Advanced options + it.configConnectors { con -> con.dump() } // Set a Consumer to configure the connectors + it.securityProvider = null // Use a custom security provider + it.withTrustConfig { trust -> trust.pemFromString("cert") } // Set the trust configuration, explained below. (by default all clients are trusted) + } + + SSLPlugin{ config -> + config.withTrustConfig{ + // Certificate loading options (PEM/DER/P7B) + it.certificateFromPath("path/to/certificate.pem") // load a PEM/DER/P7B cert from the given path + it.certificateFromClasspath("certificateName.pem") // load a PEM/DER/P7B cert from the given path in the classpath + it.certificateFromInputStream(secondCertInputStream) // load a PEM/DER/P7B cert from the given input stream + it.p7bCertificateFromString("p7b encoded certificate") // load a P7B cert from the given string + it.pemFromString("pem encoded certificate") // load a PEM cert from the given string + + // Trust store loading options (JKS/PKCS12) + it.trustStoreFromPath("path/to/truststore.jks", "password") // load a trust store from the given path + it.trustStoreFromClasspath("truststore.jks", "password") // load a trust store from the given path in the classpath + it.trustStoreFromInputStream(secondCertInputStream, "password") // load a trust store from the given input stream + } + } + + Javalin.create { + it.registerPlugin(plugin) + } + + plugin.reload{ + // any options other than loading certificates/keys will be ignored. + it.pemFromPath("/path/to/new/cert.pem","/path/to/new/key.pem") + + // you can also replace trust configuration + it.withTrustConfig{ trust -> + trust.certificateFromPath("path/to/new/certificate.pem") + } + } + } + +} diff --git a/src/test/kotlin/io/javalin/community/ssl/IntegrationTestClass.kt b/src/test/kotlin/io/javalin/community/ssl/IntegrationTestClass.kt new file mode 100644 index 0000000..7a1b922 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/IntegrationTestClass.kt @@ -0,0 +1,161 @@ +package io.javalin.community.ssl + +import io.javalin.Javalin +import io.javalin.community.ssl.certs.Server +import io.javalin.config.JavalinConfig +import io.javalin.http.Context +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.tls.HandshakeCertificates +import okhttp3.tls.decodeCertificatePem +import org.junit.jupiter.api.Assertions +import org.slf4j.LoggerFactory +import java.io.IOException +import java.security.* +import java.security.cert.CertificateException +import java.security.cert.X509Certificate +import java.util.* +import java.util.concurrent.atomic.AtomicInteger +import java.util.function.Consumer +import java.util.function.Function +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSession +import javax.net.ssl.X509TrustManager + +abstract class IntegrationTestClass { + private fun assertWorks(protocol: Protocol, givenConfig: Consumer) { + var config = givenConfig + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + val url = if (protocol == Protocol.HTTP) http else https + config = config.andThen { sslConfig: SSLConfig -> + sslConfig.insecurePort = insecurePort + sslConfig.securePort = securePort + } + val app : Javalin by lazy { createTestApp(config) } + try { + app.start() + val response = client.newCall(Request.Builder().url(url).build()).execute() + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + response.close() + } catch (e: IOException) { + Assertions.fail(e) + } finally { + app.stop() + } + } + + fun assertSslWorks(config: Consumer) { + assertWorks(Protocol.HTTPS, config) + } + + fun assertHttpWorks(config: Consumer) { + assertWorks(Protocol.HTTP, config) + } + + enum class Protocol { + HTTP, + HTTPS + } + + companion object { + @JvmField + val log = LoggerFactory.getLogger(IntegrationTestClass::class.java) + const val SUCCESS = "success" + @JvmField + val HTTPS_URL_WITH_PORT = Function { port: Int? -> String.format("https://localhost:%s/", port) } + @JvmField + val HTTP_URL_WITH_PORT = Function { port: Int? -> String.format("http://localhost:%s/", port) } + private val trustAllCerts = arrayOf(object : X509TrustManager { + override fun checkClientTrusted(chain: Array, authType: String) {} + override fun checkServerTrusted(chain: Array, authType: String) {} + override fun getAcceptedIssuers(): Array { + return arrayOf() + } + }) + @JvmStatic + val ports = AtomicInteger(10000) + @JvmField + val client = createHttpsClient() + @JvmField + val untrustedClient = untrustedHttpsClient() + private fun createHttpsClient(): OkHttpClient { + val builder = HandshakeCertificates.Builder() + builder.addTrustedCertificate(Server.CERTIFICATE_AS_STRING.decodeCertificatePem()) + try { + val ks = KeyStore.getInstance("pkcs12") + ks.load(Server.P12_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), Server.KEY_STORE_PASSWORD.toCharArray()) + for (alias in Collections.list(ks.aliases())) { + builder.addTrustedCertificate(ks.getCertificate(alias) as X509Certificate) + } + } catch (e: IOException) { + throw RuntimeException(e) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } catch (e: CertificateException) { + throw RuntimeException(e) + } catch (e: KeyStoreException) { + throw RuntimeException(e) + } + val clientCertificates: HandshakeCertificates = builder.build() + + return OkHttpClient.Builder() + .hostnameVerifier { _: String?, _: SSLSession? -> true } + .sslSocketFactory(clientCertificates.sslSocketFactory(),clientCertificates.trustManager) + .build() + } + + private fun untrustedHttpsClient(): OkHttpClient { + val newBuilder: OkHttpClient.Builder = untrustedClientBuilder() + return newBuilder.build() + } + + @JvmStatic + protected fun untrustedClientBuilder(): OkHttpClient.Builder { + val sslContext: SSLContext = try { + SSLContext.getInstance("SSL") + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } + try { + sslContext.init(null, trustAllCerts, SecureRandom()) + } catch (e: KeyManagementException) { + throw RuntimeException(e) + } + val newBuilder = OkHttpClient.Builder() + newBuilder.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0]) + newBuilder.hostnameVerifier { _: String?, _: SSLSession? -> true } + return newBuilder + } + + @JvmStatic + fun createTestApp(config: Consumer): Javalin { + return Javalin.create { javalinConfig: JavalinConfig -> + javalinConfig.showJavalinBanner = false + javalinConfig.registerPlugin(SSLPlugin(config)) + javalinConfig.router.mount{ + it.get("/", { ctx: Context -> ctx.result(SUCCESS) }) + } + } + } + + @JvmStatic + @Throws(IOException::class) + protected fun testSuccessfulEndpoint(client: OkHttpClient, url: String, protocol: okhttp3.Protocol) { + val response = client.newCall(Request.Builder().url(url).build()).execute() + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + Assertions.assertEquals(protocol, response.protocol) + response.close() + } + + @JvmStatic + @Throws(IOException::class) + protected fun testSuccessfulEndpoint(url: String, protocol: okhttp3.Protocol) { + testSuccessfulEndpoint(client, url, protocol) + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/KeystoreLoadingTests.kt b/src/test/kotlin/io/javalin/community/ssl/KeystoreLoadingTests.kt new file mode 100644 index 0000000..e0adc87 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/KeystoreLoadingTests.kt @@ -0,0 +1,281 @@ +package io.javalin.community.ssl + +import io.javalin.community.ssl.certs.Server +import nl.altindag.ssl.exception.GenericIOException +import nl.altindag.ssl.exception.GenericKeyStoreException +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.io.InputStream +import java.net.URISyntaxException +import java.nio.file.Path +import java.util.function.Supplier + +@Tag("integration") +class KeystoreLoadingTests : IntegrationTestClass() { + ////////////////////////////// + // Valid keystore loading // + ////////////////////////////// + @Test + fun `loading a valid JKS keystore from the classpath`() { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + Server.P12_KEY_STORE_NAME, + Server.KEY_STORE_PASSWORD + ) + } + } + + @Test + fun `loading a valid P12 keystore from the classpath`() { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + Server.P12_KEY_STORE_NAME, + Server.KEY_STORE_PASSWORD + ) + } + } + + @Test + fun `loading a valid JKS keystore from a path`() { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + Server.P12_KEY_STORE_PATH, + Server.KEY_STORE_PASSWORD + ) + } + } + + @Test + fun `loading a valid P12 keystore from a path`() { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + Server.P12_KEY_STORE_PATH, + Server.KEY_STORE_PASSWORD + ) + } + } + + @Test + fun `loading a valid JKS keystore from an input stream`() { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + Server.JKS_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), + Server.KEY_STORE_PASSWORD + ) + } + } + + @Test + fun `loading a valid P12 keystore from an input stream`() { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + Server.P12_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), + Server.KEY_STORE_PASSWORD + ) + } + } + + ////////////////////////////// + // Invalid keystore loading // + ////////////////////////////// + @Test + fun `loading a missing JKS keystore from the classpath fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + "invalid", + Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a JKS keystore from the classpath with an invalid password fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + Server.JKS_KEY_STORE_NAME, "invalid" + ) + } + } + } + + @Test + fun `loading a P12 keystore from the classpath with an invalid password fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + Server.P12_KEY_STORE_NAME, "invalid" + ) + } + } + } + + @Test + fun `loading a missing JKS keystore from a path fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + "invalid", + Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a JKS keystore from a path with an invalid password fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + Server.JKS_KEY_STORE_PATH, "invalid" + ) + } + } + } + + @Test + fun `loading a P12 keystore from a path with an invalid password fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + Server.P12_KEY_STORE_PATH, "invalid" + ) + } + } + } + + @Test + fun `loading a missing JKS keystore from an input stream fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + InputStream.nullInputStream(), Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a JKS keystore from an input stream with an invalid password fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + Server.JKS_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), "invalid" + ) + } + } + } + + @Test + fun `loading a P12 keystore from an input stream with an invalid password fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + Server.P12_KEY_STORE_INPUT_STREAM_SUPPLIER.get(), "invalid" + ) + } + } + } + + @Test + fun `loading a malformed JKS keystore from the classpath fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + MALFORMED_JKS_FILE_NAME, Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a malformed P12 keystore from the classpath fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromClasspath( + MALFORMED_P12_FILE_NAME, Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a malformed JKS keystore from a path fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + MALFORMED_JKS_FILE_PATH, Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a malformed P12 keystore from a path fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromPath( + MALFORMED_P12_FILE_PATH, Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `Loading a malformed JKS keystore from an input stream fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + MALFORMED_JKS_INPUT_STREAM_SUPPLIER.invoke(), Server.KEY_STORE_PASSWORD + ) + } + } + } + + @Test + fun `loading a malformed P12 keystore from an input stream fails`() { + Assertions.assertThrows(GenericKeyStoreException::class.java) { + assertSslWorks { config: SSLConfig -> + config.keystoreFromInputStream( + MALFORMED_P12_INPUT_STREAM_SUPPLIER.invoke(), Server.KEY_STORE_PASSWORD + ) + } + } + } + + companion object { + private const val MALFORMED_JKS_FILE_NAME = "server/malformed.jks" + private const val MALFORMED_P12_FILE_NAME = "server/malformed.p12" + private val MALFORMED_JKS_FILE_PATH: String + private val MALFORMED_P12_FILE_PATH: String + + init { + try { + MALFORMED_JKS_FILE_PATH = + Path.of(ClassLoader.getSystemResource(MALFORMED_JKS_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + MALFORMED_P12_FILE_PATH = + Path.of(ClassLoader.getSystemResource(MALFORMED_P12_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + val MALFORMED_JKS_INPUT_STREAM_SUPPLIER : () -> InputStream = { + this::class.java.classLoader.getResourceAsStream(MALFORMED_JKS_FILE_NAME)!! + } + val MALFORMED_P12_INPUT_STREAM_SUPPLIER : () -> InputStream = { + this::class.java.classLoader.getResourceAsStream(MALFORMED_P12_FILE_NAME)!! + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/PemLoadingTests.kt b/src/test/kotlin/io/javalin/community/ssl/PemLoadingTests.kt new file mode 100644 index 0000000..d5a2d02 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/PemLoadingTests.kt @@ -0,0 +1,234 @@ +package io.javalin.community.ssl + +import io.javalin.community.ssl.certs.Server +import nl.altindag.ssl.exception.GenericIOException +import nl.altindag.ssl.pem.exception.CertificateParseException +import nl.altindag.ssl.pem.exception.PrivateKeyParseException +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.io.InputStream + +@Tag("integration") +class PemLoadingTests : IntegrationTestClass() { + @Test + fun `Loading a passwordless PEM file from a string works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromString( + Server.CERTIFICATE_AS_STRING, + Server.NON_ENCRYPTED_KEY_AS_STRING + ) + } + } + + @Test + fun `Loading a an invalid key PEM file from a string fails`() { + Assertions.assertThrows(PrivateKeyParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromString( + Server.CERTIFICATE_AS_STRING, "invalid" + ) + } + } + } + + @Test + fun `Loading a an invalid certificate PEM file from a string fails`() { + Assertions.assertThrows(CertificateParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromString( + "invalid", + Server.NON_ENCRYPTED_KEY_AS_STRING + ) + } + } + } + + @Test + fun `Loading a PEM file with a wrong password from a string fails`() { + Assertions.assertThrows(PrivateKeyParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromString( + Server.CERTIFICATE_AS_STRING, Server.ENCRYPTED_KEY_AS_STRING, "invalid" + ) + } + } + } + + @Test + fun `Loading an encrypted PEM file from a string works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromString( + Server.CERTIFICATE_AS_STRING, + Server.ENCRYPTED_KEY_AS_STRING, + Server.KEY_PASSWORD + ) + } + } + + @Test + fun `Loading a passwordless PEM file from the classpath works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromClasspath( + Server.CERTIFICATE_FILE_NAME, + Server.NON_ENCRYPTED_KEY_FILE_NAME + ) + } + } + + @Test + fun `Loading an encrypted PEM file from the classpath works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromClasspath( + Server.CERTIFICATE_FILE_NAME, + Server.ENCRYPTED_KEY_FILE_NAME, + Server.KEY_PASSWORD + ) + } + } + + @Test + fun `Loading a PEM file with a wrong password from the classpath fails`() { + Assertions.assertThrows(PrivateKeyParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromClasspath( + Server.CERTIFICATE_FILE_NAME, Server.ENCRYPTED_KEY_FILE_NAME, "invalid" + ) + } + } + } + + @Test + fun `Loading a PEM file from an invalid classpath cert location fails`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromClasspath( + "invalid", + Server.NON_ENCRYPTED_KEY_FILE_NAME + ) + } + } + } + + @Test + fun `Loading a PEM file from an invalid classpath key location fails`() { + Assertions.assertThrows(IllegalArgumentException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromClasspath( + Server.CERTIFICATE_FILE_NAME, "invalid" + ) + } + } + } + + @Test + fun `Loading a passwordless PEM file from a path works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromPath( + Server.CERTIFICATE_PATH, + Server.NON_ENCRYPTED_KEY_PATH + ) + } + } + + @Test + fun `Loading an encrypted PEM file from a path works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromPath( + Server.CERTIFICATE_PATH, + Server.ENCRYPTED_KEY_PATH, + Server.KEY_PASSWORD + ) + } + } + + @Test + fun `Loading a PEM file with a wrong password from a path fails`() { + Assertions.assertThrows(PrivateKeyParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromPath( + Server.CERTIFICATE_PATH, Server.ENCRYPTED_KEY_PATH, "invalid" + ) + } + } + } + + @Test + fun `Loading a PEM file from an invalid cert path fails`() { + Assertions.assertThrows(GenericIOException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromPath( + "invalid", + Server.NON_ENCRYPTED_KEY_PATH + ) + } + } + } + + @Test + fun `Loading a PEM file from an invalid key path fails`() { + Assertions.assertThrows(GenericIOException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromPath( + Server.CERTIFICATE_PATH, "invalid" + ) + } + } + } + + @Test + fun `Loading a passwordless PEM file from an input stream works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromInputStream( + Server.CERTIFICATE_INPUT_STREAM_SUPPLIER.get(), + Server.NON_ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get() + ) + } + } + + @Test + fun `Loading an encrypted PEM file from an input stream works`() { + assertSslWorks { config: SSLConfig -> + config.pemFromInputStream( + Server.CERTIFICATE_INPUT_STREAM_SUPPLIER.get(), + Server.ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get(), + Server.KEY_PASSWORD + ) + } + } + + @Test + fun `Loading a PEM file with a wrong password from an input stream fails`() { + Assertions.assertThrows(PrivateKeyParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromInputStream( + Server.CERTIFICATE_INPUT_STREAM_SUPPLIER.get(), + Server.ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get(), + "invalid" + ) + } + } + } + + @Test + fun `Loading a PEM file from an invalid cert input stream fails`() { + Assertions.assertThrows(CertificateParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromInputStream( + InputStream.nullInputStream(), Server.NON_ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get() + ) + } + } + } + + @Test + fun `Loading a PEM file from an invalid key input stream fails`() { + Assertions.assertThrows(PrivateKeyParseException::class.java) { + assertSslWorks { config: SSLConfig -> + config.pemFromInputStream( + Server.ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER.get(), InputStream.nullInputStream() + ) + } + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/SSLConfigExceptionTest.kt b/src/test/kotlin/io/javalin/community/ssl/SSLConfigExceptionTest.kt new file mode 100644 index 0000000..6691115 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/SSLConfigExceptionTest.kt @@ -0,0 +1,12 @@ +package io.javalin.community.ssl + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class SSLConfigExceptionTest { + @Test + fun `test all exception types`(){ + assertEquals("There is no certificate or key file provided",SSLConfigException(SSLConfigException.Types.MISSING_CERT_AND_KEY_FILE).message) + assertEquals("Both the certificate and key must be provided using the same method",SSLConfigException(SSLConfigException.Types.MULTIPLE_IDENTITY_LOADING_OPTIONS).message) + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/SSLConfigTests.kt b/src/test/kotlin/io/javalin/community/ssl/SSLConfigTests.kt new file mode 100644 index 0000000..24574ae --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/SSLConfigTests.kt @@ -0,0 +1,452 @@ +package io.javalin.community.ssl + +import io.javalin.Javalin +import io.javalin.community.ssl.certs.Server +import okhttp3.OkHttpClient +import okhttp3.Request +import org.eclipse.jetty.server.ConnectionFactory +import org.eclipse.jetty.server.ServerConnector +import org.eclipse.jetty.server.SslConnectionFactory +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.io.IOException +import java.util.* +import java.util.function.Supplier + +@Tag("integration") +class SSLConfigTests : IntegrationTestClass() { + @Test + fun `Test that the insecure connector is disabled when insecure is set to false`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.insecurePort = insecurePort + }.start().let { app -> + Assertions.assertThrows(Exception::class.java) { + client.newCall(Request.Builder().url(http).build()).execute() + } // should throw exception + val response = client.newCall(Request.Builder().url(https).build()).execute() // should not throw exception + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + app.stop() + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the secure connector is disabled when insecure is set to true`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.secure = false + config.insecurePort = insecurePort + config.securePort = securePort + }.start().let { _ -> + Assertions.assertThrows(Exception::class.java) { + client.newCall(Request.Builder().url(https).build()).execute() + } // should throw exception + val response = client.newCall(Request.Builder().url(http).build()).execute() // should not throw exception + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the insecure port can be changed`() { + try { + createTestApp { config: SSLConfig -> + config.secure = false + config.insecurePort = 8080 + }.start().let { _ -> + val response = client.newCall(Request.Builder().url("http://localhost:8080/").build()) + .execute() // should not throw exception + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the secure port can be changed`() { + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = 8443 + }.start().let { _ -> + val response = client.newCall(Request.Builder().url("https://localhost:8443/").build()) + .execute() // should not throw exception + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that redirecting from http to https works`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + val noRedirectClient: OkHttpClient = untrustedClientBuilder().also { it.followSslRedirects(false) }.build() + try { + createTestApp { config: SSLConfig -> + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.insecurePort = insecurePort + config.redirect = true + }.start().let { _ -> + val redirect = noRedirectClient.newCall(Request.Builder().url(http).build()).execute() + Assertions.assertTrue(redirect.isRedirect) + Assertions.assertEquals(https, redirect.header("Location")) + val redirected = client.newCall(Request.Builder().url(http).build()).execute() + Assertions.assertEquals(200, redirected.code) + Assertions.assertEquals(SUCCESS, redirected.body!!.string()) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the insecure connector works with http1-1`() { + val insecurePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + try { + createTestApp { config: SSLConfig -> + config.secure = false + config.http2 = false + config.insecurePort = insecurePort + }.start().let { _ -> testSuccessfulEndpoint(http, okhttp3.Protocol.HTTP_1_1) } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that http2 can be disabled on the insecure connector`() { + val http2client: OkHttpClient = + listOf(okhttp3.Protocol.H2_PRIOR_KNOWLEDGE).let{ OkHttpClient.Builder().protocols(it).build() } + val http1Client: OkHttpClient = OkHttpClient.Builder().build() + val insecurePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + try { + createTestApp { config: SSLConfig -> + config.secure = false + config.http2 = false + config.insecurePort = insecurePort + }.start().let { _ -> + Assertions.assertThrows(Exception::class.java) { + http2client.newCall( + Request.Builder().url(http).build() + ).execute() + } // Should fail to connect using HTTP/2 + testSuccessfulEndpoint(http1Client, http, okhttp3.Protocol.HTTP_1_1) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that http2 can be disabled on the secure connector`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.http2 = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.insecurePort = insecurePort + }.start().let { _ -> testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_1_1) } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the insecure connector works with http2`() { + val client: OkHttpClient = + listOf(okhttp3.Protocol.H2_PRIOR_KNOWLEDGE).let { OkHttpClient.Builder().protocols(it).build() } + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + try { + createTestApp { config: SSLConfig -> + config.secure = false + config.http2 = true + config.insecurePort = insecurePort + config.securePort = securePort + }.start().let { _ -> testSuccessfulEndpoint(client, http, okhttp3.Protocol.H2_PRIOR_KNOWLEDGE) } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the secure connector works with http2`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.insecurePort = insecurePort + }.start().let { _ -> testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that by default both connectors are enabled, and that http1 and http2 works`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + }.start().let { _ -> + testSuccessfulEndpoint(http, okhttp3.Protocol.HTTP_1_1) + testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the host can be changed`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.host = "localhost" + }.start().let { _ -> + testSuccessfulEndpoint(http, okhttp3.Protocol.HTTP_1_1) + testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the host change fails when it doesn't match`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.host = "wronghost" + }.start().let { _ -> Assertions.fail() } + } catch (ignored: Exception) {} + } + + @Test + fun `Test that sniHostCheck works when it matches`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.host = "localhost" + config.sniHostCheck = true + }.start().let { _ -> + testSuccessfulEndpoint(http, okhttp3.Protocol.HTTP_1_1) + testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that sniHostCheck fails when it doesn't match over https`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.GOOGLE_CERTIFICATE_AS_STRING, Server.GOOGLE_KEY_AS_STRING) + config.host = "localhost" + config.sniHostCheck = true + }.start().let { _ -> + //http request should be successful + testSuccessfulEndpoint(untrustedClient, http, okhttp3.Protocol.HTTP_1_1) + //https request should fail + val wrongHttpsResponse = untrustedClient.newCall(Request.Builder().url(https).build()).execute() + Assertions.assertEquals(400, wrongHttpsResponse.code) + Assertions.assertTrue( + Objects.requireNonNull(wrongHttpsResponse.body)?.string()!!.contains("Error 400 Invalid SNI") + ) + wrongHttpsResponse.close() + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that sniHostCheck can be disabled and a request with a wrong hostname can be made`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.GOOGLE_CERTIFICATE_AS_STRING, Server.GOOGLE_KEY_AS_STRING) + config.host = "localhost" + config.sniHostCheck = false + }.start().let { _ -> + testSuccessfulEndpoint(untrustedClient, http, okhttp3.Protocol.HTTP_1_1) + testSuccessfulEndpoint(untrustedClient, https, okhttp3.Protocol.HTTP_2) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the connectors can be configured through the consumer`() { + val insecurePort = ports.getAndIncrement() + val securePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecurePort = insecurePort + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.configConnectors { connector: ServerConnector -> + connector.setIdleTimeout(1000) + connector.name = "customName" + } + }.start().let { app -> + testSuccessfulEndpoint(http, okhttp3.Protocol.HTTP_1_1) + testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) + for (connector in app.unsafeConfig().pvt.jetty.server!!.connectors) { + Assertions.assertEquals(1000, connector.idleTimeout) + Assertions.assertEquals("customName", connector.name) + } + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the Security Provider can be automatically configured when the config is set to null`() { + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securityProvider = null + }.start().let { app -> + printSecurityProviderName(app) + testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the Security Provider works when it is set to the default`() { + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + }.start().let { app -> + printSecurityProviderName(app) + testSuccessfulEndpoint(https, okhttp3.Protocol.HTTP_2) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test no identity loaded`() { + Assertions.assertThrows(SSLConfigException::class.java) { + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = 8443 + }.start() + } + Assertions.assertTrue { + try { + createTestApp { + it.insecure = false + it.securePort = 8443 + }.start() + return@assertTrue false + } catch (e: SSLConfigException) { + return@assertTrue e.message?.contains(SSLConfigException.Types.MISSING_CERT_AND_KEY_FILE.message) == true + } + } + } + + companion object { + private fun getSecurityProviderName(app: Javalin): String { + val conn = app.jettyServer()!!.server().getConnectors()[0] as ServerConnector + return conn.connectionFactories.stream() + .filter { cf: ConnectionFactory? -> cf is SslConnectionFactory } + .map { cf: ConnectionFactory -> cf as SslConnectionFactory } + .map { sslConnectionFactory: SslConnectionFactory -> sslConnectionFactory.sslContextFactory.sslContext.provider.name } + .findFirst() + .orElseThrow() + } + + private fun printSecurityProviderName(app: Javalin) { + println("Security provider: " + getSecurityProviderName(app)) + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/SSLPluginTest.kt b/src/test/kotlin/io/javalin/community/ssl/SSLPluginTest.kt new file mode 100644 index 0000000..27b857a --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/SSLPluginTest.kt @@ -0,0 +1,247 @@ +package io.javalin.community.ssl + +import io.javalin.Javalin +import io.javalin.community.ssl.certs.Server +import io.javalin.config.JavalinConfig +import io.javalin.http.Context +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.tls.HandshakeCertificates +import okhttp3.tls.decodeCertificatePem +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.io.File +import java.io.IOException +import java.security.KeyStore +import java.security.KeyStoreException +import java.security.NoSuchAlgorithmException +import java.security.cert.CertificateException +import java.security.cert.X509Certificate +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLSession + +@Tag("integration") +class SSLPluginTest : IntegrationTestClass() { + @Test + fun `Test the reload of a pem identity`() { + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + + // Create a http client that trusts the self-signed certificates + val builder = HandshakeCertificates.Builder() + builder.addTrustedCertificate(Server.CERTIFICATE_AS_STRING.decodeCertificatePem()) // Valid certificate from Vigo + builder.addTrustedCertificate(Server.NORWAY_CERTIFICATE_AS_STRING.decodeCertificatePem()) // Valid certificate from Bergen + val clientCertificates: HandshakeCertificates = builder.build() + + // Two clients are needed, one for the initial connection and one for after the reload, due to the way OkHttp caches connections + val client: OkHttpClient = HostnameVerifier { _: String?, _: SSLSession? -> true } + .let { + OkHttpClient.Builder().sslSocketFactory( + clientCertificates.sslSocketFactory(), + clientCertificates.trustManager + ).hostnameVerifier(it).build() + } + val client2: OkHttpClient = HostnameVerifier { _: String?, _: SSLSession? -> true } + .let { + OkHttpClient.Builder().sslSocketFactory( + clientCertificates.sslSocketFactory(), + clientCertificates.trustManager + ).hostnameVerifier(it).build() + } + val sslPlugin = SSLPlugin { sslConfig: SSLConfig -> + sslConfig.insecure = false + sslConfig.securePort = securePort + sslConfig.pemFromString(Server.NORWAY_CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + } + try { + Javalin.create { config: JavalinConfig -> + config.showJavalinBanner = false + config.registerPlugin(sslPlugin) + config.router.mount{ + it.get("/") { ctx: Context -> ctx.result(SUCCESS) } + } + }.start().let { _ -> + + // Initial connection + var res = client.newCall(Request.Builder().url(https).build()).execute() + //Check that the certificate is the one we expect + var cert = res.handshake!!.peerCertificates[0] as X509Certificate + log.info("First Certificate: {}", cert.getSubjectX500Principal().name) + Assertions.assertTrue(cert.getIssuerX500Principal().name.contains("Bergen")) + + // Reload the identity + sslPlugin.reload { newConf: SSLConfig -> + newConf.pemFromString( + Server.CERTIFICATE_AS_STRING, + Server.NON_ENCRYPTED_KEY_AS_STRING + ) + } + // Second connection + res = client2.newCall(Request.Builder().url(https).build()).execute() + cert = res.handshake!!.peerCertificates[0] as X509Certificate + log.info("Second Certificate: {}", cert.getSubjectX500Principal().name) + Assertions.assertTrue(cert.getIssuerX500Principal().name.contains("Vigo")) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Throws(CertificateException::class, KeyStoreException::class, IOException::class, NoSuchAlgorithmException::class) + fun testReloadIdentityKeystore(norwayKeyStorePath: String, vigoKeyStorePath: String) { + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + val certificates: MutableList = ArrayList() + + // Create a http client that trusts the self-signed certificates + val keyStore = KeyStore.getInstance( + File(norwayKeyStorePath), + Server.KEY_STORE_PASSWORD.toCharArray() + ) // Valid certificate from Bergen + keyStore.aliases().asIterator().forEachRemaining { alias: String? -> + try { + certificates.add(keyStore.getCertificate(alias) as X509Certificate) + } catch (e: KeyStoreException) { + Assertions.fail(e) + } + } + val keyStore2 = KeyStore.getInstance( + File(vigoKeyStorePath), + Server.KEY_STORE_PASSWORD.toCharArray() + ) // Valid certificate from Vigo + keyStore2.aliases().asIterator().forEachRemaining { alias: String? -> + try { + certificates.add(keyStore2.getCertificate(alias) as X509Certificate) + } catch (e: KeyStoreException) { + Assertions.fail(e) + } + } + + // Create a http client that trusts the self-signed certificates + val builder = HandshakeCertificates.Builder() + for (certificate in certificates) { + builder.addTrustedCertificate(certificate) + } + val clientCertificates: HandshakeCertificates = builder.build() + + // Two clients are needed, one for the initial connection and one for after the reload, due to the way OkHttp caches connections + val client: OkHttpClient = HostnameVerifier { _: String?, _: SSLSession? -> true } + .let { + OkHttpClient.Builder().sslSocketFactory( + clientCertificates.sslSocketFactory(), + clientCertificates.trustManager + ).hostnameVerifier(it).build() + } + val client2: OkHttpClient = HostnameVerifier { _: String?, _: SSLSession? -> true } + .let { + OkHttpClient.Builder().sslSocketFactory( + clientCertificates.sslSocketFactory(), + clientCertificates.trustManager + ).hostnameVerifier(it).build() + } + val sslPlugin = SSLPlugin { sslConfig: SSLConfig -> + sslConfig.insecure = false + sslConfig.securePort = securePort + sslConfig.keystoreFromPath(norwayKeyStorePath, Server.KEY_STORE_PASSWORD) + } + try { + Javalin.create { config: JavalinConfig -> + config.showJavalinBanner = false + config.registerPlugin(sslPlugin) + config.router.mount{ + it.get("/") { ctx: Context -> ctx.result(SUCCESS) } + } + }.start().let { _ -> + + // Initial connection + var res = client.newCall(Request.Builder().url(https).build()).execute() + //Check that the certificate is the one we expect + var cert = res.handshake!!.peerCertificates[0] as X509Certificate + log.info("First Certificate: {}", cert.getSubjectX500Principal().name) + Assertions.assertTrue(cert.getIssuerX500Principal().name.contains("Bergen")) + + // Reload the identity + sslPlugin.reload { newConf: SSLConfig -> + newConf.keystoreFromPath( + vigoKeyStorePath, + Server.KEY_STORE_PASSWORD + ) + } + + // Second connection + res = client2.newCall(Request.Builder().url(https).build()).execute() + cert = res.handshake!!.peerCertificates[0] as X509Certificate + log.info("Second Certificate: {}", cert.getSubjectX500Principal().name) + Assertions.assertTrue(cert.getIssuerX500Principal().name.contains("Vigo")) + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test the reload of a p12 identity`() { + try { + testReloadIdentityKeystore(Server.NORWAY_P12_KEY_STORE_PATH, Server.P12_KEY_STORE_PATH) + } catch (e: Exception) { + Assertions.fail(e) + } + } + + @Test + fun `Test the reload of JKS identity`() { + try { + testReloadIdentityKeystore(Server.NORWAY_JKS_KEY_STORE_PATH, Server.JKS_KEY_STORE_PATH) + } catch (e: Exception) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the reload of a server with no SSL connector fails`() { + val insecurePort = ports.getAndIncrement() + val http = HTTP_URL_WITH_PORT.apply(insecurePort) + val sslPlugin = SSLPlugin { sslConfig: SSLConfig -> + sslConfig.secure = false + sslConfig.insecurePort = insecurePort + } + try { + Javalin.create { config: JavalinConfig -> + config.showJavalinBanner = false + config.registerPlugin(sslPlugin) + config.router.mount{ + it.get("/") { ctx: Context -> ctx.result(SUCCESS) } + } + }.start().let { _ -> + val res = OkHttpClient().newCall(Request.Builder().url(http).build()).execute() + Assertions.assertTrue(res.isSuccessful) + Assertions.assertThrows(IllegalStateException::class.java) { + sslPlugin.reload { newConf: SSLConfig -> + newConf.pemFromString( + Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING + ) + } + } + } + } catch (e: IOException) { + Assertions.fail(e) + } + } + + @Test + fun `Test that the reload of a non started server fails`() { + val sslPlugin = SSLPlugin { sslConfig: SSLConfig -> + sslConfig.secure = false + sslConfig.insecurePort = ports.getAndIncrement() + } + Assertions.assertThrows(IllegalStateException::class.java) { + sslPlugin.reload { newConf: SSLConfig -> + newConf.pemFromString( + Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING + ) + } + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/TLSConfigTest.kt b/src/test/kotlin/io/javalin/community/ssl/TLSConfigTest.kt new file mode 100644 index 0000000..e268fd9 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/TLSConfigTest.kt @@ -0,0 +1,130 @@ +package io.javalin.community.ssl + +import io.javalin.community.ssl.certs.Server +import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient +import okhttp3.Request +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.net.UnknownServiceException +import javax.net.ssl.SSLHandshakeException + + +@Tag("integration") +class TLSConfigTest : IntegrationTestClass() { + @Test + fun `test that a Modern TLS config does not allow old protocols`() { + + val protocols = TLSConfig.OLD.protocols.subtract(TLSConfig.MODERN.protocols.asIterable().toSet()) + + // remove modern protocols from old protocols, so that ONLY unsupported protocols are left + val client = clientWithTLSConfig(TLSConfig(TLSConfig.MODERN.cipherSuites, protocols.toTypedArray())) + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + createTestApp { config: SSLConfig -> + config.insecure = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.tlsConfig = TLSConfig.MODERN + }.start().let { _ -> + //Should fail with SSLHandshakeException because of the old protocols + Assertions.assertThrows(SSLHandshakeException::class.java) { + client.newCall( + Request.Builder().url( + https + ).build() + ).execute() + } + } + } + + @Test + fun `test that a Modern TLS config does not allow old cipher suites`() { + val cipherSuites = TLSConfig.OLD.cipherSuites.subtract(TLSConfig.MODERN.cipherSuites.asIterable().toSet()) + // remove modern cipher suites from old cipher suites, so that we can test ONLY the old cipher suites + val client = clientWithTLSConfig(TLSConfig(cipherSuites.toTypedArray(), TLSConfig.MODERN.protocols)) + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + createTestApp { config: SSLConfig -> + config.insecure = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.tlsConfig = TLSConfig.MODERN + }.start().let { _ -> + //Should fail with SSLHandshakeException because of the old cipher suites + Assertions.assertThrows(SSLHandshakeException::class.java) { + client.newCall( + Request.Builder().url( + https + ).build() + ).execute() + } + } + } + + @Test + fun `test that an Intermediate TLS config does not allow old protocols`() { + val protocols = TLSConfig.OLD.protocols.subtract(TLSConfig.INTERMEDIATE.protocols.asIterable().toSet()) + // remove intermediate protocols from old protocols, so that ONLY unsupported protocols are left + val client = clientWithTLSConfig(TLSConfig(TLSConfig.INTERMEDIATE.cipherSuites, protocols.toTypedArray())) + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + createTestApp { config: SSLConfig -> + config.insecure = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.tlsConfig = TLSConfig.INTERMEDIATE + }.start().let { _ -> + //Should fail with SSLHandshakeException because of the old protocols + Assertions.assertThrows(UnknownServiceException::class.java) { + client.newCall( + Request.Builder().url(https).build() + ).execute() + } + } + } + + @Test + fun `test that an Intermediate TLS config does not allow old cipher suites`(){ + val cipherSuites = TLSConfig.OLD.cipherSuites.subtract(TLSConfig.INTERMEDIATE.cipherSuites.asIterable().toSet()) + // remove intermediate cipher suites from old cipher suites, so that we can test ONLY the old cipher suites + val client = clientWithTLSConfig(TLSConfig(cipherSuites.toTypedArray(), TLSConfig.INTERMEDIATE.protocols)) + val securePort = ports.getAndIncrement() + val https = HTTPS_URL_WITH_PORT.apply(securePort) + createTestApp { config: SSLConfig -> + config.insecure = false + config.pemFromString(Server.CERTIFICATE_AS_STRING, Server.NON_ENCRYPTED_KEY_AS_STRING) + config.securePort = securePort + config.tlsConfig = TLSConfig.INTERMEDIATE + }.start().let { _ -> + //Should fail with SSLHandshakeException because of the old cipher suites + Assertions.assertThrows(SSLHandshakeException::class.java) { + client.newCall( + Request.Builder().url( + https + ).build() + ).execute() + } + } + } + + companion object { + + private fun clientWithTLSConfig(config: TLSConfig): OkHttpClient { + + + val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) + .cipherSuites(*config.cipherSuites) + .tlsVersions(*config.protocols) + .build() + + return listOf(spec).let { + OkHttpClient.Builder() + .connectionSpecs(it) + .build() + } + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/TrustConfigTests.kt b/src/test/kotlin/io/javalin/community/ssl/TrustConfigTests.kt new file mode 100644 index 0000000..725217b --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/TrustConfigTests.kt @@ -0,0 +1,310 @@ +package io.javalin.community.ssl + +import io.javalin.community.ssl.certs.Client +import okhttp3.ConnectionPool +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.tls.HandshakeCertificates +import okhttp3.tls.HeldCertificate +import okhttp3.tls.decodeCertificatePem +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.io.IOException +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.security.KeyManagementException +import java.security.NoSuchAlgorithmException +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import java.util.function.Supplier +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSession + +@Tag("integration") +class TrustConfigTests : IntegrationTestClass() { + @Test + fun `Client with no certificate should not be able to access the server`() { + val unauthClient = client //This is the client without the client certificate + val securePort = ports.getAndIncrement() + val url = HTTPS_URL_WITH_PORT.apply(securePort) + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.http2 = false // Disable HTTP/2 to avoid "connection closed" errors in tests due to connection reuse + config.pemFromString(Client.SERVER_CERTIFICATE_AS_STRING, Client.SERVER_PRIVATE_KEY_AS_STRING) + config.withTrustConfig { trustConfig: TrustConfig -> trustConfig.pemFromString(Client.CLIENT_CERTIFICATE_AS_STRING) } + } + .start().let { _ -> + Assertions.assertThrows(Exception::class.java) { + unauthClient.newCall( + Request.Builder().url(url).build() + ).execute() + } + } + } + + @Test + fun `Client with a wrong certificate should not be able to access the server`() { + val securePort = ports.getAndIncrement() + val url = HTTPS_URL_WITH_PORT.apply(securePort) + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.http2 = false // Disable HTTP/2 to avoid "connection closed" errors in tests due to connection reuse + config.pemFromString(Client.SERVER_CERTIFICATE_AS_STRING, Client.SERVER_PRIVATE_KEY_AS_STRING) + config.withTrustConfig { trustConfig: TrustConfig -> trustConfig.pemFromString(Client.CLIENT_CERTIFICATE_AS_STRING) } + }.start().let { _ -> + //wrongClient.get().newCall(new Request.Builder().url(url).build()).execute(); + val req = HttpRequest.newBuilder() + .uri(URI.create(url)) + .build() + Assertions.assertThrows(Exception::class.java) { + wrongJavaClient.get() + .send(req, HttpResponse.BodyHandler { _: HttpResponse.ResponseInfo? -> null }) + } + } + } + + @Test + fun `Loading PEM from a path works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromPath(Client.CLIENT_PEM_PATH) } + } + + @Test + fun `Loading P7B from a path works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromPath(Client.CLIENT_P7B_PATH) } + } + + @Test + fun `Loading DER from a path works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromPath(Client.CLIENT_DER_PATH) } + } + + @Test + fun `Loading PEM from the classpath works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromClasspath(Client.CLIENT_PEM_FILE_NAME) } + } + + @Test + fun `Loading P7B from the classpath works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromClasspath(Client.CLIENT_P7B_FILE_NAME) } + } + + @Test + fun `Loading DER from the classpath works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromClasspath(Client.CLIENT_DER_FILE_NAME) } + } + + @Test + fun `Loading PEM from an input stream works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromInputStream(Client.CLIENT_PEM_INPUT_STREAM_SUPPLIER.get()) } + } + + @Test + fun `Loading P7B from an input stream works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromInputStream(Client.CLIENT_P7B_INPUT_STREAM_SUPPLIER.get()) } + } + + @Test + fun `Loading DER from an input stream works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.certificateFromInputStream(Client.CLIENT_DER_INPUT_STREAM_SUPPLIER.get()) } + } + + @Test + fun `Loading PEM from a string works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.pemFromString(Client.CLIENT_CERTIFICATE_AS_STRING) } + } + + @Test + fun `Loading P7B from a string works`() { + trustConfigWorks { trustConfig: TrustConfig -> trustConfig.p7bCertificateFromString(Client.CLIENT_P7B_CERTIFICATE_AS_STRING) } + } + + @Test + fun `Loading a JKS Keystore from a path works`() { + trustConfigWorks { trustConfig: TrustConfig -> + trustConfig.trustStoreFromPath( + Client.CLIENT_JKS_PATH, + Client.KEYSTORE_PASSWORD + ) + } + } + + @Test + fun `Loading a P12 Keystore from a path works`() { + trustConfigWorks { trustConfig: TrustConfig -> + trustConfig.trustStoreFromPath( + Client.CLIENT_P12_PATH, + Client.KEYSTORE_PASSWORD + ) + } + } + + @Test + fun `Loading a JKS Keystore from the classpath works`() { + trustConfigWorks { trustConfig: TrustConfig -> + trustConfig.trustStoreFromClasspath( + Client.CLIENT_JKS_FILE_NAME, + Client.KEYSTORE_PASSWORD + ) + } + } + + @Test + fun `Loading a P12 Keystore from the classpath works`() { + trustConfigWorks { trustConfig: TrustConfig -> + trustConfig.trustStoreFromClasspath( + Client.CLIENT_P12_FILE_NAME, + Client.KEYSTORE_PASSWORD + ) + } + } + + @Test + fun `Loading a JKS Keystore from an input stream works`() { + trustConfigWorks { trustConfig: TrustConfig -> + trustConfig.trustStoreFromInputStream( + Client.CLIENT_JKS_INPUT_STREAM_SUPPLIER.get(), + Client.KEYSTORE_PASSWORD + ) + } + } + + @Test + fun `Loading a P12 Keystore from an input stream works`() { + trustConfigWorks { trustConfig: TrustConfig -> + trustConfig.trustStoreFromInputStream( + Client.CLIENT_P12_INPUT_STREAM_SUPPLIER.get(), + Client.KEYSTORE_PASSWORD + ) + } + } + + companion object { + private val authenticatedClient = + Supplier { httpsClientBuilder(Client.CLIENT_CERTIFICATE_AS_STRING, Client.CLIENT_PRIVATE_KEY_AS_STRING) } + private val wrongClient = Supplier { + httpsClientBuilder( + Client.WRONG_CLIENT_CERTIFICATE_AS_STRING, + Client.WRONG_CLIENT_PRIVATE_KEY_AS_STRING + ) + } + private val wrongJavaClient = Supplier { + javaHttpClientBuilder( + Client.WRONG_CLIENT_CERTIFICATE_AS_STRING, + Client.WRONG_CLIENT_PRIVATE_KEY_AS_STRING + ) + } + + private fun httpsClientBuilder(clientCertificate: String, privateKey: String): OkHttpClient { + val builder = HandshakeCertificates.Builder() + //Server certificate + builder.addTrustedCertificate(Client.SERVER_CERTIFICATE_AS_STRING.decodeCertificatePem()) + + //Client certificate (Concatenated with the private key) + val heldCertificate = HeldCertificate.decode(clientCertificate + privateKey) + builder.heldCertificate(heldCertificate) + val clientCertificates: HandshakeCertificates = builder.build() + val sslContext: SSLContext + try { + sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, null, null) + sslContext.init( + arrayOf(clientCertificates.keyManager), arrayOf(clientCertificates.trustManager), + null + ) + } catch (e: KeyManagementException) { + throw RuntimeException(e) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } + return ConnectionPool(0, 1, TimeUnit.MICROSECONDS).let { + OkHttpClient.Builder() + .sslSocketFactory(sslContext.socketFactory, clientCertificates.trustManager) + .hostnameVerifier{_,_ -> true} + .connectionPool(it) + .build() + } + } + + /** + * Needed in order to test multiple wrong clients, see: + * [Java HTTPS client certificate authentication](https://stackoverflow.com/a/32513368/5899345) + * [ + * Java caching SSL failures - can I flush these somehow +](https://stackoverflow.com/questions/54671365/java-caching-ssl-failures-can-i-flush-these-somehow) * + */ + private fun javaHttpClientBuilder(clientCertificate: String, privateKey: String): HttpClient { + val builder = HandshakeCertificates.Builder() + //Server certificate + builder.addTrustedCertificate(Client.SERVER_CERTIFICATE_AS_STRING.decodeCertificatePem()) + + //Client certificate (Concatenated with the private key) + val heldCertificate = HeldCertificate.decode(clientCertificate + privateKey) + builder.heldCertificate(heldCertificate) + val clientCertificates: HandshakeCertificates = builder.build() + val sslContext: SSLContext + try { + sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, null, null) + sslContext.init( + arrayOf(clientCertificates.keyManager), arrayOf(clientCertificates.trustManager), + null + ) + } catch (e: KeyManagementException) { + throw RuntimeException(e) + } catch (e: NoSuchAlgorithmException) { + throw RuntimeException(e) + } + return HttpClient.newBuilder() + .sslContext(sslContext) + .build() + } + + @Throws(IOException::class) + protected fun testSuccessfulEndpoint(url: String) { + val client = authenticatedClient.get() + val response = client.newCall(Request.Builder().url(url).build()).execute() + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + response.close() + } + + protected fun testWrongCertOnEndpoint(url: String?) { + val req = HttpRequest.newBuilder() + .uri(URI.create(url)) + .build() + Assertions.assertThrows(Exception::class.java) { + wrongJavaClient.get().send(req, HttpResponse.BodyHandler { response: HttpResponse.ResponseInfo -> + println(response.statusCode()) + null + }) + } + } + + protected fun trustConfigWorks(consumer: Consumer) { + val securePort = ports.getAndIncrement() + val url = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.pemFromString(Client.SERVER_CERTIFICATE_AS_STRING, Client.SERVER_PRIVATE_KEY_AS_STRING) + config.http2 = false + config.withTrustConfig(consumer) + }.start().let { _ -> + testSuccessfulEndpoint(url) + testWrongCertOnEndpoint(url) + } + } catch (e: Exception) { + Assertions.fail(e) + } + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/certs/CertificateAuthorityTests.kt b/src/test/kotlin/io/javalin/community/ssl/certs/CertificateAuthorityTests.kt new file mode 100644 index 0000000..fabd1e6 --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/certs/CertificateAuthorityTests.kt @@ -0,0 +1,217 @@ +package io.javalin.community.ssl.certs + +import io.javalin.Javalin +import io.javalin.community.ssl.IntegrationTestClass +import io.javalin.community.ssl.SSLConfig +import io.javalin.community.ssl.SSLPlugin +import io.javalin.community.ssl.TrustConfig +import io.javalin.config.JavalinConfig +import io.javalin.http.Context +import nl.altindag.ssl.SSLFactory +import nl.altindag.ssl.pem.util.PemUtils +import okhttp3.OkHttpClient +import okhttp3.Request +import org.eclipse.jetty.server.ServerConnector +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import java.io.IOException +import java.util.* +import java.util.function.Supplier + +/** + * Tests for the testing the trust of Certificates using a CA. + * [issue](https://github.com/javalin/javalin-ssl/issues/56#issuecomment-1378373123) + */ +@Tag("integration") +class CertificateAuthorityTests : IntegrationTestClass() { + @Test + fun `Client certificate works when trusting root CA`() { + val keyManager = PemUtils.loadIdentityMaterial(CLIENT_FULLCHAIN_CER, CLIENT_KEY_NAME) + val sslFactory = SSLFactory.builder() + .withIdentityMaterial(keyManager) + .withTrustingAllCertificatesWithoutValidation() + .build() + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(sslFactory.sslSocketFactory, sslFactory.trustManager.orElseThrow()) + builder.hostnameVerifier(sslFactory.hostnameVerifier) + assertClientWorks(builder.build()) + } + + @Test + fun `Client fails when no certificate is provided`() { + val sslFactory = SSLFactory.builder() + .withTrustingAllCertificatesWithoutValidation() + .build() + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(sslFactory.sslSocketFactory, sslFactory.trustManager.orElseThrow()) + builder.hostnameVerifier(sslFactory.hostnameVerifier) + assertClientFails(builder.build()) + } + + @Test + fun `Client fails when a self-signed certificate is provided, and a CA is trusted`() { + val sslFactory = SSLFactory.builder() + .withIdentityMaterial( + PemUtils.parseIdentityMaterial( + Client.CLIENT_CERTIFICATE_AS_STRING, + Client.CLIENT_PRIVATE_KEY_AS_STRING, + "".toCharArray() + ) + ) + .withTrustingAllCertificatesWithoutValidation() + .build() + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(sslFactory.sslSocketFactory, sslFactory.trustManager.orElseThrow()) + builder.hostnameVerifier(sslFactory.hostnameVerifier) + assertClientFails(builder.build()) + } + + @Test + fun `Client fails when a certificate without chain is provided, and a CA is trusted`() { + val keyManager = PemUtils.loadIdentityMaterial(CLIENT_CER, CLIENT_KEY_NAME) + val sslFactory = SSLFactory.builder() + .withIdentityMaterial(keyManager) + .withTrustingAllCertificatesWithoutValidation() + .build() + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(sslFactory.sslSocketFactory, sslFactory.trustManager.orElseThrow()) + builder.hostnameVerifier(sslFactory.hostnameVerifier) + assertClientFails(builder.build()) + } + + @Test + fun `mTLS works when trusting a root CA, and an intermediate CA issues both the client and server certificates`() { + val keyManager = PemUtils.loadIdentityMaterial(CLIENT_FULLCHAIN_CER, CLIENT_KEY_NAME) + val sslFactory = SSLFactory.builder() + .withIdentityMaterial(keyManager) + .withTrustMaterial(PemUtils.loadTrustMaterial(ROOT_CERT_NAME)) + .withUnsafeHostnameVerifier() // we don't care about the hostname, we just want to test the certificate + .build() + val builder = OkHttpClient.Builder() + builder.sslSocketFactory(sslFactory.sslSocketFactory, sslFactory.trustManager.orElseThrow()) + builder.hostnameVerifier(sslFactory.hostnameVerifier) + assertClientWorks(builder.build()) + } + + @Test + fun `Hot reloading works when using mTLS`() { + val keyManager = PemUtils.loadIdentityMaterial(CLIENT_FULLCHAIN_CER, CLIENT_KEY_NAME) + val client = Supplier { + val sslFactory = SSLFactory.builder() + .withIdentityMaterial(keyManager) + .withTrustMaterial(PemUtils.loadTrustMaterial(ROOT_CERT_NAME)) // root cert of the client above + .withUnsafeHostnameVerifier() // we don't care about the hostname, we just want to test the certificate + .build() + OkHttpClient.Builder() + .sslSocketFactory(sslFactory.sslSocketFactory, sslFactory.trustManager.orElseThrow()) + .hostnameVerifier(sslFactory.hostnameVerifier) + .build() + } + val securePort = ports.getAndIncrement() + val url = HTTPS_URL_WITH_PORT.apply(securePort) + val sslPlugin = SSLPlugin { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME) + config.http2 = false + config.configConnectors { conn: ServerConnector -> conn.idleTimeout = 0 } // disable idle timeout for testing + config.withTrustConfig { trustConfig: TrustConfig -> trustConfig.certificateFromClasspath(ROOT_CERT_NAME) } + } + try { + Javalin.create { javalinConfig: JavalinConfig -> + javalinConfig.showJavalinBanner = false + javalinConfig.registerPlugin(sslPlugin) + javalinConfig.router.mount { + it.get("/") { ctx: Context -> ctx.result(SUCCESS) } + } + }.start().let { _ -> + testSuccessfulEndpoint(url, client.get()) // works + sslPlugin.reload { config: SSLConfig -> + config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME) + config.withTrustConfig { trustConfig: TrustConfig -> + trustConfig.certificateFromClasspath(Server.CERTIFICATE_FILE_NAME) // this is some other certificate + } + } + testWrongCertOnEndpoint( + url, + client.get() + ) // fails because the server now has a different trust material + sslPlugin.reload { config: SSLConfig -> + config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME) + config.withTrustConfig { trustConfig: TrustConfig -> + trustConfig.certificateFromClasspath(ROOT_CERT_NAME) // back to the original certificate + } + } + testSuccessfulEndpoint(url, client.get()) // works again + } + } catch (e: Exception) { + Assertions.fail(e) + } + } + + companion object { + const val ROOT_CERT_NAME = "ca/root-ca.cer" + const val CLIENT_FULLCHAIN_CER = "ca/client-fullchain.cer" + const val CLIENT_CER = "ca/client-nochain.cer" + const val CLIENT_KEY_NAME = "ca/client.key" + const val SERVER_CERT_NAME = "ca/server.cer" + const val SERVER_KEY_NAME = "ca/server.key" + + @Throws(IOException::class) + protected fun testSuccessfulEndpoint(url: String, client: OkHttpClient) { + val response = client.newCall(Request.Builder().url(url).build()).execute() + Assertions.assertEquals(200, response.code) + Assertions.assertEquals(SUCCESS, Objects.requireNonNull(response.body)?.string()) + response.close() + client.connectionPool.evictAll() + } + + protected fun testWrongCertOnEndpoint(url: String, client: OkHttpClient) { + Assertions.assertThrows(Exception::class.java) { + client.newCall(Request.Builder().url(url).build()).execute().close() + client.connectionPool.evictAll() + } + } + + protected fun assertClientWorks(client: OkHttpClient) { + val securePort = ports.getAndIncrement() + val url = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME) + config.http2 = false + config.withTrustConfig { trustConfig: TrustConfig -> + trustConfig.certificateFromClasspath( + ROOT_CERT_NAME + ) + } + }.start().let { _ -> testSuccessfulEndpoint(url, client) } + } catch (e: Exception) { + Assertions.fail(e) + } + } + + protected fun assertClientFails(client: OkHttpClient) { + val securePort = ports.getAndIncrement() + val url = HTTPS_URL_WITH_PORT.apply(securePort) + try { + createTestApp { config: SSLConfig -> + config.insecure = false + config.securePort = securePort + config.pemFromClasspath(SERVER_CERT_NAME, SERVER_KEY_NAME) + config.http2 = false + config.withTrustConfig { trustConfig: TrustConfig -> + trustConfig.certificateFromClasspath( + ROOT_CERT_NAME + ) + } + }.start().let { _ -> testWrongCertOnEndpoint(url, client) } + } catch (e: Exception) { + Assertions.fail(e) + } + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/certs/Client.kt b/src/test/kotlin/io/javalin/community/ssl/certs/Client.kt new file mode 100644 index 0000000..f5a460a --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/certs/Client.kt @@ -0,0 +1,300 @@ +package io.javalin.community.ssl.certs + +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.InputStream +import java.net.URISyntaxException +import java.nio.file.Path +import java.util.function.Supplier + +object Client { + const val SERVER_CERTIFICATE_AS_STRING = + """-----BEGIN CERTIFICATE----- +MIIDpjCCAo6gAwIBAgIUKK29nJVFCs8SjBqcvxrg7boyem8wDQYJKoZIhvcNAQEL +BQAwQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4GA1UECAwH +R2FsaWNpYTENMAsGA1UEBwwEVmlnbzAgFw0yMjA3MDYxMTQyMDdaGA80MDA1MDMx +MjExNDIwN1owQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4G +A1UECAwHR2FsaWNpYTENMAsGA1UEBwwEVmlnbzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALtW247iPVAuCcQByuqgj8tSzJcwVqCmheT6ld0Xe7DYoLOL +EsjilB/jgG9aBEBfYJ2h74K7SIdqiSDz4rgUuJUzhZnJo5d3n3wT9Wb2AZcsqFce +JK0UNBKe2/1b01dFWtQFW4zHC/JM/Gp0dMTy1Vt1Zf/3SmQjSD/KzgJf4m2O/GOP +3iRFsCSPC4CU3TZCDmI5/qRr4icJCY5s3gJ+RT+edfsvtdkfAO4hK/p+37RrwHax +nyFLoAzYdJMcnDX/+V7Ez2y7jkTkcUk2gKG+3dpio2XqAE9pXcXa4kYk0NL9Vw6L +C2QMefFKHLDqLWx/bfQXpbULFawldETDbuLVe7UCAwEAAaOBkTCBjjAdBgNVHQ4E +FgQUiiPTBoFstcGbb0zYWsM/ZwupRRYwHwYDVR0jBBgwFoAUiiPTBoFstcGbb0zY +WsM/ZwupRRYwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcN +AQELBQADggEBAGvqUrtYWZpKBJNYL4UVLnm2+dQl33l8BH7PhU6YvMufThDCVjOw +IJ7ezOReDlCAmytQD7ChKpsJrAOBzKRdrifL0f88psbE83+6Ys/s/1rHMq282p/S +WPRiZDVO8Mw2ra9v9b6cprW5phHJkp7TiIBP82A+v19lt3R+vE4HZ91ZyioNqMzf +Aqvd5gfxHexpilgil0osF0o/8ajSnLiBfWI82Lz/1JB+xUMYW91ahRgt13/54h13 +eL70steoAmx55he3pQaaeRZKzI1nLxsrTkjs055jDn0G/yj1L6kY3OeVFg3AhETJ +sg+yATMTef2Qskr4dgzb1LJkC9meaU2TFwk= +-----END CERTIFICATE----- +""" + const val SERVER_PRIVATE_KEY_AS_STRING = + """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VtuO4j1QLgnE +AcrqoI/LUsyXMFagpoXk+pXdF3uw2KCzixLI4pQf44BvWgRAX2Cdoe+Cu0iHaokg +8+K4FLiVM4WZyaOXd598E/Vm9gGXLKhXHiStFDQSntv9W9NXRVrUBVuMxwvyTPxq +dHTE8tVbdWX/90pkI0g/ys4CX+Jtjvxjj94kRbAkjwuAlN02Qg5iOf6ka+InCQmO +bN4CfkU/nnX7L7XZHwDuISv6ft+0a8B2sZ8hS6AM2HSTHJw1//lexM9su45E5HFJ +NoChvt3aYqNl6gBPaV3F2uJGJNDS/VcOiwtkDHnxShyw6i1sf230F6W1CxWsJXRE +w27i1Xu1AgMBAAECggEAfPI7UZr3BckO3lnLup0ICrXYmmW1AUTPPJ8c4O7Oom55 +EAaLqsvjuzkC6kGBYGW8jKX6lpjOkPKvLvk6l0fKrEhGrQFdSKKSDjFJlTgya19v +j1sdXwqAiILHer2JwUUShSJlowkGoL5UA7RURR8oye0M8KFATnVxtIpQyCinXiW/ +LkDuqUr8MIbu6V/KcoSOLfJyTWyuwSRPHuFKhv154UAqaTkSPbf2mCTa9hH5Tb4f +Lfzy9o3Ux4ieZceG28De+SmC7uMzbBs1stowOuDmFg3znI/1Br/sQEAXPFngDe3s +soDD2PbLo7/4SPBNgl5vygf7jhvxHPY3DTUXOxLSgQKBgQD4EzKVTx/GpF7Yswma +oixidzSi/KnHJiMjIERF4QPVfDNnggRORNMbPnRhNWSRhS7r+INYbN4yB/vBZO5I +IIqowdJbLjGbmq91equP0zzrP2wCjqtFK6gRElX7acAWY5xTesIT5Fa1Ug++dFLS +MxCZKL6JMZaHJzZVzXugaltMsQKBgQDBUvPSaDnIBrZGdNtAyNMxZyVbp/ObIKW1 +TvCDX2hqf+yiTVclbZr5QkwCE3MHErfsKlWU01K9CtzsQh4u9L5tPaeFlvm6iZq6 +ktbflNvI+z+qEW3JbROR4WwwbmWFvKRLBA0OQom7tGuNnNyRtkDFxlkFJPcD6Eff +ZEq+ewrQRQKBgQCV7URM6J0TuJN58/qB8jFQ8Spmtr0FFw91UzLv6KYgiAepLvLb +Os07UeuUNGiragqJoo//CQzgv+JvZ0h7Xu9uPnWblbd1i28vWQwGyGuw4Yutn/vy +ugfBCYvdfnQRE/KOoUpaK04cF5RcToEfeK03Y2CEGewXkqNMB/wHXz/+gQKBgE8Y +34WQ+0Mp69375dEl2bL23sQXfYZU3zfFaoZ1vMUGPg1R03wO0j91rp+S0ZdtQy8v +SwCvTcTm8uj/TFYt8NPFTAtOcDKwJkx708p6n0ol8jBlHSQyqrUfJCLUqFkFi7rd +l3HkK3JPKUoxidVcWjgRJU8DhsVkfjOaVzKEKTJ5AoGARBwn7gt2H35urQ6/U3nJ +hFjOVn01F5uV0NvRtRDCsAIUMeA2T4pwALUUIqlA9HmpwYgLeG4bZ+SkhNpy70N/ +qcufT1DeM+q3H5zFPANyjcqVaqa6KUnttvi/lhxMdRb6GsA9TzzHzY1P9ovpIOCK +IS639NPzxpI0Ka+v6t+nFEM= +-----END PRIVATE KEY----- +""" + const val CLIENT_CERTIFICATE_AS_STRING = + """-----BEGIN CERTIFICATE----- +MIIDmDCCAoCgAwIBAgIUdWY83fnUuYRDmDnHi34wkeG+yA4wDQYJKoZIhvcNAQEL +BQAwUjEPMA0GA1UEAwwGY2xpZW50MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFk +cmlkMQ8wDQYDVQQHDAZNYWRyaWQxEDAOBgNVBAoMB0phdmFsaW4wIBcNMjMwMTEw +MTEwNTE0WhgPMjEyMjEyMTcxMTA1MTRaMFIxDzANBgNVBAMMBmNsaWVudDELMAkG +A1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMRAwDgYD +VQQKDAdKYXZhbGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsCUt +UOTWxQ3zbVXK8FPFh4DsDCphoUt/k6v8n4miZWxfw45VqsSk5JktmFhqSsmerh0N +cQZ7rji69LK/dr4wfZJjWLGlyNDJnT1W/PP3HaGErJxDl/NqLjl+xULXsp7+/SP7 +Jz6QcEKmDYOyQND79MaYXlhkCLtt/RslfIP1YQ4AFCGcw4z/cGERuMtcLY8FFT+N +U4OD26AZX4fAQ+fQRAdALzp63wCnWiYyQ+0Nqeq4wDM+HYlAsUbwSwiJSseIVn2u +nn1kQq45TUcL8HUuVGr9CF8PyvkOLxbdzC0q43MfPDck7CgqR2YG9XHrca9cT6c+ +zE+BGhjzOjlAxUCYqwIDAQABo2QwYjAdBgNVHQ4EFgQU3MAhBUHI6S9obysrX38v +HiGLmIcwHwYDVR0jBBgwFoAU3MAhBUHI6S9obysrX38vHiGLmIcwCwYDVR0PBAQD +AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBVjsXw +P4ZbUt5XqN8Iy30YqBi90OtfcmWxVnuc7O/HU2ue+PLM3rRyKYVgwY6G/HoRAibq +HsLnGa/2jT+qzri68CKfjE/wmBIAgzaKF4XmhOxIxQjTUuxbVd4BmrDmGqsMgWoQ +5XnYvyBQJ9LTnvvxPk4UvhUjm1/6RbPFqqxVenTUpYS4U3JTv1c9ddu9VSA8ebT4 +BGBVq2iwgTm38xN9C6eS/xGCdLXGIaEQvmfgAPi1Nmw32KrLJfL3oz9bWdYhp9Hg +fZg2Pug5bLDqy8ktyTDdM+q4d+wd3XpKzuLvCIr2q03vrT9j+dMIEOTaqxWQAYiH +CYGXrU6Ry61UJSer +-----END CERTIFICATE----- +""" + const val CLIENT_PRIVATE_KEY_AS_STRING = + """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwJS1Q5NbFDfNt +VcrwU8WHgOwMKmGhS3+Tq/yfiaJlbF/DjlWqxKTkmS2YWGpKyZ6uHQ1xBnuuOLr0 +sr92vjB9kmNYsaXI0MmdPVb88/cdoYSsnEOX82ouOX7FQteynv79I/snPpBwQqYN +g7JA0Pv0xpheWGQIu239GyV8g/VhDgAUIZzDjP9wYRG4y1wtjwUVP41Tg4PboBlf +h8BD59BEB0AvOnrfAKdaJjJD7Q2p6rjAMz4diUCxRvBLCIlKx4hWfa6efWRCrjlN +RwvwdS5Uav0IXw/K+Q4vFt3MLSrjcx88NyTsKCpHZgb1cetxr1xPpz7MT4EaGPM6 +OUDFQJirAgMBAAECggEADdQNV7Fvbu7mcmnu0akx865aWaYmHfyIWnaBEaFDf4Tf +i8Gr1gk0DMI9wx0F0zM64t5jBMGGiinn+3fg8hiCRAlvBTKFGlvRyCddoeQhPVFF +0is+Xzp71n8rBZ92wY4b5JGjkPQncLi6worZPp9peFDy+00jJVBZlSpBaiIN7H2E +iZQYUMI07u6xJW/EUE6X9g3AhgV9QMxfJawn8AWHXR8+9iNsOb9hlVUWBPwR7xb5 +4KqB/89UFp/40tEDeKz9/MMsH5FjNCNPCaLADJS2Xy1Q1icV6V42HsaZm9vZUL+J +dru6OwEo6iJhWKjkBaWvVl4HuOPrrUP9sLSN6g6PCQKBgQDXih7xgHF35yDPvnNx +fqqxfRO+PMHq1se2tOhAdeDmdStUyl/u1NwJ9BE9Fb/lbdulYFfZJtef2TmeX31x +DaQWXrg4Pai2pnCcSfItogWJSFrg6dphbABwVslTvWw2ikB6hN2jmUaReM0atW2S +YUVWD0JFMsf4IimgAcGPgebprQKBgQDRNfB2k6NhqgKBXKrshT/3No/kMnhkNl9H +i/UmiCUYvw5E/L4q2wrsehnERAPpod3EoHjkYCmY/BK4oRCtkz6t1nnWX1zGabY3 +Nn4Ie+BMCYp2NLa7yGZ0sk1rrtlgZBoaR1ZF0+HADPpffELPD6HzvUQuPyN+wlA0 +SWwq8DuGtwKBgBxyxIbHlzJmNTR2RLJ0L39hrNttFYMzegSpeAYaCOciC+gTFfpl +6ez+Y9AWMM/NYjI/txiYQdl9SFeY7uufC0tQkSwLJ1uEOFTIhch0HBr0i9onw4Uc +RiqNqeD9nWzNbpk9NCvFrUTCFwAxdhbd89LaDLspaq9bgvb1hGC2mo25AoGBAKVP +ks+Pf3Unik0/tQupis7DvVVakAjXcdgt/itRPsbcCOF4OKfSZ0JOhNexysmsjnjV +OFF0rsnkvMJI+s284LUqGSHMPpnFZCciltoLUEOk8lTO+GlPQ64ISebBxaBF2N5U +6hXJA8PmPVx/6qaEurrHHf3RBDIgRpHaRm9zXgXnAoGASlEFHkUKwf8G3AePstzk +sHoxJiKMTq2qFb/NTVE4z4+pc03uhxno79+R4aV4JD0dK1gyRaX5/TCwdvI5smS3 +Vfl5JN+HiO0zClecR8N83arOLka2prJ3ZjjCy2JgZKRXZQ/vcsTKnvh3DIFyR/NZ +OKM5x3IGChzxEZLumfedQX4= +-----END PRIVATE KEY----- +""" + const val WRONG_CLIENT_CERTIFICATE_AS_STRING = + """-----BEGIN CERTIFICATE----- +MIIDmDCCAoCgAwIBAgIUD0yMp2hSck6P1TtqpbVvf1QlJMswDQYJKoZIhvcNAQEL +BQAwUjEPMA0GA1UEAwwGY2xpZW50MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFk +cmlkMQ8wDQYDVQQHDAZNYWRyaWQxEDAOBgNVBAoMB0phdmFsaW4wIBcNMjMwMTEw +MTE0MzM0WhgPMjEyMjEyMTcxMTQzMzRaMFIxDzANBgNVBAMMBmNsaWVudDELMAkG +A1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMRAwDgYD +VQQKDAdKYXZhbGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvt3f +vHU1vVoZFu5pV53IXjSP7FoRi6mB27RASyOTXZGCdKTmRZxlgUF7yrMjse99zkys +GR4csPE62vxDB5SHiaLdCVDWFUNmvENJ+Om6v4SnUrVju/1OUDthsTBXe6t7N0Ou +ihPxN5tZKumdDaB56djIXkEfmPFFc/7vRC9cqYISWvKtFT2bkBwzNkcUzTlR05WL +8m5napdl8SQ3/Gza+iVjDtkBDvKs4nlG+QmhT0U4+5B1vah1doKfv+Sn2CAfoTs0 +aIMuHAcdApLR4IVEIADPhNb9pePurXChFHGq7kY90g+wh69rNVsi4uq8HwPSTaQe +YhsTebk71irMquoSMwIDAQABo2QwYjAdBgNVHQ4EFgQUeZ640SK+L1/GPQIis8mz +bHOOQvYwHwYDVR0jBBgwFoAUeZ640SK+L1/GPQIis8mzbHOOQvYwCwYDVR0PBAQD +AgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBNinqE +9Xltwk+khvbRmkF/AIbXMIIFpgGjUWmlg42aUmba+OdQKjHbChiSZHpsue6o/Abj +AgPpb4xH9AacQVM2yFTh/o9UeRwAJtjHrSzIgkBTy2YOM6TFXi2M6a6fBWuEuYQC +jB0std0HNK0ln2MqFKJn3IMk6oiX3XslTXbcTOP8S/T2fj4bc3C4kBZWjUj3qreD +QqzvaWOpVUt7a/slICZ5fVII0vn7EnaNvjsZq9ilBs9MuBH92LNJ0nIO9rhw94TQ +xYyJ1RUBugQrcnpx6xMW3cIUuv/IXu14X+5wEOw21udKaafen5WYVqEkVBW12bgP +0I8c8C6x8S6P4eDO +-----END CERTIFICATE----- +""" + const val WRONG_CLIENT_PRIVATE_KEY_AS_STRING = + """-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+3d+8dTW9WhkW +7mlXncheNI/sWhGLqYHbtEBLI5NdkYJ0pOZFnGWBQXvKsyOx733OTKwZHhyw8Tra +/EMHlIeJot0JUNYVQ2a8Q0n46bq/hKdStWO7/U5QO2GxMFd7q3s3Q66KE/E3m1kq +6Z0NoHnp2MheQR+Y8UVz/u9EL1ypghJa8q0VPZuQHDM2RxTNOVHTlYvybmdql2Xx +JDf8bNr6JWMO2QEO8qzieUb5CaFPRTj7kHW9qHV2gp+/5KfYIB+hOzRogy4cBx0C +ktHghUQgAM+E1v2l4+6tcKEUcaruRj3SD7CHr2s1WyLi6rwfA9JNpB5iGxN5uTvW +Ksyq6hIzAgMBAAECggEBAKXa/ZGxNHqPMWAo2idFt5iNCkey2K5JJMu67WedyW+0 +gu1DYco5pkbUlXLFig4T83lyTNYiwYHMjX0/WivbGJA0kuiGcxHVGRAdVMlUqW/F +IPURJFJ2Qjgb8b9cJ5kSoSabzK61t5W/i5Nrn4r42ReoxiyJYKCxf83VSSsyEM5F +9C+qcjux8+tkF7NlBWXwl2/qqcbuqDuhsTFNmq5Fngx6Xwv7hk1gU0Ibglyo7KmO +75EGcN3T2QWMPMc9C027h3260ROlNOWMNexJZ4vtrWR8GNFi0wOnjaHUW2eilrrg +XGhuzzYFO2ikAPXsJo/+fqfhrqms8ujRlExYqECMkDkCgYEA8xbkZ5UvnXT9uthL +/nJe2Ax1rYOOK7VLsYHciNxkoDmJwRufIK7MUw2qGVKpBB5jAjXBDiYf5cVlyLDJ +tB/5Qh7PkTWTTOMlcY9QsV+nYklf4IYvaURoSKqreotx0PsGQq0R6kpVy2MWn3xs +R4aWmMoCTzVMLVgE2Ibtuv14tIUCgYEAyQDyo7865bssHzFRN52Mq5Ls2WMC0H/q +Owk2NzJoSqNecyzpOvt3hM71IAdOo/xQQdJ7dNMh/B/etbKW3sJNhyVCb1XPVhKk ++ixd4slensXlJvHoXiugmkbIhoEYx8c+2fhxQSP7PXdCanFtCL/M4Ey9MkaUymyK +E/7kAafPpVcCgYEAiWg2QYrluFZqGhSrmC+kBvG8DxGe6nv3RmZGd6JEywDbKinn +3/yOiJ/ft6Ku4SIgCx7BerL4MtRK/Y9Y5JVyOvrZj5Y+Jib7gl5lWW3dWsRpCqwu +3o0JeZHnjkSGWH+cgVH9H3dXWbkwD4SwXBnqxIDjn0xcPAFV8+MJPDqM4VUCgYBz +cV/qPAKPvxhwMdr7njkUsaXmlL8hENZuYbQJr6HGfF3auIibn6HdXR/b7VZ1SIyv +wTu2tSxnqcY3hQKxndb5L6UgXKBgRwUJykGB5zW46t/ZpkZXD6eF8/Fnju20j/LB +LbeeOhQqETzL9akxxTbd/DUNkwwR1pTXNyWs7byMsQKBgGW6QkazJ5iFHZrwKlgS +lJG//bnbUU1ZPITh25RihWa78l5tnUAK4iJwBmOPwjtSC+4qG35xCt1TSBHPR/yy +hMlv7rBUPwS1UHzvVifQwE5D2NHyselZYdhxyqqJ2hlq4ykjZZJ+vc01FRl2Wfd/ +SQO2OwXQkrajbcSXGpQg/aQs +-----END PRIVATE KEY----- +""" + const val CLIENT_P7B_CERTIFICATE_AS_STRING = + """-----BEGIN PKCS7----- +MIIDyQYJKoZIhvcNAQcCoIIDujCCA7YCAQExADALBgkqhkiG9w0BBwGgggOcMIID +mDCCAoCgAwIBAgIUdWY83fnUuYRDmDnHi34wkeG+yA4wDQYJKoZIhvcNAQELBQAw +UjEPMA0GA1UEAwwGY2xpZW50MQswCQYDVQQGEwJFUzEPMA0GA1UECAwGTWFkcmlk +MQ8wDQYDVQQHDAZNYWRyaWQxEDAOBgNVBAoMB0phdmFsaW4wIBcNMjMwMTEwMTEw +NTE0WhgPMjEyMjEyMTcxMTA1MTRaMFIxDzANBgNVBAMMBmNsaWVudDELMAkGA1UE +BhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwGTWFkcmlkMRAwDgYDVQQK +DAdKYXZhbGluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsCUtUOTW +xQ3zbVXK8FPFh4DsDCphoUt/k6v8n4miZWxfw45VqsSk5JktmFhqSsmerh0NcQZ7 +rji69LK/dr4wfZJjWLGlyNDJnT1W/PP3HaGErJxDl/NqLjl+xULXsp7+/SP7Jz6Q +cEKmDYOyQND79MaYXlhkCLtt/RslfIP1YQ4AFCGcw4z/cGERuMtcLY8FFT+NU4OD +26AZX4fAQ+fQRAdALzp63wCnWiYyQ+0Nqeq4wDM+HYlAsUbwSwiJSseIVn2unn1k +Qq45TUcL8HUuVGr9CF8PyvkOLxbdzC0q43MfPDck7CgqR2YG9XHrca9cT6c+zE+B +GhjzOjlAxUCYqwIDAQABo2QwYjAdBgNVHQ4EFgQU3MAhBUHI6S9obysrX38vHiGL +mIcwHwYDVR0jBBgwFoAU3MAhBUHI6S9obysrX38vHiGLmIcwCwYDVR0PBAQDAgeA +MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBVjsXwP4Zb +Ut5XqN8Iy30YqBi90OtfcmWxVnuc7O/HU2ue+PLM3rRyKYVgwY6G/HoRAibqHsLn +Ga/2jT+qzri68CKfjE/wmBIAgzaKF4XmhOxIxQjTUuxbVd4BmrDmGqsMgWoQ5XnY +vyBQJ9LTnvvxPk4UvhUjm1/6RbPFqqxVenTUpYS4U3JTv1c9ddu9VSA8ebT4BGBV +q2iwgTm38xN9C6eS/xGCdLXGIaEQvmfgAPi1Nmw32KrLJfL3oz9bWdYhp9HgfZg2 +Pug5bLDqy8ktyTDdM+q4d+wd3XpKzuLvCIr2q03vrT9j+dMIEOTaqxWQAYiHCYGX +rU6Ry61UJSeroQAxAA== +-----END PKCS7----- +""" + const val KEYSTORE_PASSWORD = "password" + const val CLIENT_PEM_FILE_NAME = "client/cert.pem" + const val CLIENT_P7B_FILE_NAME = "client/cert.p7b" + const val CLIENT_DER_FILE_NAME = "client/cert.der" + const val CLIENT_P12_FILE_NAME = "client/cert.p12" + const val CLIENT_JKS_FILE_NAME = "client/cert.jks" + @JvmField + val CLIENT_PEM_PATH: String + @JvmField + val CLIENT_P7B_PATH: String + @JvmField + val CLIENT_DER_PATH: String + @JvmField + val CLIENT_P12_PATH: String + @JvmField + val CLIENT_JKS_PATH: String + + init { + try { + CLIENT_PEM_PATH = + Path.of(ClassLoader.getSystemResource(CLIENT_PEM_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + CLIENT_P7B_PATH = + Path.of(ClassLoader.getSystemResource(CLIENT_P7B_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + CLIENT_DER_PATH = + Path.of(ClassLoader.getSystemResource(CLIENT_DER_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + CLIENT_P12_PATH = + Path.of(ClassLoader.getSystemResource(CLIENT_P12_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + CLIENT_JKS_PATH = + Path.of(ClassLoader.getSystemResource(CLIENT_JKS_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + @JvmField + val CLIENT_PEM_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier FileInputStream(CLIENT_PEM_PATH) + } catch (e: FileNotFoundException) { + throw RuntimeException(e) + } + } + @JvmField + val CLIENT_P7B_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier FileInputStream(CLIENT_P7B_PATH) + } catch (e: FileNotFoundException) { + throw RuntimeException(e) + } + } + @JvmField + val CLIENT_DER_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier FileInputStream(CLIENT_DER_PATH) + } catch (e: FileNotFoundException) { + throw RuntimeException(e) + } + } + @JvmField + val CLIENT_P12_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier FileInputStream(CLIENT_P12_PATH) + } catch (e: FileNotFoundException) { + throw RuntimeException(e) + } + } + @JvmField + val CLIENT_JKS_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier FileInputStream(CLIENT_JKS_PATH) + } catch (e: FileNotFoundException) { + throw RuntimeException(e) + } + } +} diff --git a/src/test/kotlin/io/javalin/community/ssl/certs/Server.kt b/src/test/kotlin/io/javalin/community/ssl/certs/Server.kt new file mode 100644 index 0000000..ec2e71c --- /dev/null +++ b/src/test/kotlin/io/javalin/community/ssl/certs/Server.kt @@ -0,0 +1,289 @@ +package io.javalin.community.ssl.certs + +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.net.URISyntaxException +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import java.util.function.Supplier + +object Server { + const val CERTIFICATE_AS_STRING = + """-----BEGIN CERTIFICATE----- +MIIDpjCCAo6gAwIBAgIUKK29nJVFCs8SjBqcvxrg7boyem8wDQYJKoZIhvcNAQEL +BQAwQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4GA1UECAwH +R2FsaWNpYTENMAsGA1UEBwwEVmlnbzAgFw0yMjA3MDYxMTQyMDdaGA80MDA1MDMx +MjExNDIwN1owQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4G +A1UECAwHR2FsaWNpYTENMAsGA1UEBwwEVmlnbzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALtW247iPVAuCcQByuqgj8tSzJcwVqCmheT6ld0Xe7DYoLOL +EsjilB/jgG9aBEBfYJ2h74K7SIdqiSDz4rgUuJUzhZnJo5d3n3wT9Wb2AZcsqFce +JK0UNBKe2/1b01dFWtQFW4zHC/JM/Gp0dMTy1Vt1Zf/3SmQjSD/KzgJf4m2O/GOP +3iRFsCSPC4CU3TZCDmI5/qRr4icJCY5s3gJ+RT+edfsvtdkfAO4hK/p+37RrwHax +nyFLoAzYdJMcnDX/+V7Ez2y7jkTkcUk2gKG+3dpio2XqAE9pXcXa4kYk0NL9Vw6L +C2QMefFKHLDqLWx/bfQXpbULFawldETDbuLVe7UCAwEAAaOBkTCBjjAdBgNVHQ4E +FgQUiiPTBoFstcGbb0zYWsM/ZwupRRYwHwYDVR0jBBgwFoAUiiPTBoFstcGbb0zY +WsM/ZwupRRYwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcN +AQELBQADggEBAGvqUrtYWZpKBJNYL4UVLnm2+dQl33l8BH7PhU6YvMufThDCVjOw +IJ7ezOReDlCAmytQD7ChKpsJrAOBzKRdrifL0f88psbE83+6Ys/s/1rHMq282p/S +WPRiZDVO8Mw2ra9v9b6cprW5phHJkp7TiIBP82A+v19lt3R+vE4HZ91ZyioNqMzf +Aqvd5gfxHexpilgil0osF0o/8ajSnLiBfWI82Lz/1JB+xUMYW91ahRgt13/54h13 +eL70steoAmx55he3pQaaeRZKzI1nLxsrTkjs055jDn0G/yj1L6kY3OeVFg3AhETJ +sg+yATMTef2Qskr4dgzb1LJkC9meaU2TFwk= +-----END CERTIFICATE-----""" + val CERTIFICATE_INPUT_STREAM_SUPPLIER = Supplier { + ByteArrayInputStream( + CERTIFICATE_AS_STRING.toByteArray( + StandardCharsets.UTF_8 + ) + ) + } + const val NORWAY_CERTIFICATE_AS_STRING = + """-----BEGIN CERTIFICATE----- +MIIDrjCCApagAwIBAgIUZ8z5/me7+2mTkDnIYyx1dKjwYMQwDQYJKoZIhvcNAQEL +BQAwRjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJOTzESMBAGA1UECAwJ +SG9yZGFsYW5kMQ8wDQYDVQQHDAZCZXJnZW4wIBcNMjIxMTI2MTIyNjI1WhgPMzAy +MjAzMjkxMjI2MjVaMEYxEjAQBgNVBAMMCWxvY2FsaG9zdDELMAkGA1UEBhMCTk8x +EjAQBgNVBAgMCUhvcmRhbGFuZDEPMA0GA1UEBwwGQmVyZ2VuMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1bbjuI9UC4JxAHK6qCPy1LMlzBWoKaF5PqV +3Rd7sNigs4sSyOKUH+OAb1oEQF9gnaHvgrtIh2qJIPPiuBS4lTOFmcmjl3effBP1 +ZvYBlyyoVx4krRQ0Ep7b/VvTV0Va1AVbjMcL8kz8anR0xPLVW3Vl//dKZCNIP8rO +Al/ibY78Y4/eJEWwJI8LgJTdNkIOYjn+pGviJwkJjmzeAn5FP551+y+12R8A7iEr ++n7ftGvAdrGfIUugDNh0kxycNf/5XsTPbLuORORxSTaAob7d2mKjZeoAT2ldxdri +RiTQ0v1XDosLZAx58UocsOotbH9t9BeltQsVrCV0RMNu4tV7tQIDAQABo4GRMIGO +MB0GA1UdDgQWBBSKI9MGgWy1wZtvTNhawz9nC6lFFjAfBgNVHSMEGDAWgBSKI9MG +gWy1wZtvTNhawz9nC6lFFjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDwYDVR0RBAgwBocEfwAAATAN +BgkqhkiG9w0BAQsFAAOCAQEAd+wmmJf/LYXPWhIAf/qBa7HOH1gbzOQ1CMH6qyxm +ueH4MdUr2tewC53M0eYVfEMwOc4NVBmXXKLANkDByjBKGObr4N5cHjyUI1zk0Eew +tkkv8IoHl9pJTewNCdDkgCPADRoQ//gOkqpzxh3uEdhjHAB/KlyylUkxnbAJH9ap +v7V2Ju/yOjXtA0Pl+fdaeYg1Y8TVKLJSUqSR2CFHQgodxMsSG/l2QbpHXVIt50sm +fQxiURVXr/qqP7KubKb2MzRQWVIQemlK3FJgVoY4Mp3zCmSBBq/N2Dioudu/XQQu +tkLrsE6joYC2ST27wJDgYgAY7CmFelbIRZZl94FzR4Jp5A== +-----END CERTIFICATE-----""" + const val NON_ENCRYPTED_KEY_AS_STRING = + """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VtuO4j1QLgnE +AcrqoI/LUsyXMFagpoXk+pXdF3uw2KCzixLI4pQf44BvWgRAX2Cdoe+Cu0iHaokg +8+K4FLiVM4WZyaOXd598E/Vm9gGXLKhXHiStFDQSntv9W9NXRVrUBVuMxwvyTPxq +dHTE8tVbdWX/90pkI0g/ys4CX+Jtjvxjj94kRbAkjwuAlN02Qg5iOf6ka+InCQmO +bN4CfkU/nnX7L7XZHwDuISv6ft+0a8B2sZ8hS6AM2HSTHJw1//lexM9su45E5HFJ +NoChvt3aYqNl6gBPaV3F2uJGJNDS/VcOiwtkDHnxShyw6i1sf230F6W1CxWsJXRE +w27i1Xu1AgMBAAECggEAfPI7UZr3BckO3lnLup0ICrXYmmW1AUTPPJ8c4O7Oom55 +EAaLqsvjuzkC6kGBYGW8jKX6lpjOkPKvLvk6l0fKrEhGrQFdSKKSDjFJlTgya19v +j1sdXwqAiILHer2JwUUShSJlowkGoL5UA7RURR8oye0M8KFATnVxtIpQyCinXiW/ +LkDuqUr8MIbu6V/KcoSOLfJyTWyuwSRPHuFKhv154UAqaTkSPbf2mCTa9hH5Tb4f +Lfzy9o3Ux4ieZceG28De+SmC7uMzbBs1stowOuDmFg3znI/1Br/sQEAXPFngDe3s +soDD2PbLo7/4SPBNgl5vygf7jhvxHPY3DTUXOxLSgQKBgQD4EzKVTx/GpF7Yswma +oixidzSi/KnHJiMjIERF4QPVfDNnggRORNMbPnRhNWSRhS7r+INYbN4yB/vBZO5I +IIqowdJbLjGbmq91equP0zzrP2wCjqtFK6gRElX7acAWY5xTesIT5Fa1Ug++dFLS +MxCZKL6JMZaHJzZVzXugaltMsQKBgQDBUvPSaDnIBrZGdNtAyNMxZyVbp/ObIKW1 +TvCDX2hqf+yiTVclbZr5QkwCE3MHErfsKlWU01K9CtzsQh4u9L5tPaeFlvm6iZq6 +ktbflNvI+z+qEW3JbROR4WwwbmWFvKRLBA0OQom7tGuNnNyRtkDFxlkFJPcD6Eff +ZEq+ewrQRQKBgQCV7URM6J0TuJN58/qB8jFQ8Spmtr0FFw91UzLv6KYgiAepLvLb +Os07UeuUNGiragqJoo//CQzgv+JvZ0h7Xu9uPnWblbd1i28vWQwGyGuw4Yutn/vy +ugfBCYvdfnQRE/KOoUpaK04cF5RcToEfeK03Y2CEGewXkqNMB/wHXz/+gQKBgE8Y +34WQ+0Mp69375dEl2bL23sQXfYZU3zfFaoZ1vMUGPg1R03wO0j91rp+S0ZdtQy8v +SwCvTcTm8uj/TFYt8NPFTAtOcDKwJkx708p6n0ol8jBlHSQyqrUfJCLUqFkFi7rd +l3HkK3JPKUoxidVcWjgRJU8DhsVkfjOaVzKEKTJ5AoGARBwn7gt2H35urQ6/U3nJ +hFjOVn01F5uV0NvRtRDCsAIUMeA2T4pwALUUIqlA9HmpwYgLeG4bZ+SkhNpy70N/ +qcufT1DeM+q3H5zFPANyjcqVaqa6KUnttvi/lhxMdRb6GsA9TzzHzY1P9ovpIOCK +IS639NPzxpI0Ka+v6t+nFEM= +-----END PRIVATE KEY----- +""" + val NON_ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER = Supplier { + ByteArrayInputStream( + NON_ENCRYPTED_KEY_AS_STRING.toByteArray(StandardCharsets.UTF_8) + ) + } + const val ENCRYPTED_KEY_AS_STRING = + """-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIMDP+/JKdUc4CAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBRfpz0ZTscSvALfUISuNYIBIIE +0I8rVF2h/qQANJ3WvXFcmm7dFqEiQwUm8cDxaDpPd8RRweclTesEj70yg+3xcGLh +rhSFrNSB2wmy/jB6lFcN02KcSU3p6H7aSuLRffbYYAQ3LGU6Ie79NW68x189zB/b +sDi6gWkxHCrGzBydKR4a6ZvF9TMnP743hCw3t3NrO/4xdoZ9+YaxmBaBjt4E1Bns +J2yCHHV5kXXsWOZJvTTWxf+fIEQNe1cjidBxcpvQxreZpOsday2KM8tctom+p9lw +DEF0mhUi/FHZZnmfgr1Cz4+PmspjOTykX+0RWD1wi0kMJwqo6aRHwlEbEE+f83Df +kazqIXOfD0VrzSXTwr1kIzjQI+DK8sKyfg5lfTby1AFy5cvtJxL7cK6As9Cq05XI +i2fX5PWUj1sHplMOI2+qh31R7w6qb0DygXC22ZFNLlFYwP0QKPp9XzZZLIvPI662 +9xlF4VgtcS9JV7hztrg6Bbc23l1cSsBXPqreWd39NM/Kggf6J3GV/P0AacYYp0OY +A3Pt9i+RV+HHv8OwfZ+v4RH8hVhtDkPWyBJX581zwF5OQLqjksKa1FNC8qB/VlE4 +Ponm33vn1gWtKY962sYoJxDVHbgWwpWP7bSqtO66jiwlTEyQwltd3LbZGcJ4yJNd +eEJUE8vFHyXmEx1KoDHUh2/v9l6pdo59PgBlY8mxl95AuTNds+dtuqZQE6ZNZvDZ +lw7dOp+BATb/3YWCF5O6jQjm11ji9kZxgnPBTSiaegFFIa4OxdQwP2pxbyVk4rGH +/gs6olWtc3hqqQspMJDqT2cJeaE61us7hUya1w1LjivOvofR3Zt2v2Wtxmm+ey3e +/mPBZH1LIRdP9vEuHxKkjjppXVRWL5TdHeN9Ai6jG3/WCp/NgBnjOi9/5GVX7j+T +dUzGBaxwUGt10QZ8Yvo9qT8Cg2tiUD770EzaD09aiRfoAs7YwLsVi35gul+JyNrD +CzqZ8I2NZ6Uo/r9I9Xda9qkoxbS2hNg+53whm5L2fT4SrJ69MOY/tM3mR8q1Ta18 +W+dXuFSD+3nAU7Aqug4LlKcOS9/RW18kRtHRVatXZscxITO5dlgmw7zEVzrkwa+q +r1y4YG488XZZ3KCXPJthnmP4nIYERW6hn62P8EKzM8wfxxT39O96QNzMgszor/WA +TG8o0JDRvG5WW/OfVA4Ls8QK6lx3E2cPhyqnvM+HirtP2xL4Gd0SibGIK7QvewSf +9a5TnbQsuoWvTqtzX7PEb9snLxQjaxTLZEbTyimwEyaaZ1Ev72did2EdmA3UEtzq +e+X86mvYOZrAJIWNGIfoMI3QPtxlC2MbDjUcLB5crk90T2dCcIdwpr6cKGdnEqP+ +DmkkwTl84MSV2tVQ2qCJPtiwsR8V2xkwqesD1p0G5whR2SxsUQDoG48l1zRkLrA5 +PbwUii9Xapp5+R08t+dIt19cRJyewjAKxpWkKHNjtXmBMJpvJ65A4wAAV5vqcTdY +FIrJEMySqFDrodCwkAs9s8FKIWvEnWKkaX2NvjoTWdQEGmKpiEazUsknd4wNX8js +MjjY/VHqWNYR6cF84H+WuFS86S37Vt3nBEpos0vp9n8epNNC+ETcewKMgovLJMnt +na5mQXa7ctzrJ+bqW9B+QLBX6KZk3tRnigYO6Fum0t7I +-----END ENCRYPTED PRIVATE KEY----- +""" + val ENCRYPTED_KEY_INPUT_STREAM_SUPPLIER = Supplier { + ByteArrayInputStream( + ENCRYPTED_KEY_AS_STRING.toByteArray( + StandardCharsets.UTF_8 + ) + ) + } + const val GOOGLE_CERTIFICATE_AS_STRING = + """-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIULxRctoyJitKoVft6Dn6F458+uK8wDQYJKoZIhvcNAQEL +BQAwJzETMBEGA1UEAwwKZ29vZ2xlLmNvbTEQMA4GA1UECgwHSmF2YWxpbjAgFw0y +MjA4MDgwOTE4NDRaGA8yMTIyMDcxNTA5MTg0NFowJzETMBEGA1UEAwwKZ29vZ2xl +LmNvbTEQMA4GA1UECgwHSmF2YWxpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBANgf9ywzZUgab2y9WIJTf4H6rxMITbRDC3xYJRVEHc8WgegrlPwXw4V/ +Qr92xChowUKZBQX98yvLXMc0YziRmqY+m5IriaWY7Jvr82pkHkLKWyjzFmHZBMAd +lWWjDKDD5abKPhsPu4tJg3YedRm7SjZIm7rpj+rEau4ALGRonM8L4jpqZJ+Jg8Sq +DDKTnVGLiYuhxtmby+/PRV/mhmJJ6dPOOcdIQlIn1PCrUDJbt2zMkuflrVzl+6eY +7GYq5h+QqCgO2XK+6q5RQBxtMUIp8Bi0AR6j+g3yAE6uiAPXhDQOj+fzKRJhoYcf +cQ84yxzN4benHPPPfLw42rquA7qB/BECAwEAAaN/MH0wHQYDVR0OBBYEFCIswSAR +oq1RbOP1ygckApt9wHPKMB8GA1UdIwQYMBaAFCIswSARoq1RbOP1ygckApt9wHPK +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAi4zGlp2DmaH9sMiRgqA5 +dDO8UbrW1TNNES7Dwo1519sERiosCzGVBqjVOzUzFYyrW/jF8kKd8NoIZSlWvoeQ +A0+6dxYy7oNY0UTJbW25hSRXMF1FCnxBfLLZ1J9lowzhts3yx5REJZVWEvsF0Agy +qgNEkKYSaeUtuSzUhMVPGs9AuMwl/M0M1q+2WBMeDLaGXhAXJC5jZ47BkEVgnz5+ +/IIWbFJQ+eGEgL+GVFCxgebvJwncPruDipaS7i486kYyoymBKiSXeUN+z+gdaIHk +YHuSkRVAU4BiSGd72UK+KWIYBttkeINcYLRyZbdYkY5sgBGTJnj2ke0vnM13o7UM +jw== +-----END CERTIFICATE----- +""" + const val GOOGLE_KEY_AS_STRING = + """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYH/csM2VIGm9s +vViCU3+B+q8TCE20Qwt8WCUVRB3PFoHoK5T8F8OFf0K/dsQoaMFCmQUF/fMry1zH +NGM4kZqmPpuSK4mlmOyb6/NqZB5Cylso8xZh2QTAHZVlowygw+Wmyj4bD7uLSYN2 +HnUZu0o2SJu66Y/qxGruACxkaJzPC+I6amSfiYPEqgwyk51Ri4mLocbZm8vvz0Vf +5oZiSenTzjnHSEJSJ9Twq1AyW7dszJLn5a1c5funmOxmKuYfkKgoDtlyvuquUUAc +bTFCKfAYtAEeo/oN8gBOrogD14Q0Do/n8ykSYaGHH3EPOMsczeG3pxzzz3y8ONq6 +rgO6gfwRAgMBAAECggEAOCrWid4xjDOSkagDwJsCoD0OEtwtlZN3ALHHsWcqeA9Z +Y4UwCvQCFEemiSvMftP6pdwuugftkowfaIXs416z2lCbDbnS4/6CP2Nqt1Odqa39 +Uv8Z6gQEgAkwMmHVflJq9JXK3i2Qh/pq99+ifzV1a/YiwsjAZjr1rzTMVKv7VLM/ +cRwEffg/kus8b/RDUVDcTFlX2drypfAI9T2P7kaiMLVKI7tcWnn1RPUdUqlG8LjB +4ZyyZXNGNrd93zYWWe1DflUwP187SyoGbsn0vQe15n0U7cVOE8xLOBNFJiKSUJGi +hdUUeIgXZS9MGTfQN6QldRsFNlv6DGDvSzocqtl7gQKBgQD6P96IbtQeD/UwVKb1 +1KzvDzdzlxT4XG7e2ZPj71tG9d84V72PDPAYnzftPJRkD08lDJLvAvKqqKCPTX3V +QgauF2njX1XLa649E4/hb5VHnJQj9Fwlx3/KUaNWI8zu3PR+5gKp/dlJiJz0v84T +gNw/VYhzfXyjPtbJXYrij8QpRQKBgQDdF1pbBKDAkBwoiOa2jgBtjnzHm7SxLCqq +/BBsKT54q8yxdTsY6MZ4J+497Z+AL65zHKMgcDsMEOvKDqLhurvqvWzUGTCv/5St +xOuwJ0k4kIVrQEBvvAJmBT44WOQZM3M9vxbwoG2mjfsSPz7OdrkK+hnwBlu91AjK +mP9rKa/mXQKBgQDYbfSgOnnpphOAQTZE1jLabmae6cORKSAaTELDl3dx36O2ruua +lK3yHYHZA9Oy1iq0+DL706jcQArc5UA2+GuelVFW/FTPIcoHuKtvZXnN/XWBww0O +/4NeD00casoKq74pIfSb4JfUKPrWEizAYWoavHbOq3DoHqjUbrp3R693oQKBgA7i +T5bpDNlp2jtwW/fWP3kgqo3VkaiLzKOOLJzbefUtu64Gsl/O6+2S4psQsDg0/Y2K +VAEPDSqWyQjlS1ne9F+tOPJeb8SpdBzusN8/BdLlB9ZckPn0skSj/bhVY6W+rPdv +MeApLLiVvl1QHK5Rl8uBYtWh1/NDnwPkoO1Z9RmRAoGALzIURD5Dg9FCpd9ym+UZ +JWI9lPvbL3uBzD53ys0dtzoSaTayishooeggYYJEbYpAxNH0WE1M7dqZH/OjTaAO +Kp7LluqrRTUGMYHogBX485sCWhZ91r4RqPa90UcUcpjXUnVu7Absn7/FOcT1z++M +6HNWxu22y49Nc0iAEtqCOVk= +-----END PRIVATE KEY----- +""" + const val CERTIFICATE_FILE_NAME = "server/cert.crt" + const val ENCRYPTED_KEY_FILE_NAME = "server/encrypted.key" + const val NON_ENCRYPTED_KEY_FILE_NAME = "server/passwordless.key" + val CERTIFICATE_PATH: String + val ENCRYPTED_KEY_PATH: String + val NON_ENCRYPTED_KEY_PATH: String + const val KEY_PASSWORD = "password" + const val JKS_KEY_STORE_NAME = "server/keystore.jks" + val JKS_KEY_STORE_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier ClassLoader.getSystemResource(JKS_KEY_STORE_NAME).openStream() + } catch (e: IOException) { + throw RuntimeException(e) + } + } + const val P12_KEY_STORE_NAME = "server/keystore.p12" + val P12_KEY_STORE_INPUT_STREAM_SUPPLIER = Supplier { + try { + return@Supplier ClassLoader.getSystemResource(P12_KEY_STORE_NAME).openStream() + } catch (e: IOException) { + throw RuntimeException(e) + } + } + const val NORWAY_JKS_KEY_STORE_NAME = "server/norway.jks" + const val NORWAY_P12_KEY_STORE_NAME = "server/norway.p12" + @JvmField + val JKS_KEY_STORE_PATH: String + @JvmField + val P12_KEY_STORE_PATH: String + @JvmField + val NORWAY_JKS_KEY_STORE_PATH: String + @JvmField + val NORWAY_P12_KEY_STORE_PATH: String + const val KEY_STORE_PASSWORD = "password" + + init { + try { + CERTIFICATE_PATH = + Path.of(ClassLoader.getSystemResource(CERTIFICATE_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + ENCRYPTED_KEY_PATH = + Path.of(ClassLoader.getSystemResource(ENCRYPTED_KEY_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + NON_ENCRYPTED_KEY_PATH = + Path.of(ClassLoader.getSystemResource(NON_ENCRYPTED_KEY_FILE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + JKS_KEY_STORE_PATH = + Path.of(ClassLoader.getSystemResource(JKS_KEY_STORE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + P12_KEY_STORE_PATH = + Path.of(ClassLoader.getSystemResource(P12_KEY_STORE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + NORWAY_JKS_KEY_STORE_PATH = + Path.of(ClassLoader.getSystemResource(NORWAY_JKS_KEY_STORE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } + + init { + try { + NORWAY_P12_KEY_STORE_PATH = + Path.of(ClassLoader.getSystemResource(NORWAY_P12_KEY_STORE_NAME).toURI()).toAbsolutePath().toString() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } + } +} diff --git a/src/intTest/resources/ca/client-fullchain.cer b/src/test/resources/ca/client-fullchain.cer similarity index 100% rename from src/intTest/resources/ca/client-fullchain.cer rename to src/test/resources/ca/client-fullchain.cer diff --git a/src/intTest/resources/ca/client-nochain.cer b/src/test/resources/ca/client-nochain.cer similarity index 100% rename from src/intTest/resources/ca/client-nochain.cer rename to src/test/resources/ca/client-nochain.cer diff --git a/src/intTest/resources/ca/client.key b/src/test/resources/ca/client.key similarity index 100% rename from src/intTest/resources/ca/client.key rename to src/test/resources/ca/client.key diff --git a/src/intTest/resources/ca/keystores/client.p12 b/src/test/resources/ca/keystores/client.p12 similarity index 100% rename from src/intTest/resources/ca/keystores/client.p12 rename to src/test/resources/ca/keystores/client.p12 diff --git a/src/intTest/resources/ca/keystores/issuer-ca.p12 b/src/test/resources/ca/keystores/issuer-ca.p12 similarity index 100% rename from src/intTest/resources/ca/keystores/issuer-ca.p12 rename to src/test/resources/ca/keystores/issuer-ca.p12 diff --git a/src/intTest/resources/ca/keystores/root-ca.p12 b/src/test/resources/ca/keystores/root-ca.p12 similarity index 100% rename from src/intTest/resources/ca/keystores/root-ca.p12 rename to src/test/resources/ca/keystores/root-ca.p12 diff --git a/src/intTest/resources/ca/keystores/server.p12 b/src/test/resources/ca/keystores/server.p12 similarity index 100% rename from src/intTest/resources/ca/keystores/server.p12 rename to src/test/resources/ca/keystores/server.p12 diff --git a/src/intTest/resources/ca/root-ca.cer b/src/test/resources/ca/root-ca.cer similarity index 100% rename from src/intTest/resources/ca/root-ca.cer rename to src/test/resources/ca/root-ca.cer diff --git a/src/intTest/resources/ca/server.cer b/src/test/resources/ca/server.cer similarity index 100% rename from src/intTest/resources/ca/server.cer rename to src/test/resources/ca/server.cer diff --git a/src/intTest/resources/ca/server.key b/src/test/resources/ca/server.key similarity index 100% rename from src/intTest/resources/ca/server.key rename to src/test/resources/ca/server.key diff --git a/src/test/resources/cert.crt b/src/test/resources/cert.crt deleted file mode 100644 index fbd2aa9..0000000 --- a/src/test/resources/cert.crt +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDpjCCAo6gAwIBAgIUKK29nJVFCs8SjBqcvxrg7boyem8wDQYJKoZIhvcNAQEL -BQAwQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4GA1UECAwH -R2FsaWNpYTENMAsGA1UEBwwEVmlnbzAgFw0yMjA3MDYxMTQyMDdaGA80MDA1MDMx -MjExNDIwN1owQjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJFUzEQMA4G -A1UECAwHR2FsaWNpYTENMAsGA1UEBwwEVmlnbzCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALtW247iPVAuCcQByuqgj8tSzJcwVqCmheT6ld0Xe7DYoLOL -EsjilB/jgG9aBEBfYJ2h74K7SIdqiSDz4rgUuJUzhZnJo5d3n3wT9Wb2AZcsqFce -JK0UNBKe2/1b01dFWtQFW4zHC/JM/Gp0dMTy1Vt1Zf/3SmQjSD/KzgJf4m2O/GOP -3iRFsCSPC4CU3TZCDmI5/qRr4icJCY5s3gJ+RT+edfsvtdkfAO4hK/p+37RrwHax -nyFLoAzYdJMcnDX/+V7Ez2y7jkTkcUk2gKG+3dpio2XqAE9pXcXa4kYk0NL9Vw6L -C2QMefFKHLDqLWx/bfQXpbULFawldETDbuLVe7UCAwEAAaOBkTCBjjAdBgNVHQ4E -FgQUiiPTBoFstcGbb0zYWsM/ZwupRRYwHwYDVR0jBBgwFoAUiiPTBoFstcGbb0zY -WsM/ZwupRRYwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr -BgEFBQcDAjAMBgNVHRMBAf8EAjAAMA8GA1UdEQQIMAaHBH8AAAEwDQYJKoZIhvcN -AQELBQADggEBAGvqUrtYWZpKBJNYL4UVLnm2+dQl33l8BH7PhU6YvMufThDCVjOw -IJ7ezOReDlCAmytQD7ChKpsJrAOBzKRdrifL0f88psbE83+6Ys/s/1rHMq282p/S -WPRiZDVO8Mw2ra9v9b6cprW5phHJkp7TiIBP82A+v19lt3R+vE4HZ91ZyioNqMzf -Aqvd5gfxHexpilgil0osF0o/8ajSnLiBfWI82Lz/1JB+xUMYW91ahRgt13/54h13 -eL70steoAmx55he3pQaaeRZKzI1nLxsrTkjs055jDn0G/yj1L6kY3OeVFg3AhETJ -sg+yATMTef2Qskr4dgzb1LJkC9meaU2TFwk= ------END CERTIFICATE----- diff --git a/src/intTest/resources/client/cert.der b/src/test/resources/client/cert.der similarity index 100% rename from src/intTest/resources/client/cert.der rename to src/test/resources/client/cert.der diff --git a/src/intTest/resources/client/cert.jks b/src/test/resources/client/cert.jks similarity index 100% rename from src/intTest/resources/client/cert.jks rename to src/test/resources/client/cert.jks diff --git a/src/intTest/resources/client/cert.p12 b/src/test/resources/client/cert.p12 similarity index 100% rename from src/intTest/resources/client/cert.p12 rename to src/test/resources/client/cert.p12 diff --git a/src/intTest/resources/client/cert.p7b b/src/test/resources/client/cert.p7b similarity index 100% rename from src/intTest/resources/client/cert.p7b rename to src/test/resources/client/cert.p7b diff --git a/src/intTest/resources/client/cert.pem b/src/test/resources/client/cert.pem similarity index 100% rename from src/intTest/resources/client/cert.pem rename to src/test/resources/client/cert.pem diff --git a/src/intTest/resources/client/key.pem b/src/test/resources/client/key.pem similarity index 100% rename from src/intTest/resources/client/key.pem rename to src/test/resources/client/key.pem diff --git a/src/test/resources/passwordless.key b/src/test/resources/passwordless.key deleted file mode 100644 index 45b8f48..0000000 --- a/src/test/resources/passwordless.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VtuO4j1QLgnE -AcrqoI/LUsyXMFagpoXk+pXdF3uw2KCzixLI4pQf44BvWgRAX2Cdoe+Cu0iHaokg -8+K4FLiVM4WZyaOXd598E/Vm9gGXLKhXHiStFDQSntv9W9NXRVrUBVuMxwvyTPxq -dHTE8tVbdWX/90pkI0g/ys4CX+Jtjvxjj94kRbAkjwuAlN02Qg5iOf6ka+InCQmO -bN4CfkU/nnX7L7XZHwDuISv6ft+0a8B2sZ8hS6AM2HSTHJw1//lexM9su45E5HFJ -NoChvt3aYqNl6gBPaV3F2uJGJNDS/VcOiwtkDHnxShyw6i1sf230F6W1CxWsJXRE -w27i1Xu1AgMBAAECggEAfPI7UZr3BckO3lnLup0ICrXYmmW1AUTPPJ8c4O7Oom55 -EAaLqsvjuzkC6kGBYGW8jKX6lpjOkPKvLvk6l0fKrEhGrQFdSKKSDjFJlTgya19v -j1sdXwqAiILHer2JwUUShSJlowkGoL5UA7RURR8oye0M8KFATnVxtIpQyCinXiW/ -LkDuqUr8MIbu6V/KcoSOLfJyTWyuwSRPHuFKhv154UAqaTkSPbf2mCTa9hH5Tb4f -Lfzy9o3Ux4ieZceG28De+SmC7uMzbBs1stowOuDmFg3znI/1Br/sQEAXPFngDe3s -soDD2PbLo7/4SPBNgl5vygf7jhvxHPY3DTUXOxLSgQKBgQD4EzKVTx/GpF7Yswma -oixidzSi/KnHJiMjIERF4QPVfDNnggRORNMbPnRhNWSRhS7r+INYbN4yB/vBZO5I -IIqowdJbLjGbmq91equP0zzrP2wCjqtFK6gRElX7acAWY5xTesIT5Fa1Ug++dFLS -MxCZKL6JMZaHJzZVzXugaltMsQKBgQDBUvPSaDnIBrZGdNtAyNMxZyVbp/ObIKW1 -TvCDX2hqf+yiTVclbZr5QkwCE3MHErfsKlWU01K9CtzsQh4u9L5tPaeFlvm6iZq6 -ktbflNvI+z+qEW3JbROR4WwwbmWFvKRLBA0OQom7tGuNnNyRtkDFxlkFJPcD6Eff -ZEq+ewrQRQKBgQCV7URM6J0TuJN58/qB8jFQ8Spmtr0FFw91UzLv6KYgiAepLvLb -Os07UeuUNGiragqJoo//CQzgv+JvZ0h7Xu9uPnWblbd1i28vWQwGyGuw4Yutn/vy -ugfBCYvdfnQRE/KOoUpaK04cF5RcToEfeK03Y2CEGewXkqNMB/wHXz/+gQKBgE8Y -34WQ+0Mp69375dEl2bL23sQXfYZU3zfFaoZ1vMUGPg1R03wO0j91rp+S0ZdtQy8v -SwCvTcTm8uj/TFYt8NPFTAtOcDKwJkx708p6n0ol8jBlHSQyqrUfJCLUqFkFi7rd -l3HkK3JPKUoxidVcWjgRJU8DhsVkfjOaVzKEKTJ5AoGARBwn7gt2H35urQ6/U3nJ -hFjOVn01F5uV0NvRtRDCsAIUMeA2T4pwALUUIqlA9HmpwYgLeG4bZ+SkhNpy70N/ -qcufT1DeM+q3H5zFPANyjcqVaqa6KUnttvi/lhxMdRb6GsA9TzzHzY1P9ovpIOCK -IS639NPzxpI0Ka+v6t+nFEM= ------END PRIVATE KEY----- diff --git a/src/intTest/resources/server/cert.crt b/src/test/resources/server/cert.crt similarity index 100% rename from src/intTest/resources/server/cert.crt rename to src/test/resources/server/cert.crt diff --git a/src/intTest/resources/server/encrypted.key b/src/test/resources/server/encrypted.key similarity index 100% rename from src/intTest/resources/server/encrypted.key rename to src/test/resources/server/encrypted.key diff --git a/src/intTest/resources/server/keystore.jks b/src/test/resources/server/keystore.jks similarity index 100% rename from src/intTest/resources/server/keystore.jks rename to src/test/resources/server/keystore.jks diff --git a/src/intTest/resources/server/keystore.p12 b/src/test/resources/server/keystore.p12 similarity index 100% rename from src/intTest/resources/server/keystore.p12 rename to src/test/resources/server/keystore.p12 diff --git a/src/intTest/resources/server/malformed.jks b/src/test/resources/server/malformed.jks similarity index 100% rename from src/intTest/resources/server/malformed.jks rename to src/test/resources/server/malformed.jks diff --git a/src/intTest/resources/server/malformed.p12 b/src/test/resources/server/malformed.p12 similarity index 100% rename from src/intTest/resources/server/malformed.p12 rename to src/test/resources/server/malformed.p12 diff --git a/src/intTest/resources/server/norway.jks b/src/test/resources/server/norway.jks similarity index 100% rename from src/intTest/resources/server/norway.jks rename to src/test/resources/server/norway.jks diff --git a/src/intTest/resources/server/norway.p12 b/src/test/resources/server/norway.p12 similarity index 100% rename from src/intTest/resources/server/norway.p12 rename to src/test/resources/server/norway.p12 diff --git a/src/intTest/resources/server/passwordless.key b/src/test/resources/server/passwordless.key similarity index 100% rename from src/intTest/resources/server/passwordless.key rename to src/test/resources/server/passwordless.key diff --git a/src/intTest/resources/server/wrong.pem b/src/test/resources/server/wrong.pem similarity index 100% rename from src/intTest/resources/server/wrong.pem rename to src/test/resources/server/wrong.pem