├── .gitignore ├── .java-version ├── .mailmap ├── .scala-steward.conf ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── documentation └── src │ └── main │ └── paradox │ ├── CertificateGeneration.md │ ├── CertificateRevocation.md │ ├── DebuggingSSL.md │ ├── DefaultContext.md │ ├── ExampleSSLConfig.md │ ├── HostnameVerification.md │ ├── KeyStores.md │ ├── LooseSSL.md │ ├── Protocols.md │ ├── TestingSSL.md │ ├── WSQuickStart.md │ ├── code │ ├── HowsMySSLSpec.scala │ ├── genca.sh │ ├── genclient.sh │ ├── genpassword.sh │ ├── genserver.sh │ ├── genserverexp.sh │ └── gentruststore.sh │ └── index.md ├── project ├── AutomaticModuleName.scala ├── Common.scala ├── Dependencies.scala ├── build.properties └── plugins.sbt ├── release.sh ├── scripts ├── tests.sh ├── validate-docs.sh └── validate-pom-files.sh └── ssl-config-core └── src ├── main ├── resources │ └── reference.conf └── scala │ └── com │ └── typesafe │ └── sslconfig │ ├── ssl │ ├── AlgorithmChecker.scala │ ├── Algorithms.scala │ ├── CompositeCertificateException.scala │ ├── CompositeX509KeyManager.scala │ ├── CompositeX509TrustManager.scala │ ├── Config.scala │ ├── Debug.scala │ ├── DefaultHostnameVerifier.scala │ ├── DisabledComplainingHostnameVerifier.scala │ ├── FakeChainedKeyStore.scala │ ├── FakeKeyStore.scala │ ├── FakeSSLTools.scala │ ├── KeyStore.scala │ ├── MonkeyPatcher.scala │ ├── NoopHostnameVerifier.scala │ ├── Protocols.scala │ ├── SSLContextBuilder.scala │ ├── SystemConfiguration.scala │ ├── debug │ │ ├── ClassFinder.scala │ │ ├── DebugConfiguration.scala │ │ ├── FixCertpathDebugLogging.scala │ │ ├── FixInternalDebugLogging.scala │ │ └── FixLoggingAction.scala │ ├── package.scala │ └── tracing │ │ ├── TraceLogger.scala │ │ ├── TracingSSLContextSpi.scala │ │ ├── TracingSSLEngine.scala │ │ ├── TracingSSLServerSocketFactory.scala │ │ ├── TracingSSLSocketFactory.scala │ │ ├── TracingX509ExtendedKeyManager.scala │ │ └── TracingX509ExtendedTrustManager.scala │ └── util │ ├── Configuration.scala │ ├── LoggerFactory.scala │ └── NoDepsLogger.scala └── test └── scala └── com └── typesafe └── sslconfig └── ssl ├── CertificateGenerator.scala ├── CompositeX509KeyManagerSpec.scala ├── CompositeX509TrustManagerSpec.scala ├── ConfigSSLContextBuilderSpec.scala ├── FakeKeyStoreSpec.scala ├── KeyStoreSpec.scala ├── LoggingSSLFactorySpec.scala ├── SSLConfigParserSpec.scala └── SystemPropertiesSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | sona.sbt 2 | *# 3 | *.iml 4 | *.ipr 5 | *.iws 6 | *.pyc 7 | *.tm.epoch 8 | *.vim 9 | */project/boot 10 | */project/build/target 11 | */project/project.target.config-classes 12 | *~ 13 | .#* 14 | .*.swp 15 | .DS_Store 16 | .cache 17 | .cache-tests 18 | .cache-main 19 | .classpath 20 | .codefellow 21 | .ensime* 22 | .eprj 23 | .history 24 | .idea 25 | .manager 26 | .multi-jvm 27 | .project 28 | .scala_dependencies 29 | .scalastyle 30 | .settings 31 | .tags 32 | .tags_sorted_by_file 33 | .target 34 | .worksheet 35 | Makefile 36 | TAGS 37 | _akka_cluster/ 38 | _dump 39 | _mb 40 | activemq-data 41 | akka-contrib/rst_preprocessed/ 42 | akka-docs/_build/ 43 | akka-docs/exts/ 44 | akka-docs/rst_preprocessed/ 45 | akka-docs-dev/_build/ 46 | akka-docs-dev/exts/ 47 | akka-docs-dev/rst_preprocessed/ 48 | akka-osgi/src/main/resources/*.conf 49 | akka.sublime-project 50 | akka.sublime-workspace 51 | akka.tmproj 52 | beanstalk/ 53 | bin/ 54 | data 55 | deploy/*.jar 56 | etags 57 | lib_managed 58 | logs 59 | manifest.mf 60 | mongoDB/ 61 | multiverse.log 62 | out 63 | project/akka-build.properties 64 | project/boot/* 65 | project/plugins/project 66 | redis/ 67 | reports 68 | run-codefellow 69 | schoir.props 70 | semantic.cache 71 | src_managed 72 | storage 73 | tags 74 | target 75 | target-sbt 76 | tm*.lck 77 | tm*.log 78 | tm.out 79 | worker*.log 80 | *-shim.sbt 81 | test-output 82 | .tmpBin 83 | /maven-4.0.0.xsd 84 | .bsp/ 85 | -------------------------------------------------------------------------------- /.java-version: -------------------------------------------------------------------------------- 1 | 1.8 2 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Kenji Yoshida <6b656e6a69@gmail.com> 2 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | updates.pin = [ 2 | # Currently code compiled with Scala 3.0 can't 3 | # read code compiled with Scala 3.1, so let's 4 | # postpone updating beyond 3.0.x until we have 5 | # a reason to do so. 6 | # See also https://www.scala-lang.org/blog/2022/02/01/scala-3.1.1-released.html 7 | { groupId = "org.scala-lang", artifactId = "scala3-library", version = "3.0." } 8 | ] 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | version: ~> 1.0 # needed for imports 2 | import: scala/scala-dev:travis/default.yml 3 | 4 | language: scala 5 | 6 | addons: 7 | apt: 8 | packages: 9 | # Needed for `xmllint` which is used to validate pom files 10 | - libxml2-utils 11 | # Used to download maven xsd file 12 | - wget 13 | # To debug docs generation 14 | - tree 15 | 16 | git: 17 | depth: false # Avoid sbt-dynver not seeing the tag 18 | 19 | env: 20 | - ADOPTOPENJDK=8 21 | - ADOPTOPENJDK=11 22 | 23 | scala: 24 | - 2.11.12 25 | - 2.12.15 26 | - 2.13.8 27 | - 3.0.2 28 | 29 | script: 30 | - ./scripts/tests.sh 31 | - ./scripts/validate-pom-files.sh 32 | - ./scripts/validate-docs.sh 33 | 34 | stages: 35 | - name: test 36 | - name: release 37 | if: tag IS present 38 | 39 | jobs: 40 | include: 41 | - stage: release 42 | script: sbt ci-release 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | - Apache 2.0 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSL Config 2 | 3 | [![Build Status](https://travis-ci.com/lightbend/ssl-config.svg?branch=main)](https://travis-ci.com/lightbend/ssl-config) ![Maven metadata URL](https://img.shields.io/maven-metadata/v/http/central.maven.org/maven2/com/typesafe/ssl-config-core_2.13/maven-metadata.xml.svg) 4 | 5 | Goal and purpose of this library is to make [Play WS][] library as well as Akka HTTP "secure by default". 6 | Sadly, while Java's security has been steadily improving some settings are still left up to the user, 7 | and certain algorithms which should never be used in a serious production system are still accepted by 8 | the default settings of the SSL/TLS infrastructure. These things are possible to fix, by providing specialized 9 | implementations and/or defining additional settings for the Java runtime to use – this is exactly the purpose of SSL Config. 10 | 11 | Additional modules offer integration with Play WS (which by default utilises [Async Http Client][]), 12 | Akka Http and any other library which may need support from this library. 13 | 14 | ## Versions 15 | 16 | The project is maintained on two branches: 17 | 18 | - [`main`](https://github.com/lightbend/ssl-config/tree/main) which requires Java 8 and is used by Akka `2.5.x` and later. 19 | - [`release-0.1`](https://github.com/lightbend/ssl-config/tree/release-0.1) which is Java 6 compatible 20 | (does lots of manual improvements and checks that JDK6 didn't). 21 | Currently only the *legacy version* of Akka Streams & Http (which is `2.0.x`) uses this version. 22 | 23 | ## State of this project 24 | 25 | ssl-config at this point in time is used primarily internally in Akka HTTP, and is being evolved 26 | towards being "the place" one configures all possible SSL/TLS related settings, mostly focused on 27 | the client side of things. 28 | 29 | The project is hosted externally of either Akka or Play, in order to foster convergence and re-use 30 | of the more tricky bits of configuring TLS. 31 | 32 | Binary compatibility is **not guaranteed** between versions (in the `0.x.z` series) of ssl-config at this point in time. 33 | We aim to stabilise the APIs and provide a stable release eventually. 34 | 35 | ## Documentation 36 | 37 | Docs are available on: 38 | 39 | ## Recommended reading 40 | 41 | An excellent series by [Will Sargent](https://github.com/wsargent) about making 42 | [Play WS][] (from which this library originates) "secure by default": 43 | 44 | - [Fixing the Most Dangerous Code in the World](https://tersesystems.com/blog/2014/01/13/fixing-the-most-dangerous-code-in-the-world/) 45 | - [Fixing X.509 Certificates](https://tersesystems.com/blog/2014/03/20/fixing-x509-certificates/) 46 | - [Fixing Certificate Revocation](https://tersesystems.com/blog/2014/03/22/fixing-certificate-revocation/) 47 | - [Fixing Hostname Verification](https://tersesystems.com/blog/2014/03/23/fixing-hostname-verification/) 48 | - [Testing Hostname Verification](https://tersesystems.com/blog/2014/03/31/testing-hostname-verification) 49 | 50 | ## License 51 | 52 | Lightbend 2015-2020, Apache 2.0 53 | 54 | [Async Http Client]: https://github.com/AsyncHttpClient/async-http-client/ 55 | [Play WS]: https://www.playframework.com/documentation/latest/ScalaWS 56 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import com.typesafe.sbt.osgi.SbtOsgi 2 | import com.typesafe.sbt.osgi.SbtOsgi.autoImport._ 3 | import com.typesafe.tools.mima.core._ 4 | 5 | ThisBuild / scalaVersion := Version.scala212 6 | 7 | val disablePublishingSettings = Seq( 8 | // https://github.com/sbt/sbt/pull/3380 9 | publish / skip := true, 10 | publishArtifact := false, 11 | mimaReportBinaryIssues := false 12 | ) 13 | 14 | lazy val sslConfigCore = project.in(file("ssl-config-core")) 15 | .settings(AutomaticModuleName.settings("ssl.config.core")) 16 | .settings(osgiSettings: _*) 17 | .settings( 18 | crossScalaVersions := Seq(Version.scala213, Version.scala212, Version.scala211, Version.scala3), 19 | javacOptions ++= Seq("-source", "1.8", "-target", "1.8"), 20 | name := "ssl-config-core", 21 | mimaReportSignatureProblems := true, 22 | mimaPreviousArtifacts := (CrossVersion.partialVersion(scalaVersion.value) match { 23 | case Some((2, _)) => 24 | Set("0.5.0") 25 | case _ => 26 | Set() 27 | }).map("com.typesafe" %% "ssl-config-core" % _), 28 | mimaBinaryIssueFilters ++= Seq( 29 | // private[sslconfig] 30 | ProblemFilters.exclude[DirectMissingMethodProblem]("com.typesafe.sslconfig.ssl.SSLLooseConfig.this"), 31 | // private[sslconfig] 32 | ProblemFilters.exclude[DirectMissingMethodProblem]("com.typesafe.sslconfig.ssl.SSLConfigSettings.this"), 33 | // synthetic on private[sslconfig] 34 | ProblemFilters.exclude[IncompatibleResultTypeProblem]("com.typesafe.sslconfig.ssl.SSLConfigSettings.$default*") 35 | ), 36 | libraryDependencies ++= Dependencies.sslConfigCore, 37 | libraryDependencies ++= Dependencies.testDependencies, 38 | OsgiKeys.bundleSymbolicName := s"${organization.value}.sslconfig", 39 | OsgiKeys.exportPackage := Seq(s"com.typesafe.sslconfig.*;version=${version.value}"), 40 | OsgiKeys.importPackage := Seq("!sun.misc", "!sun.security.*", configImport(), "*"), 41 | OsgiKeys.requireCapability := """osgi.ee;filter:="(&(osgi.ee=JavaSE)(version>=1.8))"""", 42 | ).enablePlugins(SbtOsgi) 43 | 44 | val documentation = project.enablePlugins(ParadoxPlugin, ParadoxSitePlugin).settings( 45 | name := "SSL Config", 46 | Paradox / siteSubdirName := "", 47 | paradoxTheme := Some(builtinParadoxTheme("generic")), 48 | disablePublishingSettings, 49 | ) 50 | 51 | lazy val root = project.in(file(".")) 52 | .aggregate( 53 | sslConfigCore, 54 | documentation 55 | ) 56 | .settings(disablePublishingSettings: _*) 57 | 58 | def configImport(packageName: String = "com.typesafe.config.*") = versionedImport(packageName, "1.4.2", "1.5.0") 59 | def versionedImport(packageName: String, lower: String, upper: String) = s"""$packageName;version="[$lower,$upper)"""" 60 | 61 | lazy val checkCodeFormat = taskKey[Unit]("Check that code format is following Scalariform rules") 62 | 63 | checkCodeFormat := { 64 | import scala.sys.process._ 65 | val exitCode = ("git diff --exit-code" !) 66 | if (exitCode != 0) { 67 | sys.error( 68 | """ 69 | |ERROR: Scalariform check failed, see differences above. 70 | |To fix, format your sources using sbt scalariformFormat test:scalariformFormat before submitting a pull request. 71 | |Additionally, please squash your commits (eg, use git commit --amend) if you're going to update this pull request. 72 | """.stripMargin) 73 | } 74 | } 75 | 76 | addCommandAlias("validateCode", ";scalariformFormat;test:scalariformFormat;headerCheck;test:headerCheck;checkCodeFormat") 77 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/CertificateGeneration.md: -------------------------------------------------------------------------------- 1 | # Generating X.509 Certificates 2 | 3 | ## X.509 Certificates 4 | 5 | Public key certificates are a solution to the problem of identity. 6 | Encryption alone is enough to set up a secure connection, but there's no 7 | guarantee that you are talking to the server that you think you are 8 | talking to. Without some means to verify the identity of a remote 9 | server, an attacker could still present itself as the remote server and 10 | then forward the secure connection onto the remote server. Public key 11 | certificates solve this problem. 12 | 13 | The best way to think about public key certificates is as a passport 14 | system. Certificates are used to establish information about the bearer 15 | of that information in a way that is difficult to forge. This is why 16 | certificate verification is so important: accepting **any** certificate 17 | means that even an attacker's certificate will be blindly accepted. 18 | 19 | ## Using Keytool 20 | 21 | Use the keytool version that comes with JDK 8: 22 | 23 | * [1.8](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html) 24 | 25 | The examples below use keytool 1.8 for marking a certificate for CA 26 | usage or for a hostname. 27 | 28 | ## Generating a random password 29 | 30 | Create a random password using pwgen (`brew install pwgen` if you're 31 | on a Mac): 32 | 33 | @@snip [genpassword.sh](./code/genpassword.sh) { #context } 34 | 35 | ## Server Configuration 36 | 37 | You will need a server with a DNS hostname assigned, for hostname 38 | verification. In this example, we assume the hostname is 39 | `example.com`. 40 | 41 | ### Generating a server CA 42 | 43 | The first step is to create a certificate authority that will sign the 44 | example.com certificate. The root CA certificate has a couple of 45 | additional attributes (ca:true, keyCertSign) that mark it explicitly as 46 | a CA certificate, and will be kept in a trust store. 47 | 48 | @@snip [genca.sh](./code/genca.sh) { #context } 49 | 50 | ### Generating example.com certificates 51 | 52 | The example.com certificate is presented by the `example.com` server 53 | in the handshake. 54 | 55 | @@snip [genserver.sh](./code/genserver.sh) { #context } 56 | 57 | You should see: 58 | 59 | ``` 60 | Alias name: example.com 61 | Creation date: ... 62 | Entry type: PrivateKeyEntry 63 | Certificate chain length: 2 64 | Certificate[1]: 65 | Owner: CN=example.com, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US 66 | Issuer: CN=exampleCA, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US 67 | ``` 68 | 69 | ### Configuring example.com certificates in Nginx 70 | 71 | If example.com does not use Java as a TLS termination point, and you are 72 | using nginx, you may need to export the certificates in PEM format. 73 | 74 | Unfortunately, keytool does not export private key information, so 75 | openssl must be installed to pull private keys. 76 | 77 | @@snip [genserverexp.sh](./code/genserverexp.sh) { #context } 78 | 79 | Now that you have both `example.com.crt` (the public key certificate) 80 | and `example.com.key` (the private key), you can set up an HTTPS 81 | server. 82 | 83 | For example, to use the keys in nginx, you would set the following in 84 | `nginx.conf`: 85 | 86 | ``` 87 | ssl_certificate /etc/nginx/certs/example.com.crt; 88 | ssl_certificate_key /etc/nginx/certs/example.com.key; 89 | ``` 90 | 91 | If you are using client authentication (covered in **Client 92 | Configuration** below), you will also need to add: 93 | 94 | ``` 95 | ssl_client_certificate /etc/nginx/certs/clientca.crt; 96 | ssl_verify_client on; 97 | ``` 98 | 99 | You can check the certificate is what you expect by checking the server: 100 | 101 | ``` 102 | keytool -printcert -sslserver example.com 103 | ``` 104 | 105 | ## Client Configuration 106 | 107 | There are two parts to setting up a client -- configuring a trust store, 108 | and configuring client authentication. 109 | 110 | ### Configuring a Trust Store 111 | 112 | Any clients need to see that the server's example.com certificate is 113 | trusted, but don't need to see the private key. Generate a trust store 114 | which contains only the certificate and hand that out to clients. Many 115 | java clients prefer to have the trust store in JKS format. 116 | 117 | @@snip [gentruststore.sh](./code/gentruststore.sh) { #context } 118 | 119 | You should see a `trustedCertEntry` for exampleca: 120 | 121 | ``` 122 | Alias name: exampleca 123 | Creation date: ... 124 | Entry type: trustedCertEntry 125 | 126 | Owner: CN=exampleCA, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US 127 | Issuer: CN=exampleCA, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US 128 | ``` 129 | 130 | The `exampletrust.jks` store will be used in the TrustManager. 131 | 132 | ```conf 133 | ssl-config { 134 | trustManager = { 135 | stores = [ 136 | { path = "/Users/wsargent/work/ssltest/conf/exampletrust.jks" } 137 | ] 138 | } 139 | } 140 | ``` 141 | 142 | @@@ note 143 | 144 | Also see the @ref:[Configuring Key Stores and Trust Stores](KeyStores.md) section for more information. 145 | 146 | @@@ 147 | 148 | ### Configure Client Authentication 149 | 150 | Client authentication can be obscure and poorly documented, but it 151 | relies on the following steps: 152 | 153 | 1. The server asks for a client certificate, presenting a CA that it 154 | expects a client certificate to be signed with. In this case, 155 | `CN=clientCA` (see the [debug 156 | example](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ReadDebug.html)). 157 | 2. The client looks in the KeyManager for a certificate which is signed 158 | by `clientCA`, using `chooseClientAlias` and 159 | `certRequest.getAuthorities`. 160 | 3. The KeyManager will return the `client` certificate to the server. 161 | 4. The server will do an additional ClientKeyExchange in the handshake. 162 | 163 | The steps to create a client CA and a signed client certificate are 164 | broadly similiar to the server certificate generation, but for 165 | convenience are presented in a single script: 166 | 167 | @@snip [genclient.sh](./code/genclient.sh) { #context } 168 | 169 | There should be one alias `client`, looking like the following: 170 | 171 | ``` 172 | Your keystore contains 1 entry 173 | 174 | Alias name: client 175 | Creation date: ... 176 | Entry type: PrivateKeyEntry 177 | Certificate chain length: 2 178 | Certificate[1]: 179 | Owner: CN=client, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US 180 | Issuer: CN=clientCA, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US 181 | ``` 182 | 183 | And put `client.jks` in the key manager: 184 | 185 | ```conf 186 | ssl-config { 187 | keyManager = { 188 | stores = [ 189 | { type = "JKS", path = "conf/client.jks", password = $PW } 190 | ] 191 | } 192 | } 193 | ``` 194 | 195 | @@@ note 196 | 197 | Also see the @ref:[Configuring Key Stores and Trust Stores](KeyStores.md) section for more information. 198 | 199 | @@@ 200 | 201 | ## Certificate Management Tools 202 | 203 | If you want to examine certificates in a graphical tool than a command 204 | line tool, you can use [Keystore 205 | Explorer](http://keystore-explorer.sourceforge.net/) or 206 | [xca](http://sourceforge.net/projects/xca/). [Keystore 207 | Explorer](http://keystore-explorer.sourceforge.net/) is especially 208 | convenient as it recognizes JKS format. It works better as a manual 209 | installation, and requires some tweaking to the export policy. 210 | 211 | If you want to use a command line tool with more flexibility than 212 | keytool, try [java-keyutil](https://code.google.com/p/java-keyutil/), 213 | which understands multi-part PEM formatted certificates and JKS. 214 | 215 | ## Certificate Settings 216 | 217 | ### Secure 218 | 219 | If you want the best security, consider using 220 | [ECDSA](https://blog.cloudflare.com/ecdsa-the-digital-signature-algorithm-of-a-better-internet) 221 | as the signature algorithm (in keytool, this would be `-sigalg EC`). 222 | ECDSA is also known as "ECC SSL Certificate". 223 | 224 | ### Compatible 225 | 226 | For compatibility with older systems, use RSA with 2048 bit keys and 227 | SHA256 as the signature algorithm. If you are creating your own CA 228 | certificate, use 4096 bits for the root. 229 | 230 | ## Further Reading 231 | 232 | * [JSSE Reference Guide To Creating 233 | KeyStores](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore) 234 | * [Java PKI Programmer's 235 | Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html) 236 | * [Fixing X.509 237 | Certificates](https://tersesystems.com/2014/03/20/fixing-x509-certificates/) -------------------------------------------------------------------------------- /documentation/src/main/paradox/CertificateRevocation.md: -------------------------------------------------------------------------------- 1 | # Configuring Certificate Revocation 2 | 3 | Certificate Revocation in JSSE can be done through two means: 4 | certificate revocation lists (CRLs) and OCSP. 5 | 6 | Certificate Revocation can be very useful in situations where a server's 7 | private keys are compromised, as in the case of 8 | [Heartbleed](http://heartbleed.com). 9 | 10 | Certificate Revocation is disabled by default in JSSE. It is defined in 11 | two places: 12 | 13 | * [PKI Programmer's Guide, Appendix 14 | C](https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html#AppC) 15 | * [Enable OCSP 16 | Checking](https://blogs.oracle.com/xuelei/entry/enable_ocsp_checking) 17 | 18 | To enable OCSP, you must set the following system properties on the 19 | command line: 20 | 21 | ``` 22 | java -Dcom.sun.security.enableCRLDP=true -Dcom.sun.net.ssl.checkRevocation=true 23 | ``` 24 | 25 | After doing the above, you can enable certificate revocation in the 26 | client: 27 | 28 | ```conf 29 | ssl-config.checkRevocation = true 30 | ``` 31 | 32 | Setting `checkRevocation` will set the internal `ocsp.enable` 33 | security property automatically: 34 | 35 | ```scala 36 | java.security.Security.setProperty("ocsp.enable", "true") 37 | ``` 38 | 39 | And this will set OCSP checking when making HTTPS requests. 40 | 41 | @@@ note 42 | 43 | Enabling OCSP requires a round trip to the OCSP responder. 44 | This adds a notable overhead on HTTPS calls, and can make calls up 45 | to [33% 46 | slower](https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30). 47 | The mitigation technique, OCSP stapling, is not supported in JSSE. 48 | 49 | @@@ 50 | 51 | Or, if you wish to use a static CRL list, you can define a list of URLs: 52 | 53 | ```conf 54 | ssl-config.revocationLists = [ "http://example.com/crl" ] 55 | ``` 56 | 57 | ## Further Reading 58 | 59 | * [Fixing Certificate 60 | Revocation](https://tersesystems.com/2014/03/22/fixing-certificate-revocation/) 61 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/DebuggingSSL.md: -------------------------------------------------------------------------------- 1 | # Debugging SSL Connections 2 | 3 | In the event that an HTTPS connection does not go through, debugging JSSE can be a hassle. 4 | 5 | @@@ warning 6 | 7 | Enabling debugging wraps the regular SSLContext into a tracing SSLContext. 8 | This means any code that relied on `instanceOf` checks of the old SSLContext 9 | will start behaving differently when debugging is enabled. 10 | For example this appears to be the case when trying to use this module 11 | with the Jetty ALPN agent. 12 | 13 | @@@ 14 | 15 | @@@ note 16 | 17 | Prior to 0.4.0, the debug system relied on undocumented modification of internal JSSE debug settings that were normally set using 18 | `javax.net.debug` and `java.security.debug` system properties on startup. 19 | 20 | This system has been removed, and the debug flags that do not have a direct correlation in the new system are deprecated. 21 | @@@ 22 | 23 | WS SSL provides configuration options that will turn trace logging at a **warn** level for SSLContext, SSLEngine, TrustManager and KeyManager. 24 | 25 | To configure, set the `ssl-config.debug` property in 26 | `application.conf`: 27 | 28 | ```conf 29 | ssl-config.debug = { 30 | # Enable all debugging 31 | all = false 32 | 33 | # Enable sslengine / socket tracing 34 | ssl = false 35 | 36 | # Enable SSLContext tracing 37 | sslctx = false 38 | 39 | # Enable key manager tracing 40 | keymanager = false 41 | 42 | # Enable trust manager tracing 43 | trustmanager = false 44 | } 45 | ``` 46 | 47 | You can also set `javax.net.debug` and `java.security.debug` system properties directly at startup, using a [`.jvmopts`](https://www.scala-sbt.org/1.0/docs/Travis-CI-with-sbt.html) file for sbt: 48 | 49 | ```bash 50 | # Don't allow client to dictate terms - this can also be used for DoS attacks. 51 | # Undocumented, defined in sun.security.ssl.Handshaker.java:205 52 | -Djdk.tls.rejectClientInitiatedRenegotiation=true 53 | 54 | # Add more details to the disabled algorithms list 55 | # http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#DisabledAlgorithms 56 | # and http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7133344 57 | -Djava.security.properties=disabledAlgorithms.properties 58 | 59 | # Enable this if you need to use OCSP or CRL 60 | # http://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html#AppC 61 | #-Dcom.sun.security.enableCRLDP=true 62 | #-Dcom.sun.net.ssl.checkRevocation=true 63 | 64 | -Djavax.net.debug=ssl:handshake 65 | -Djava.security.debug=certpath:x509:ocsp 66 | ``` 67 | 68 | Oracle has a number of sections on debugging JSSE issues: 69 | 70 | * [Debugging SSL/TLS connections](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ReadDebug.html) 71 | * [Debugging Utilities](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug) 72 | * [Troubleshooting Security](https://docs.oracle.com/javase/8/docs/technotes/guides/security/troubleshooting-security.html) 73 | * [JSSE Debug Logging WithTimestamp](https://blogs.oracle.com/xuelei/entry/jsse_debug_logging_with_timestamp) 74 | * [How to Analyze Java SSL Errors](http://www.smartjava.org/content/how-analyze-java-ssl-errors) 75 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/DefaultContext.md: -------------------------------------------------------------------------------- 1 | # Using the Default SSLContext 2 | 3 | If you don't want to use the SSLContext that WS provides for you, and 4 | want to use `SSLContext.getDefault`, please set: 5 | 6 | ```conf 7 | ssl-config.default = true 8 | ``` 9 | 10 | If you are using the default SSLContext, then the only way to change 11 | JSSE behavior is through manipulating the [JSSE system 12 | properties](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#Customization). 13 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/ExampleSSLConfig.md: -------------------------------------------------------------------------------- 1 | # Example Configurations 2 | 3 | TLS can be very confusing. Here are some settings that can help. 4 | 5 | ## Connecting to an internal web service 6 | 7 | If you are using WS to communicate with a single internal web service 8 | which is configured with an up to date TLS implementation, then you have 9 | no need to use an external CA. Internal certificates will work fine, and 10 | are arguably [more 11 | secure](http://www.thoughtcrime.org/blog/authenticity-is-broken-in-ssl-but-your-app-ha/) 12 | than the CA system. 13 | 14 | Generate a self signed certificate from the @ref:[generating certificates](CertificateGeneration.md) section, and tell the client to 15 | trust the CA's public certificate. 16 | 17 | ```conf 18 | ssl-config { 19 | trustManager = { 20 | stores = [ 21 | { type = "JKS", path = "exampletrust.jks" } 22 | ] 23 | } 24 | } 25 | ``` 26 | 27 | ## Connecting to an internal web service with client authentication 28 | 29 | If you are using client authentication, then you need to include a 30 | keyStore to the key manager that contains a PrivateKeyEntry, which 31 | consists of a private key and the X.509 certificate containing the 32 | corresponding public key. See the "Configure Client Authentication" 33 | section in @ref:[generating certificates](CertificateGeneration.md). 34 | 35 | ```conf 36 | ssl-config { 37 | keyManager = { 38 | stores = [ 39 | { type = "JKS", path = "client.jks", password = "changeit1" } 40 | ] 41 | } 42 | trustManager = { 43 | stores = [ 44 | { type = "JKS", path = "exampletrust.jks" } 45 | ] 46 | } 47 | } 48 | ``` 49 | 50 | ## Connecting to several external web services 51 | 52 | If you are communicating with several external web services, then you 53 | may find it more convenient to configure one client with several stores: 54 | 55 | ```conf 56 | ssl-config { 57 | trustManager = { 58 | stores = [ 59 | { type = "PEM", path = "service1.pem" } 60 | { path = "service2.jks" } 61 | { path = "service3.jks" } 62 | ] 63 | } 64 | } 65 | ``` 66 | 67 | If client authentication is required, then you can also set up the key 68 | manager with several stores: 69 | 70 | ```conf 71 | ssl-config { 72 | keyManager = { 73 | stores = [ 74 | { type: "PKCS12", path: "keys/service1-client.p12", password: "changeit1" }, 75 | { type: "PKCS12", path: "keys/service2-client.p12", password: "changeit2" }, 76 | { type: "PKCS12", path: "keys/service3-client.p12", password: "changeit3" } 77 | ] 78 | } 79 | } 80 | ``` 81 | 82 | ## Both Private and Public Servers 83 | 84 | If you are using WS to access both private and public servers on the 85 | same profile, then you will want to include the default JSSE trust store 86 | as well: 87 | 88 | ```conf 89 | ssl-config { 90 | trustManager = { 91 | stores = [ 92 | { path: exampletrust.jks } # Added trust store 93 | { path: ${java.home}/lib/security/cacerts } # Fallback to default JSSE trust store 94 | ] 95 | } 96 | } 97 | ``` -------------------------------------------------------------------------------- /documentation/src/main/paradox/HostnameVerification.md: -------------------------------------------------------------------------------- 1 | # Hostname Verification 2 | 3 | Hostname verification is a little known part of HTTPS that involves a 4 | [server identity check](https://tools.ietf.org/search/rfc2818#section-3.1) to ensure 5 | that the client is talking to the correct server and has not been 6 | redirected by a man in the middle attack. 7 | 8 | The check involves looking at the certificate sent by the server, and 9 | verifying that the `dnsName` in the `subjectAltName` field of the 10 | certificate matches the host portion of the URL used to make the 11 | request. 12 | 13 | WS SSL does hostname verification automatically. 14 | 15 | ## Debugging 16 | 17 | Hostname Verification can be tested using `dnschef`. A complete guide 18 | is out of scope of documentation, but an example can be found in 19 | [Testing Hostname 20 | Verification](https://tersesystems.com/2014/03/31/testing-hostname-verification/). -------------------------------------------------------------------------------- /documentation/src/main/paradox/KeyStores.md: -------------------------------------------------------------------------------- 1 | # Configuring Trust Stores and Key Stores 2 | 3 | Trust stores and key stores contain X.509 certificates. Those 4 | certificates contain public (or private) keys, and are organized and 5 | managed under either a TrustManager or a KeyManager, respectively. 6 | 7 | If you need to generate X.509 certificates, please see @ref:[Certificate Generation](CertificateGeneration.md) for more information. 8 | 9 | ## Configuring a Trust Manager 10 | 11 | A [trust 12 | manager](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#TrustManager) 13 | is used to keep trust anchors: these are root certificates which have 14 | been issued by certificate authorities. It determines whether the remote 15 | authentication credentials (and thus the connection) should be trusted. 16 | As an HTTPS client, the vast majority of requests will use only a trust 17 | manager. 18 | 19 | If you do not configure it at all, WS uses the default trust manager, 20 | which points to the `cacerts` key store in 21 | `${java.home}/lib/security/cacerts`. If you configure a trust manager 22 | explicitly, it will override the default settings and the `cacerts` 23 | store will not be included. 24 | 25 | If you wish to use the default trust store and add another store 26 | containing certificates, you can define multiple stores in your trust 27 | manager. The 28 | [CompositeX509TrustManager](api/scala/play/api/libs/ws/ssl/CompositeX509TrustManager.html) 29 | will try each in order until it finds a match: 30 | 31 | ```conf 32 | ssl-config { 33 | trustManager = { 34 | stores = [ 35 | { path: ${store.directory}/truststore.jks, type: "JKS" } # Added trust store 36 | { path: ${java.home}/lib/security/cacerts, password = "changeit" } # Default trust store 37 | ] 38 | } 39 | } 40 | ``` 41 | 42 | @@@ note 43 | 44 | Trust stores should only contain CA certificates with 45 | public keys, usually JKS or PEM. PKCS12 format is supported, but 46 | PKCS12 should not contain private keys in a trust store, as noted in 47 | the [reference 48 | guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SunJSSE). 49 | 50 | @@@ 51 | 52 | ## Configuring a Key Manager 53 | 54 | A [key 55 | manager](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#KeyManager) 56 | is used to present keys for a remote host. Key managers are typically 57 | only used for client authentication (also known as mutual TLS). 58 | 59 | The 60 | [CompositeX509KeyManager](api/scala/play/api/libs/ws/ssl/CompositeX509KeyManager.html) 61 | may contain multiple stores in the same manner as the trust manager. 62 | 63 | ```conf 64 | ssl-config { 65 | keyManager = { 66 | stores = [ 67 | { 68 | type: "pkcs12", 69 | path: "keystore.p12", 70 | password: "password1" 71 | }, 72 | ] 73 | } 74 | } 75 | ``` 76 | 77 | @@@ note 78 | 79 | A key store that holds private keys should use PKCS12 80 | format, as indicated in the [reference 81 | guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SunJSSE). 82 | 83 | @@@ 84 | 85 | ## Configuring a Store 86 | 87 | A store corresponds to a 88 | [KeyStore](https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html) 89 | object, which is used for both trust stores and key stores. Stores may 90 | have a 91 | [type](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore) 92 | -- `PKCS12`, [`JKS`](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#KeystoreImplementation) 93 | or `PEM` (aka Base64 encoded DER certificate) -- and may have an 94 | associated password. 95 | 96 | Stores must have either a `path` or a `data` attribute. The `path` 97 | attribute must be a file system path. 98 | 99 | ```conf 100 | { type: "PKCS12", path: "/private/keystore.p12" } 101 | ``` 102 | 103 | The `data` attribute must contain a string of PEM encoded 104 | certificates. 105 | 106 | ```conf 107 | { 108 | type: "PEM", data = """ 109 | -----BEGIN CERTIFICATE----- 110 | ...certificate data 111 | -----END CERTIFICATE----- 112 | -----BEGIN CERTIFICATE----- 113 | ...certificate data 114 | -----END CERTIFICATE----- 115 | -----BEGIN CERTIFICATE----- 116 | ...certificate data 117 | -----END CERTIFICATE----- 118 | """ 119 | } 120 | ``` 121 | 122 | ## Further Reading 123 | 124 | In most cases, you will not need to do extensive configuration once the 125 | certificates are installed. If you are having difficulty with 126 | configuration, the following blog posts may be useful: 127 | 128 | * [Key 129 | Management](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#KeyManagement) 130 | * [Java 2-way TLS/SSL (Client Certificates) and PKCS12 vs JKS 131 | KeyStores](http://blog.palominolabs.com/2011/10/18/java-2-way-tlsssl-client-certificates-and-pkcs12-vs-jks-keystores/) 132 | * [HTTPS with Client Certificates on 133 | Android](http://chariotsolutions.com/blog/post/https-with-client-certificates-on/) 134 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/LooseSSL.md: -------------------------------------------------------------------------------- 1 | # Loose Options 2 | 3 | ## We understand 4 | 5 | Setting up SSL is not all that fun. It is not lost on anyone that 6 | setting up even a single web service with HTTPS involves setting up 7 | several certificates, reading boring cryptography documentation and dire 8 | warnings. 9 | 10 | Despite that, all the security features in SSL are there for good 11 | reasons, and turning them off, even for development purposes, has led to 12 | even less fun than setting up SSL properly. 13 | 14 | ## Please read this before turning anything off! 15 | 16 | ### Man in the Middle attacks are well known 17 | 18 | The information security community is very well aware of how insecure 19 | most internal networks are, and uses that to their advantage. A video 20 | discussing attacks detailed a [wide range of possible 21 | attacks](http://2012.video.sector.ca/page/6). 22 | 23 | ### Man in the Middle attacks are common 24 | 25 | The average company can expect to have seven or eight [Man in the 26 | Middle](https://sites.google.com/site/cse825maninthemiddle/) attacks 27 | a year. In some cases, up to 300,000 users can be compromised [over 28 | several 29 | months](https://security.stackexchange.com/questions/12041/are-man-in-the-middle-attacks-extremely-rare). 30 | 31 | ### Attackers have a suite of tools that automatically exploit flaws 32 | 33 | The days of the expert hacker are over. Most security professionals use 34 | automated linux environments such as Kali Linux to do penetration 35 | testing, packed with hundreds of tools to check for exploits. A video of 36 | [Cain & Abel](https://www.youtube.com/watch?v=pfHsRscy540) shows 37 | passwords being compromised in less than 20 seconds. 38 | 39 | Hackers won't bother to see whether something will "look encrypted" or 40 | not. Instead, they'll set up a machine with a toolkit that will run 41 | through every possible exploit, and go out for coffee. 42 | 43 | ### Security is increasingly important and public 44 | 45 | More and more information flows through computers every day. The public 46 | and the media are taking increasing notice of the possibility that their 47 | private communications can be intercepted. Google, Facebook, Yahoo, and 48 | other leading companies have made secure communication a priority and 49 | have devoted millions to ensuring that [data cannot be 50 | read](https://www.eff.org/deeplinks/2013/11/encrypt-web-report-whos-doing-what). 51 | 52 | ### Ethernet / Password protected WiFi does not provide a meaningful level of security. 53 | 54 | A networking auditing tool such as a [Wifi 55 | Pineapple](https://wifipineapple.com/) costs around $100, picks up 56 | all traffic sent over a wifi network, and is so good at intercepting 57 | traffic that people have turned it on and started [intercepting traffic 58 | accidentally](http://www.troyhunt.com/2013/04/the-beginners-guide-to-breaking-website.html). 59 | 60 | ### Companies have been sued for inadequate security 61 | 62 | PCI compliance is not the only thing that companies have to worry about. 63 | The FTC sued [Fandango and Credit 64 | Karma](https://www.ftc.gov/news-events/press-releases/2014/03/fandango-credit-karma-settle-ftc-charges-they-deceived-consumers) 65 | on charges that they failed to securely transmit information, including 66 | credit card information. 67 | 68 | ### Correctly configured HTTPS clients are important 69 | 70 | Sensitive, company confidential information goes over web services. A 71 | paper discussing insecurities in WS clients was titled [The Most 72 | Dangerous Code in the World: Validating SSL Certificates in Non-Browser 73 | Software](https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf), and 74 | lists poor default configuration and explicit disabling of security 75 | options as the primary reason for exposure. The WS client has been 76 | configured as much as possible to be secure by default, and there are 77 | example configurations provided for your benefit. 78 | 79 | ## Mitigation 80 | 81 | If you must turn on loose options, there are a couple of things you can 82 | do to minimize your exposure. 83 | 84 | **Custom Play WSClient**: You can create a [custom Play WSClient](https://www.playframework.com/documentation/2.4.x/ScalaWS) 85 | specifically for the server, using the 86 | [`WSConfigParser`](api/scala/play/api/libs/ws/WSConfigParser.html) 87 | together with `ConfigFactory.parseString`, and ensure it is never used 88 | outside that context. 89 | 90 | **Environment Scoping**: You can define [environment variables in 91 | HOCON](https://github.com/lightbend/config/blob/master/HOCON.md#substitution-fallback-to-environment-variables) 92 | to ensure that any loose options are not hardcoded in configuration 93 | files, and therefore cannot escape an development environment. 94 | 95 | **Runtime / Deployment Checks**: You can add code to your deployment 96 | scripts or program that checks that `ssl-config.loose` options are 97 | not enabled in a production environment. The runtime mode can be found 98 | in the [`Application.mode`](api/scala/play/api/Application.html) 99 | method. 100 | 101 | ## Options 102 | 103 | Finally, here are the options themselves. 104 | 105 | ### Disabling Certificate Verification 106 | 107 | @@@ note 108 | 109 | In most cases, people turn off certificate verification 110 | because they haven't generated certificates. **There are other 111 | options besides disabling certificate verification.** 112 | 113 | * @ref:[Quick Start to WS SSL](WSQuickStart.md) shows how to connect 114 | directly to a server using a self signed certificate. 115 | * @ref:[Generating X.509 Certificates](CertificateGeneration.md) lists a 116 | number of GUI applications that will generate certificates for 117 | you. 118 | * @ref:[Example Configurations](ExampleSSLConfig.md) shows complete 119 | configuration of TLS using self signed certificates. 120 | * If you want to view your application through HTTPS, you can use 121 | [ngrok](https://ngrok.com/) to proxy your application. 122 | * If you need a certificate authority but don't want to pay money, 123 | [StartSSL](https://www.startssl.com/?app=1) or 124 | [CACert](http://www.cacert.org/) will give you a free 125 | certificate. 126 | * If you want a self signed certificate and private key without 127 | typing on the command line, you can use 128 | [selfsignedcertificate.com](http://www.selfsignedcertificate.com/). 129 | 130 | @@@ 131 | 132 | If you've read the above and you still want to completely disable 133 | certificate verification, set the following; 134 | 135 | ```conf 136 | ssl-config.loose.acceptAnyCertificate=true 137 | ``` 138 | 139 | With certificate verification completely disabled, you are vulnerable to 140 | attack from anyone on the network using a tool such as 141 | [mitmproxy](https://mitmproxy.org/). 142 | 143 | @@@ note 144 | 145 | By disabling certificate validation, you are also disabling 146 | hostname verification! 147 | 148 | @@@ 149 | 150 | ### Disabling Hostname Verification 151 | 152 | If you want to disable hostname verification, you can set a loose flag: 153 | 154 | ```conf 155 | ssl-config.loose.disableHostnameVerification=true 156 | ``` 157 | 158 | With hostname verification disabled, a DNS proxy such as `dnschef` can 159 | [easily intercept 160 | communication](https://tersesystems.com/2014/03/31/testing-hostname-verification/). 161 | 162 | @@@ note 163 | 164 | By disabling hostname verification, you are also disabling 165 | certificate verification! 166 | 167 | @@@ 168 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/Protocols.md: -------------------------------------------------------------------------------- 1 | # Configuring Protocols 2 | 3 | By default, WS SSL will use the most secure version of the TLS protocol 4 | available in the JVM. On JDK 1.7 and later, the default protocol is "TLSv1.2". 5 | 6 | The full protocol list in JSSE is available in the [Standard Algorithm Name Documentation](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#jssenames). 7 | 8 | ## Defining the default protocol 9 | 10 | If you want to define a different [default protocol](https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLContext.html#getInstance-java.lang.String-), 11 | you can set it specifically in the client: 12 | 13 | ```conf 14 | # Passed into SSLContext.getInstance() 15 | ssl-config.protocol = "TLSv1.2" 16 | ``` 17 | 18 | If you want to define the list of enabled protocols, you can do so 19 | explicitly: 20 | 21 | ```conf 22 | # passed into sslContext.getDefaultParameters().setEnabledProtocols() 23 | ssl-config.enabledProtocols = [ 24 | "TLSv1.2", 25 | "TLSv1.1", 26 | "TLSv1" 27 | ] 28 | ``` 29 | 30 | If you are on JDK 1.8, you can also set the `jdk.tls.client.protocols` 31 | system property to enable client protocols globally. 32 | 33 | WS recognizes "SSLv3", "SSLv2" and "SSLv2Hello" as weak protocols with a 34 | number of [security issues](https://www.schneier.com/paper-ssl.pdf), 35 | and will throw an exception if they are in the 36 | `ssl-config.enabledProtocols` list. Virtually all servers support 37 | `TLSv1`, so there is no advantage in using these older protocols. 38 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/TestingSSL.md: -------------------------------------------------------------------------------- 1 | # Testing SSL 2 | 3 | Testing an SSL client not only involves unit and integration testing, 4 | but also involves adversarial testing, which tests that an attacker 5 | cannot break or subvert a secure connection. 6 | 7 | ## Unit Testing 8 | 9 | Play comes with `play.api.test.WsTestClient`, which provides two 10 | methods, `wsCall` and `wsUrl`. It can be helpful to use 11 | `PlaySpecification` and `in new WithApplication` 12 | 13 | ```scala 14 | "calls index" in new WithApplication() { 15 | await(wsCall(routes.Application.index()).get()) 16 | } 17 | ``` 18 | 19 | ```scala 20 | wsUrl("https://example.com").get() 21 | ``` 22 | 23 | ## Integration Testing 24 | 25 | If you want confirmation that your client is correctly configured, you 26 | can call out to [HowsMySSL](https://www.howsmyssl.com/s/api.html), 27 | which has an API to check JSSE settings. 28 | 29 | @@snip [HowsMySSLSpec.scala](./code/HowsMySSLSpec.scala) { #context } 30 | 31 | Note that if you are writing tests that involve custom configuration 32 | such as revocation checking or disabled algorithms, you may need to pass 33 | system properties into SBT: 34 | 35 | ```sbt 36 | javaOptions in Test ++= Seq("-Dcom.sun.security.enableCRLDP=true", "-Dcom.sun.net.ssl.checkRevocation=true", "-Djavax.net.debug=all") 37 | ``` 38 | 39 | ## Adversarial Testing 40 | 41 | There are several points of where a connection can be attacked. Writing 42 | these tests is fairly easy, and running these adversarial tests against 43 | unsuspecting programmers can be extremely satisfying. 44 | 45 | @@@ note 46 | 47 | This should not be taken as a complete list, but as a 48 | guide. In situations where security is paramount, a review should be 49 | done by professional info-sec consultants. 50 | 51 | @@@ 52 | 53 | ### Testing Certificate Verification 54 | 55 | Write a test to connect to "[https://example.com](https://example.com)". The server should 56 | present a certificate which says the subjectAltName is dnsName, but the 57 | certificate should be signed by a CA certificate which is not in the 58 | trust store. The client should reject it. 59 | 60 | This is a very common failure. There are a number of proxies like 61 | [mitmproxy](https://mitmproxy.org) or 62 | [Fiddler](http://www.telerik.com/fiddler) which will only work if 63 | certificate verification is disabled or the proxy's certificate is 64 | explicitly added to the trust store. 65 | 66 | ### Testing Weak Cipher Suites 67 | 68 | The server should send a cipher suite that includes NULL or ANON cipher 69 | suites in the handshake. If the client accepts it, it is sending 70 | unencrypted data. 71 | 72 | @@@ note 73 | 74 | For a more in depth test of a server's cipher suites, see 75 | [sslyze](https://github.com/iSECPartners/sslyze). 76 | 77 | @@@ 78 | 79 | ### Testing Certificate Validation 80 | 81 | To test for weak signatures, the server should send the client a 82 | certificate which has been signed with, for example, the MD2 digest 83 | algorithm. The client should reject it as being too weak. 84 | 85 | To test for weak certificate, The server should send the client a 86 | certificate which contains a public key with a key size under 1024 bits. 87 | The client should reject it as being too weak. 88 | 89 | @@@ note 90 | 91 | For a more in depth test of certification validation, see 92 | [tlspretense](https://github.com/iSECPartners/tlspretense) and 93 | [frankencert](https://github.com/sumanj/frankencert). 94 | 95 | @@@ 96 | 97 | ### Testing Hostname Verification 98 | 99 | Write a test to "[https://example.com](https://example.com)". If the server presents a 100 | certificate where the subjectAltName's dnsName is not example.com, the 101 | connection should terminate. 102 | 103 | @@@ note 104 | 105 | For a more in depth test, see 106 | [dnschef](https://tersesystems.com/2014/03/31/testing-hostname-verification/). 107 | 108 | @@@ -------------------------------------------------------------------------------- /documentation/src/main/paradox/WSQuickStart.md: -------------------------------------------------------------------------------- 1 | # Quick Start to WS SSL 2 | 3 | This section is for people who need to connect to a remote web service 4 | over HTTPS, and don't want to read through the entire manual. If you 5 | need to set up a web service or configure client authentication, please 6 | proceed to the @ref:[next section](CertificateGeneration.md). 7 | 8 | ## Connecting to a Remote Server over HTTPS 9 | 10 | If the remote server is using a certificate that is signed by a well 11 | known certificate authority, then WS should work out of the box without 12 | any additional configuration. You're done! 13 | 14 | If the web service is not using a well known certificate authority, then 15 | it is using either a private CA or a self-signed certificate. You can 16 | determine this easily by using curl: 17 | 18 | ```sh 19 | curl https://financialcryptography.com # uses cacert.org as a CA 20 | ``` 21 | 22 | If you receive the following error: 23 | 24 | ``` 25 | curl: (60) SSL certificate problem: Invalid certificate chain 26 | More details here: http://curl.haxx.se/docs/sslcerts.html 27 | 28 | curl performs SSL certificate verification by default, using a "bundle" 29 | of Certificate Authority (CA) public keys (CA certs). If the default 30 | bundle file isn't adequate, you can specify an alternate file 31 | using the --cacert option. 32 | If this HTTPS server uses a certificate signed by a CA represented in 33 | the bundle, the certificate verification probably failed due to a 34 | problem with the certificate (it might be expired, or the name might 35 | not match the domain name in the URL). 36 | If you'd like to turn off curl's verification of the certificate, use 37 | the -k (or --insecure) option. 38 | ``` 39 | 40 | Then you have to obtain the CA's certificate, and add it to the trust 41 | store. 42 | 43 | ## Obtain the Root CA Certificate 44 | 45 | Ideally this should be done out of band: the owner of the web service 46 | should provide you with the root CA certificate directly, in a way that 47 | can't be faked, preferably in person. 48 | 49 | In the case where there is no communication (and this is **not 50 | recommended**), you can sometimes get the root CA certificate directly 51 | from the certificate chain, using 52 | [`keytool` from JDK 1.8](http://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html): 53 | 54 | ``` 55 | keytool -printcert -sslserver playframework.com 56 | ``` 57 | 58 | which returns #2 as the root certificate: 59 | 60 | ``` 61 | Certificate #2 62 | ==================================== 63 | Owner: CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE 64 | Issuer: CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE 65 | ``` 66 | 67 | To get the certificate chain in an exportable format, use the -rfc 68 | option: 69 | 70 | ``` 71 | keytool -printcert -sslserver playframework.com -rfc 72 | ``` 73 | 74 | which will return a series of certificates in PEM format: 75 | 76 | ``` 77 | -----BEGIN CERTIFICATE----- 78 | ... 79 | -----END CERTIFICATE----- 80 | ``` 81 | 82 | which can be copied and pasted into a file. The very last certificate in 83 | the chain will be the root CA certificate. 84 | 85 | @@@ note 86 | 87 | Not all websites will include the root CA certificate. You 88 | should decode the certificate with keytool or with [certificate 89 | decoder](https://www.sslshopper.com/certificate-decoder.html) to 90 | ensure you have the right certificate. 91 | 92 | @@@ 93 | 94 | ## Point the trust manager at the PEM file 95 | 96 | Add the following into `conf/application.conf`, specifying `PEM` 97 | format specifically: 98 | 99 | ```conf 100 | ssl-config { 101 | trustManager = { 102 | stores = [ 103 | { type = "PEM", path = "/path/to/cert/globalsign.crt" } 104 | ] 105 | } 106 | } 107 | ``` 108 | 109 | This will tell the trust manager to ignore the default `cacerts` store 110 | of certificates, and only use your custom CA certificate. 111 | 112 | After that, WS will be configured, and you can test that your connection 113 | works with: 114 | 115 | ```scala 116 | WS.url("https://example.com").get() 117 | ``` 118 | 119 | You can see more examples on the @ref:[example configurations](ExampleSSLConfig.md) page. 120 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/HowsMySSLSpec.scala: -------------------------------------------------------------------------------- 1 | package detailedtopics.configuration.ws { 2 | 3 | //#context 4 | 5 | import java.io.File 6 | 7 | import akka.actor.ActorSystem 8 | import akka.stream.ActorMaterializer 9 | import play.api.libs.ws.ahc._ 10 | 11 | import play.api.{Mode, Environment} 12 | import play.api.libs.json.JsSuccess 13 | import play.api.libs.ws._ 14 | import play.api.test._ 15 | 16 | import com.typesafe.config.{ConfigFactory, ConfigValueFactory} 17 | import scala.concurrent.duration._ 18 | 19 | import org.specs2.specification.AfterAll 20 | 21 | class HowsMySSLSpec extends PlaySpecification with AfterAll { 22 | val system = ActorSystem("howsMySSLSpec") 23 | implicit val materializer = ActorMaterializer()(system) 24 | 25 | def afterAll(): Unit = { 26 | system.shutdown() 27 | } 28 | 29 | def createClient(rawConfig: play.api.Configuration): WSClient = { 30 | val classLoader = Thread.currentThread().getContextClassLoader 31 | val parser = new WSConfigParser(rawConfig, new Environment(new File("."), classLoader, Mode.Test)) 32 | val clientConfig = new AhcWSClientConfig(parser.parse()) 33 | // Debug flags only take effect in JSSE when DebugConfiguration().configure is called. 34 | //import play.api.libs.ssl-config.debug.DebugConfiguration 35 | //clientConfig.ssl.map { 36 | // _.debug.map(new DebugConfiguration().configure) 37 | //} 38 | val builder = new AhcConfigBuilder(clientConfig) 39 | val client = new AhcWSClient(builder.build()) 40 | client 41 | } 42 | 43 | def configToMap(configString: String): Map[String, _] = { 44 | import scala.collection.JavaConverters._ 45 | ConfigFactory.parseString(configString).root().unwrapped().asScala.toMap 46 | } 47 | 48 | "WS" should { 49 | 50 | "verify common behavior" in { 51 | // GeoTrust SSL CA - G2 intermediate certificate not found in cert chain! 52 | // See https://github.com/jmhodges/howsmyssl/issues/38 for details. 53 | val geoTrustPem = 54 | """-----BEGIN CERTIFICATE----- 55 | |MIIEWTCCA0GgAwIBAgIDAjpjMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYT 56 | |AlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVz 57 | |dCBHbG9iYWwgQ0EwHhcNMTIwODI3MjA0MDQwWhcNMjIwNTIwMjA0MDQwWjBE 58 | |MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UE 59 | |AxMUR2VvVHJ1c3QgU1NMIENBIC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IB 60 | |DwAwggEKAoIBAQC5J/lP2Pa3FT+Pzc7WjRxr/X/aVCFOA9jK0HJSFbjJgltY 61 | |eYT/JHJv8ml/vJbZmnrDPqnPUCITDoYZ2+hJ74vm1kfy/XNFCK6PrF62+J58 62 | |9xD/kkNm7xzU7qFGiBGJSXl6Jc5LavDXHHYaKTzJ5P0ehdzgMWUFRxasCgdL 63 | |LnBeawanazpsrwUSxLIRJdY+lynwg2xXHNil78zs/dYS8T/bQLSuDxjTxa9A 64 | |kl0HXk7+Yhc3iemLdCai7bgK52wVWzWQct3YTSHUQCNcj+6AMRaraFX0DjtU 65 | |6QRN8MxOgV7pb1JpTr6mFm1C9VH/4AtWPJhPc48Obxoj8cnI2d+87FLXAgMB 66 | |AAGjggFUMIIBUDAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjAd 67 | |BgNVHQ4EFgQUEUrQcznVW2kIXLo9v2SaqIscVbwwEgYDVR0TAQH/BAgwBgEB 68 | |/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDov 69 | |L2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwNAYIKwYBBQUH 70 | |AQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nZW90cnVzdC5jb20w 71 | |TAYDVR0gBEUwQzBBBgpghkgBhvhFAQc2MDMwMQYIKwYBBQUHAgEWJWh0dHA6 72 | |Ly93d3cuZ2VvdHJ1c3QuY29tL3Jlc291cmNlcy9jcHMwKgYDVR0RBCMwIaQf 73 | |MB0xGzAZBgNVBAMTElZlcmlTaWduTVBLSS0yLTI1NDANBgkqhkiG9w0BAQUF 74 | |AAOCAQEAPOU9WhuiNyrjRs82lhg8e/GExVeGd0CdNfAS8HgY+yKk3phLeIHm 75 | |TYbjkQ9C47ncoNb/qfixeZeZ0cNsQqWSlOBdDDMYJckrlVPg5akMfUf+f1Ex 76 | |RF73Kh41opQy98nuwLbGmqzemSFqI6A4ZO6jxIhzMjtQzr+t03UepvTp+UJr 77 | |YLLdRf1dVwjOLVDmEjIWE4rylKKbR6iGf9mY5ffldnRk2JG8hBYo2CVEMH6C 78 | |2Kyx5MDkFWzbtiQnAioBEoW6MYhYR3TjuNJkpsMyWS4pS0XxW4lJLoKaxhgV 79 | |RNAuZAEVaDj59vlmAwxVG52/AECu8EgnTOCAXi25KhV6vGb4NQ== 80 | |-----END CERTIFICATE----- 81 | """.stripMargin 82 | 83 | val configString = """ 84 | |//ssl-config.debug=["certpath", "ssl", "trustmanager"] 85 | |ssl-config.protocol="TLSv1" 86 | |ssl-config.enabledProtocols=["TLSv1"] 87 | | 88 | |ssl-config.trustManager = { 89 | | stores = [ 90 | | { type: "PEM", data = ${geotrust.pem} } 91 | | ] 92 | |} 93 | """.stripMargin 94 | val rawConfig = ConfigFactory.parseString(configString) 95 | val configWithPem = rawConfig.withValue("geotrust.pem", ConfigValueFactory.fromAnyRef(geoTrustPem)) 96 | val configWithSystemProperties = ConfigFactory.load(configWithPem) 97 | val playConfiguration = play.api.Configuration(configWithSystemProperties) 98 | 99 | val client = createClient(playConfiguration) 100 | val response = await(client.url("https://www.howsmyssl.com/a/check").get())(5.seconds) 101 | response.status must be_==(200) 102 | 103 | val jsonOutput = response.json 104 | val result = (jsonOutput \ "tls_version").validate[String] 105 | result must beLike { 106 | case JsSuccess(value, path) => 107 | value must_== "TLS 1.0" 108 | } 109 | } 110 | } 111 | } 112 | //#context 113 | 114 | } 115 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/genca.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #//#context 4 | export PW=`cat password` 5 | 6 | # Create a self signed key pair root CA certificate. 7 | keytool -genkeypair -v \ 8 | -alias exampleca \ 9 | -dname "CN=exampleCA, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US" \ 10 | -keystore exampleca.jks \ 11 | -keypass:env PW \ 12 | -storepass:env PW \ 13 | -keyalg RSA \ 14 | -keysize 4096 \ 15 | -ext KeyUsage:critical="keyCertSign" \ 16 | -ext BasicConstraints:critical="ca:true" \ 17 | -validity 9999 18 | 19 | # Export the exampleCA public certificate as exampleca.crt so that it can be used in trust stores. 20 | keytool -export -v \ 21 | -alias exampleca \ 22 | -file exampleca.crt \ 23 | -keypass:env PW \ 24 | -storepass:env PW \ 25 | -keystore exampleca.jks \ 26 | -rfc 27 | #//#context 28 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/genclient.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #//#context 4 | export PW=`cat password` 5 | 6 | # Create a self signed certificate & private key to create a root certificate authority. 7 | keytool -genkeypair -v \ 8 | -alias clientca \ 9 | -keystore client.jks \ 10 | -dname "CN=clientca, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US" \ 11 | -keypass:env PW \ 12 | -storepass:env PW \ 13 | -keyalg RSA \ 14 | -keysize 4096 \ 15 | -ext KeyUsage:critical="keyCertSign" \ 16 | -ext BasicConstraints:critical="ca:true" \ 17 | -validity 9999 18 | 19 | # Create another key pair that will act as the client. 20 | keytool -genkeypair -v \ 21 | -alias client \ 22 | -keystore client.jks \ 23 | -dname "CN=client, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US" \ 24 | -keypass:env PW \ 25 | -storepass:env PW \ 26 | -keyalg RSA \ 27 | -keysize 2048 28 | 29 | # Create a certificate signing request from the client certificate. 30 | keytool -certreq -v \ 31 | -alias client \ 32 | -keypass:env PW \ 33 | -storepass:env PW \ 34 | -keystore client.jks \ 35 | -file client.csr 36 | 37 | # Make clientCA create a certificate chain saying that client is signed by clientCA. 38 | keytool -gencert -v \ 39 | -alias clientca \ 40 | -keypass:env PW \ 41 | -storepass:env PW \ 42 | -keystore client.jks \ 43 | -infile client.csr \ 44 | -outfile client.crt \ 45 | -ext EKU="clientAuth" \ 46 | -rfc 47 | 48 | # Export the client-ca certificate from the keystore. This goes to nginx under "ssl_client_certificate" 49 | # and is presented in the CertificateRequest. 50 | keytool -export -v \ 51 | -alias clientca \ 52 | -file clientca.crt \ 53 | -storepass:env PW \ 54 | -keystore client.jks \ 55 | -rfc 56 | 57 | # Import the signed certificate back into client.jks. This is important, as JSSE won't send a client 58 | # certificate if it can't find one signed by the client-ca presented in the CertificateRequest. 59 | keytool -import -v \ 60 | -alias client \ 61 | -file client.crt \ 62 | -keystore client.jks \ 63 | -storetype JKS \ 64 | -storepass:env PW 65 | 66 | # Export the client CA's certificate and private key to pkcs12, so it's safe. 67 | keytool -importkeystore -v \ 68 | -srcalias clientca \ 69 | -srckeystore client.jks \ 70 | -srcstorepass:env PW \ 71 | -destkeystore clientca.p12 \ 72 | -deststorepass:env PW \ 73 | -deststoretype PKCS12 74 | 75 | # Import the client CA's public certificate into a JKS store for Play Server to read. We don't use 76 | # the PKCS12 because it's got the CA private key and we don't want that. 77 | keytool -import -v \ 78 | -alias clientca \ 79 | -file clientca.crt \ 80 | -keystore clientca.jks \ 81 | -storepass:env PW << EOF 82 | yes 83 | EOF 84 | 85 | # Then, strip out the client CA alias from client.jks, just leaving the signed certificate. 86 | keytool -delete -v \ 87 | -alias clientca \ 88 | -storepass:env PW \ 89 | -keystore client.jks 90 | 91 | # List out the contents of client.jks just to confirm it. 92 | keytool -list -v \ 93 | -keystore client.jks \ 94 | -storepass:env PW 95 | #//#context 96 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/genpassword.sh: -------------------------------------------------------------------------------- 1 | #//#context 2 | export PW=`pwgen -Bs 10 1` 3 | echo $PW > password 4 | #//#context 5 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/genserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #//#context 4 | export PW=`cat password` 5 | 6 | # Create a server certificate, tied to example.com 7 | keytool -genkeypair -v \ 8 | -alias example.com \ 9 | -dname "CN=example.com, OU=Example Org, O=Example Company, L=San Francisco, ST=California, C=US" \ 10 | -keystore example.com.jks \ 11 | -keypass:env PW \ 12 | -storepass:env PW \ 13 | -keyalg RSA \ 14 | -keysize 2048 \ 15 | -validity 385 16 | 17 | # Create a certificate signing request for example.com 18 | keytool -certreq -v \ 19 | -alias example.com \ 20 | -keypass:env PW \ 21 | -storepass:env PW \ 22 | -keystore example.com.jks \ 23 | -file example.com.csr 24 | 25 | # Tell exampleCA to sign the example.com certificate. Note the extension is on the request, not the 26 | # original certificate. 27 | # Technically, keyUsage should be digitalSignature for DHE or ECDHE, keyEncipherment for RSA. 28 | keytool -gencert -v \ 29 | -alias exampleca \ 30 | -keypass:env PW \ 31 | -storepass:env PW \ 32 | -keystore exampleca.jks \ 33 | -infile example.com.csr \ 34 | -outfile example.com.crt \ 35 | -ext KeyUsage:critical="digitalSignature,keyEncipherment" \ 36 | -ext EKU="serverAuth" \ 37 | -ext SAN="DNS:example.com" \ 38 | -rfc 39 | 40 | # Tell example.com.jks it can trust exampleca as a signer. 41 | keytool -import -v \ 42 | -alias exampleca \ 43 | -file exampleca.crt \ 44 | -keystore example.com.jks \ 45 | -storetype JKS \ 46 | -storepass:env PW << EOF 47 | yes 48 | EOF 49 | 50 | # Import the signed certificate back into example.com.jks 51 | keytool -import -v \ 52 | -alias example.com \ 53 | -file example.com.crt \ 54 | -keystore example.com.jks \ 55 | -storetype JKS \ 56 | -storepass:env PW 57 | 58 | # List out the contents of example.com.jks just to confirm it. 59 | # If you are using Play as a TLS termination point, this is the key store you should present as the server. 60 | keytool -list -v \ 61 | -keystore example.com.jks \ 62 | -storepass:env PW 63 | #//#context 64 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/genserverexp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #//#context 4 | export PW=`cat password` 5 | 6 | # Export example.com's public certificate for use with nginx. 7 | keytool -export -v \ 8 | -alias example.com \ 9 | -file example.com.crt \ 10 | -keypass:env PW \ 11 | -storepass:env PW \ 12 | -keystore example.com.jks \ 13 | -rfc 14 | 15 | # Create a PKCS#12 keystore containing the public and private keys. 16 | keytool -importkeystore -v \ 17 | -srcalias example.com \ 18 | -srckeystore example.com.jks \ 19 | -srcstoretype jks \ 20 | -srcstorepass:env PW \ 21 | -destkeystore example.com.p12 \ 22 | -destkeypass:env PW \ 23 | -deststorepass:env PW \ 24 | -deststoretype PKCS12 25 | 26 | # Export the example.com private key for use in nginx. Note this requires the use of OpenSSL. 27 | openssl pkcs12 \ 28 | -nocerts \ 29 | -nodes \ 30 | -passout env:PW \ 31 | -passin env:PW \ 32 | -in example.com.p12 \ 33 | -out example.com.key 34 | 35 | # Clean up. 36 | rm example.com.p12 37 | #//#context 38 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/code/gentruststore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #//#context 4 | export PW=`cat password` 5 | 6 | # Create a JKS keystore that trusts the example CA, with the default password. 7 | keytool -import -v \ 8 | -alias exampleca \ 9 | -file exampleca.crt \ 10 | -keypass:env PW \ 11 | -storepass changeit \ 12 | -keystore exampletrust.jks << EOF 13 | yes 14 | EOF 15 | 16 | # List out the details of the store password. 17 | keytool -list -v \ 18 | -keystore exampletrust.jks \ 19 | -storepass changeit 20 | #//#context 21 | -------------------------------------------------------------------------------- /documentation/src/main/paradox/index.md: -------------------------------------------------------------------------------- 1 | # SSL Config 2 | 3 | @@@ note 4 | 5 | ssl-config was originally part of Play's WS module. 6 | 7 | @@@ 8 | 9 | [Play WS](https://www.playframework.com/documentation/2.4.x/ScalaWS) allows you to set up HTTPS completely from a 10 | configuration file, without the need to write code. It does this by 11 | layering the Java Secure Socket Extension (JSSE) with a configuration 12 | layer and with reasonable defaults. 13 | 14 | JDK 1.8 contains an implementation of JSSE which is [significantly more 15 | advanced](https://docs.oracle.com/javase/8/docs/technotes/guides/security/enhancements-8.html) 16 | than previous versions, and should be used if security is a priority. 17 | 18 | Copyright (C) 2009-2020 Lightbend Inc. <[https://www.lightbend.com](https://www.lightbend.com)> 19 | 20 | @@toc { depth=2 } 21 | 22 | @@@ index 23 | 24 | * [WSQuickStart](WSQuickStart.md) 25 | * [CertificateGeneration](CertificateGeneration.md) 26 | * [KeyStores](KeyStores.md) 27 | * [Protocols](Protocols.md) 28 | * [CertificateRevocation](CertificateRevocation.md) 29 | * [HostnameVerification](HostnameVerification.md) 30 | * [ExampleSSLConfig](ExampleSSLConfig.md) 31 | * [DefaultContext](DefaultContext.md) 32 | * [DebuggingSSL](DebuggingSSL.md) 33 | * [LooseSSL](LooseSSL.md) 34 | * [TestingSSL](TestingSSL.md) 35 | 36 | @@@ 37 | 38 | ## Further Reading 39 | 40 | JSSE is a complex product. For convenience, the JSSE materials for JDK 41 | 1.8 are provided here: 42 | 43 | - [JSSE Reference 44 | Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html) 45 | - [JSSE Crypto 46 | Spec](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/CryptoSpec.html#SSLTLS) 47 | - [SunJSSE 48 | Providers](https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SunJSSEProvider) 49 | - [PKI Programmer's 50 | Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html) 51 | - [keytool](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html) 52 | -------------------------------------------------------------------------------- /project/AutomaticModuleName.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2021 Lightbend Inc. 3 | */ 4 | 5 | import sbt.{Def, _} 6 | import sbt.Keys._ 7 | 8 | /** 9 | * Helper to set Automatic-Module-Name in projects. 10 | * 11 | * !! DO NOT BE TEMPTED INTO AUTOMATICALLY DERIVING THE NAMES FROM PROJECT NAMES !! 12 | * 13 | * The names carry a lot of implications and DO NOT have to always align 1:1 with the group ids or package names, 14 | * though there should be of course a strong relationship between them. 15 | */ 16 | object AutomaticModuleName { 17 | private val AutomaticModuleName = "Automatic-Module-Name" 18 | 19 | def settings(name: String): Seq[Def.Setting[Task[Seq[PackageOption]]]] = Seq( 20 | Compile / packageBin / packageOptions += Package.ManifestAttributes(AutomaticModuleName -> name) 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /project/Common.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2021 Lightbend Inc. 3 | */ 4 | 5 | import com.typesafe.sbt.SbtScalariform 6 | import de.heikoseeberger.sbtheader._ 7 | import sbt.Keys._ 8 | import sbt._ 9 | 10 | // Docs have it as HeaderFileType but that is actually a TYPE ALIAS >:-( 11 | // https://github.com/sbt/sbt-header/blob/master/src/main/scala/de/heikoseeberger/sbtheader/HeaderPlugin.scala#L58 12 | import com.typesafe.sbt.SbtScalariform.ScalariformKeys 13 | import de.heikoseeberger.sbtheader.{CommentStyle => HeaderCommentStyle, FileType => HeaderFileType, License => HeaderLicense} 14 | import scalariform.formatter.preferences._ 15 | import com.typesafe.sbt.SbtScalariform.autoImport._ 16 | 17 | /** 18 | * Common sbt settings — automatically added to all projects. 19 | */ 20 | object Common extends AutoPlugin { 21 | 22 | override def trigger = allRequirements 23 | 24 | override def requires = plugins.JvmPlugin && HeaderPlugin 25 | 26 | // AutomateHeaderPlugin is not an allRequirements-AutoPlugin, so explicitly add settings here: 27 | override def projectSettings = 28 | AutomateHeaderPlugin.projectSettings ++ 29 | Seq( 30 | scalariformAutoformat := true, 31 | organization := "com.typesafe", 32 | homepage := Some(url("https://github.com/lightbend/ssl-config")), 33 | developers := List( 34 | Developer("wsargent", "Will Sargent", "", url("https://tersesystems.com")), 35 | Developer("ktoso", "Konrad Malawski", "", url("https://project13.pl")), 36 | ), 37 | updateOptions := updateOptions.value.withCachedResolution(true), 38 | scalacOptions ++= Seq("-encoding", "UTF-8", "-unchecked", "-deprecation", "-feature"), 39 | scalacOptions ++= { 40 | CrossVersion.partialVersion(scalaVersion.value) match { 41 | case Some((2, v)) if v <= 11 => 42 | Seq("-target:jvm-1.8") 43 | case _ => 44 | Nil 45 | } 46 | }, 47 | javacOptions ++= Seq("-encoding", "UTF-8", "-source", "1.8", "-target", "1.8"), 48 | // Scalariform settings 49 | ScalariformKeys.preferences := ScalariformKeys.preferences.value 50 | .setPreference(AlignSingleLineCaseStatements, true) 51 | .setPreference(AlignSingleLineCaseStatements.MaxArrowIndent, 100) 52 | .setPreference(DoubleIndentConstructorArguments, true) 53 | .setPreference(DanglingCloseParenthesis, Preserve) 54 | .setPreference(AlignParameters, false), 55 | 56 | // Header settings 57 | 58 | HeaderPlugin.autoImport.headerMappings := Map( 59 | HeaderFileType.scala -> HeaderCommentStyle.cStyleBlockComment, 60 | HeaderFileType.java -> HeaderCommentStyle.cStyleBlockComment, 61 | HeaderFileType.conf -> HeaderCommentStyle.hashLineComment 62 | ), 63 | 64 | organizationName := "Lightbend", 65 | startYear := Some(2015), 66 | licenses += ("Apache-2.0", new URL("https://www.apache.org/licenses/LICENSE-2.0.txt")), 67 | 68 | HeaderPlugin.autoImport.headerLicense := { 69 | // To be manually updated yearly, preventing unrelated PR's to suddenly fail 70 | // just because time passed 71 | val currentYear = 2020 72 | Some(HeaderLicense.Custom( 73 | s"""Copyright (C) 2015 - $currentYear Lightbend Inc. """ 74 | )) 75 | } 76 | ) 77 | 78 | } 79 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Version { 4 | val typesafeConfig = "1.4.2" 5 | 6 | val jodaTime = "2.10.13" 7 | val jodaTimeConvert = "2.2.2" 8 | 9 | val specs2 = "4.8.3" 10 | 11 | val scala211 = "2.11.12" 12 | val scala212 = "2.12.15" 13 | val scala213 = "2.13.8" 14 | val scala3 = "3.0.2" 15 | } 16 | 17 | object Library { 18 | val typesafeConfig = "com.typesafe" % "config" % Version.typesafeConfig // Apache2 19 | 20 | // TESTS 21 | val specs2 = Seq( 22 | "org.specs2" %% "specs2-core" % Version.specs2 % Test cross CrossVersion.for3Use2_13, 23 | "org.specs2" %% "specs2-junit" % Version.specs2 % Test cross CrossVersion.for3Use2_13, 24 | "org.specs2" %% "specs2-mock" % Version.specs2 % Test cross CrossVersion.for3Use2_13, 25 | "org.specs2" %% "specs2-matcher-extra" % Version.specs2 % Test cross CrossVersion.for3Use2_13, 26 | ) 27 | 28 | val jodaTime = "joda-time" % "joda-time" % Version.jodaTime % Test // ONLY FOR TESTS! 29 | val jodaTimeConvert = "org.joda" % "joda-convert" % Version.jodaTimeConvert % Test // ONLY FOR TESTS! 30 | 31 | } 32 | 33 | object Dependencies { 34 | import Library._ 35 | 36 | val sslConfigCore = Seq(typesafeConfig) 37 | val testDependencies = Library.specs2 ++ Seq(jodaTime, jodaTimeConvert) 38 | } 39 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.6.2 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.6") 2 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3") 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") 4 | addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.6.5") 5 | addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") 6 | addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.9.2") 7 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") 8 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Clean up the directory of local changes so that we can be sure 4 | # the HEAD from release branch is used to run the release. 5 | (read -p "The working directory will now be cleaned from all non-tracked files. Are you sure you want this? " x; test "$x" = yes) || fail "bailing out" 6 | git clean -fxd || fail "cannot git clean -fxd" 7 | 8 | echo "Starting release. You will be prompt to confirm the version and push changes at the end of the process" 9 | sbt 'release cross' 10 | 11 | # Get the latest tag which is the one created above 12 | version=$(git tag --sort=-taggerdate | head -1) 13 | 14 | echo "Publishing documentation for version $version" 15 | sbt makeSite 16 | mv -v documentation/target/site ../releasing-the-docs 17 | 18 | git checkout gh-pages 19 | git pull origin 20 | 21 | rm -rf * 22 | mv -v ../releasing-the-docs docs 23 | mv -v docs/* . 24 | rm -rfv docs 25 | touch .nojekyll 26 | git add . 27 | git commit -m "Releasing docs for version $version" 28 | git push origin gh-pages 29 | 30 | echo "Docs where published. Getting back to release branch." 31 | git checkout - 32 | 33 | echo "[RELEASE SUCCESSFUL]" 34 | echo Make sure to update mimaPreviousArtifacts 35 | -------------------------------------------------------------------------------- /scripts/tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | sbt ++${TRAVIS_SCALA_VERSION} validateCode 6 | sbt ++${TRAVIS_SCALA_VERSION} test 7 | sbt ++${TRAVIS_SCALA_VERSION} doc 8 | sbt ++${TRAVIS_SCALA_VERSION} mimaReportBinaryIssues 9 | -------------------------------------------------------------------------------- /scripts/validate-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | sbt makeSite 6 | 7 | markdown_count=$(ls documentation/src/main/paradox/*.md | wc -l) 8 | html_count=$(ls documentation/target/site/*.html | wc -l) 9 | 10 | if [ "$markdown_count" == "$html_count" ]; then 11 | echo "Website generated correctly!" 12 | else 13 | echo "Documenation was not generated correctly: md = ${markdown_count} html = ${html_count}" 14 | echo "MARKDOWN FILES:" 15 | tree documentation/src/main/paradox 16 | echo "GENERATED HTML FILES:" 17 | tree documentation/target/site 18 | exit 1 19 | fi 20 | -------------------------------------------------------------------------------- /scripts/validate-pom-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | sbt ++${TRAVIS_SCALA_VERSION} publishLocal 6 | 7 | # Downloads maven schema file which will be used to validate the generated pom file 8 | wget -c http://maven.apache.org/xsd/maven-4.0.0.xsd 9 | 10 | # Use xmllint to validate the generated pom.xml 11 | find . -name *.pom -exec xmllint --noout -schema maven-4.0.0.xsd {} \; 12 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2015 - 2020 Lightbend Inc. 2 | 3 | # ssl configuration 4 | ssl-config { 5 | 6 | logger = "com.typesafe.sslconfig.util.NoopLogger" 7 | 8 | # Whether we should use the default JVM SSL configuration or not 9 | # When false additional configuration will be applied on the context (as configured in ssl-config). 10 | default = false 11 | 12 | # The ssl protocol to use 13 | protocol = "TLSv1.2" 14 | 15 | # Whether revocation lists should be checked, if null, defaults to platform default setting. 16 | checkRevocation = null 17 | 18 | # A sequence of URLs for obtaining revocation lists 19 | revocationLists = [] 20 | 21 | # The enabled cipher suites. If empty, uses the platform default. 22 | enabledCipherSuites = [] 23 | 24 | # The enabled protocols. If empty, uses the platform default. 25 | enabledProtocols = ["TLSv1.2", "TLSv1.1", "TLSv1"] 26 | 27 | # The hostname verifier class. 28 | # If non null, should be the fully qualify classname of a class that imlpements HostnameVerifier, 29 | # otherwise the default will be used 30 | hostnameVerifierClass = null 31 | 32 | sslParameters { 33 | # translates to a setNeedClientAuth / setWantClientAuth calls 34 | # "default" – leaves the (which for JDK8 means wantClientAuth and needClientAuth are set to false.) 35 | # "none" – `setNeedClientAuth(false)` 36 | # "want" – `setWantClientAuth(true)` 37 | # "need" – `setNeedClientAuth(true)` 38 | clientAuth = "default" 39 | 40 | # protocols (names) 41 | protocols = [] 42 | } 43 | 44 | # Configuration for the key manager 45 | keyManager { 46 | # The key manager algorithm. If empty, uses the platform default. 47 | algorithm = null 48 | 49 | # The key stores 50 | stores = [ 51 | ] 52 | # The key stores should look like this 53 | prototype.stores { 54 | # The store type. If null, defaults to the platform default store type, ie JKS. 55 | type = null 56 | 57 | # The path to the keystore file. Either this must be non null, or data must be non null. 58 | path = null 59 | 60 | # The data for the keystore. Either this must be non null, or path must be non null. 61 | data = null 62 | 63 | # The password for loading the keystore. If null, uses no password. 64 | # It's recommended to load password using environment variable 65 | password = null 66 | } 67 | } 68 | 69 | trustManager { 70 | # The trust manager algorithm. If empty, uses the platform default. 71 | algorithm = null 72 | 73 | # The trust stores 74 | stores = [ 75 | ] 76 | # The key stores should look like this 77 | prototype.stores { 78 | # The store type. If null, defaults to the platform default store type, ie JKS. 79 | type = null 80 | 81 | # The path to the keystore file. Either this must be non null, or data must be non null. 82 | path = null 83 | 84 | # The data for the keystore. Either this must be non null, or path must be non null. 85 | data = null 86 | 87 | # The password for loading the truststore. If null, uses no password. 88 | # It's recommended to load password using environment variable 89 | password = null 90 | } 91 | 92 | } 93 | 94 | # The loose ssl options. These allow configuring ssl to be more loose about what it accepts, 95 | # at the cost of introducing potential security issues. 96 | loose { 97 | 98 | # If non null, overrides the platform default for whether legacy hello messsages should be allowed. 99 | allowLegacyHelloMessages = null 100 | 101 | # If non null, overrides the platform defalut for whether unsafe renegotiation should be allowed. 102 | allowUnsafeRenegotiation = null 103 | 104 | # Whether hostname verification should be disabled 105 | disableHostnameVerification = false 106 | 107 | # Whether the SNI (Server Name Indication) TLS extension should be disabled 108 | # This setting MAY be respected by client libraries. 109 | # 110 | # https://tools.ietf.org/html/rfc3546#sectiom-3.1 111 | disableSNI = false 112 | 113 | # Whether any certificate should be accepted or not 114 | acceptAnyCertificate = false 115 | } 116 | 117 | # Debug configuration 118 | debug { 119 | # Enable all debugging 120 | all = false 121 | 122 | # Enable sslengine / socket tracing 123 | ssl = false 124 | 125 | # Enable SSLContext tracing 126 | sslctx = false 127 | 128 | # Enable key manager tracing 129 | keymanager = false 130 | 131 | # Enable trust manager tracing 132 | trustmanager = false 133 | 134 | // The following settings are deprecated and have no effect in code. 135 | certpath = false # DEPRECATED 136 | ocsp = false # DEPRECATED 137 | record = false # DEPRECATED 138 | plaintext = false # DEPRECATED 139 | packet = false # DEPRECATED 140 | handshake = false # DEPRECATED 141 | data = false # DEPRECATED 142 | verbose = false # DEPRECATED 143 | keygen = false # DEPRECATED 144 | session = false # DEPRECATED 145 | defaultctx = false # DEPRECATED 146 | sessioncache = false # DEPRECATED 147 | pluggability = false # DEPRECATED 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/AlgorithmChecker.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import javax.security.cert.X509Certificate 8 | 9 | import com.typesafe.sslconfig.util.LoggerFactory 10 | 11 | @deprecated("not operative", "0.5.0") 12 | class AlgorithmChecker(mkLogger: LoggerFactory, val signatureConstraints: Set[AlgorithmConstraint], val keyConstraints: Set[AlgorithmConstraint]) { 13 | def checkKeyAlgorithms(x509Cert: X509Certificate): Unit = () 14 | } 15 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/Algorithms.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | @deprecated("not operative", "0.5.0") 8 | class AlgorithmConstraint 9 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/CompositeCertificateException.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.security.cert.CertificateException 8 | 9 | /** 10 | * A certificate exception that contains underlying exceptions. 11 | */ 12 | class CompositeCertificateException(msg: String, val throwables: Array[Throwable]) extends CertificateException(msg) { 13 | def getSourceExceptions: Array[Throwable] = throwables 14 | } 15 | 16 | object CompositeCertificateException { 17 | 18 | def unwrap(e: Throwable)(block: Throwable => Unit) = { 19 | var cause: Throwable = e 20 | while (cause != null) { 21 | cause match { 22 | case composite: CompositeCertificateException => 23 | composite.getSourceExceptions.foreach { sourceException => 24 | block(sourceException) 25 | } 26 | case other => 27 | block(other) 28 | } 29 | cause = cause.getCause 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/CompositeX509KeyManager.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import javax.net.ssl.{ SSLEngine, X509ExtendedKeyManager, X509KeyManager } 8 | import java.security.{ Principal, PrivateKey } 9 | import java.security.cert.{ CertificateException, X509Certificate } 10 | import java.net.Socket 11 | 12 | import com.typesafe.sslconfig.util.LoggerFactory 13 | 14 | import scala.collection.mutable.ArrayBuffer 15 | 16 | /** 17 | * A keymanager that wraps other X509 key managers. 18 | */ 19 | class CompositeX509KeyManager(mkLogger: LoggerFactory, keyManagers: Seq[X509KeyManager]) extends X509ExtendedKeyManager { 20 | // Must specify X509ExtendedKeyManager: otherwise you get 21 | // "X509KeyManager passed to SSLContext.init(): need an X509ExtendedKeyManager for SSLEngine use" 22 | 23 | private val logger = mkLogger(getClass) 24 | 25 | logger.debug(s"CompositeX509KeyManager start: keyManagers = $keyManagers") 26 | 27 | // You would think that from the method signature that you could use multiple key managers and trust managers 28 | // by passing them as an array in the init method. However, the fine print explicitly says: 29 | // "Only the first instance of a particular key and/or trust manager implementation type in the array is used. 30 | // (For example, only the first javax.net.ssl.X509KeyManager in the array will be used.)" 31 | // 32 | // This doesn't mean you can't have multiple key managers, but that you can't have multiple key managers of 33 | // the same class, i.e. you can't have two X509KeyManagers in the array. 34 | 35 | def getClientAliases(keyType: String, issuers: Array[Principal]): Array[String] = { 36 | logger.debug(s"getClientAliases: keyType = $keyType, issuers = ${issuersToString(issuers)}") 37 | 38 | val clientAliases = ArrayBuffer[String]() 39 | withKeyManagers { keyManager => 40 | val aliases = keyManager.getClientAliases(keyType, issuers) 41 | if (aliases != null) { 42 | clientAliases ++= aliases 43 | } 44 | } 45 | logger.debug(s"getCertificateChain: clientAliases = $clientAliases") 46 | 47 | nullIfEmpty(clientAliases.toArray) 48 | } 49 | 50 | def chooseClientAlias(keyType: Array[String], issuers: Array[Principal], socket: Socket): String = { 51 | logger.debug(s"chooseClientAlias: keyType = ${keyType.toSeq}, issuers = ${issuersToString(issuers)}, socket = $socket") 52 | 53 | withKeyManagers { keyManager => 54 | val clientAlias = keyManager.chooseClientAlias(keyType, issuers, socket) 55 | if (clientAlias != null) { 56 | logger.debug(s"chooseClientAlias: using clientAlias $clientAlias with keyManager $keyManager") 57 | return clientAlias 58 | } 59 | } 60 | null 61 | } 62 | 63 | override def chooseEngineClientAlias(keyType: Array[String], issuers: Array[Principal], engine: SSLEngine): String = { 64 | logger.debug(s"chooseEngineClientAlias: keyType = ${keyType.toSeq}, issuers = ${issuersToString(issuers)}, engine = $engine") 65 | withKeyManagers { (keyManager: X509KeyManager) => 66 | keyManager match { 67 | case extendedKeyManager: X509ExtendedKeyManager => 68 | val clientAlias = extendedKeyManager.chooseEngineClientAlias(keyType, issuers, engine) 69 | if (clientAlias != null) { 70 | logger.debug(s"chooseEngineClientAlias: using clientAlias $clientAlias with keyManager $extendedKeyManager") 71 | return clientAlias 72 | } 73 | case _ => 74 | // do nothing 75 | } 76 | } 77 | null 78 | } 79 | 80 | override def chooseEngineServerAlias(keyType: String, issuers: Array[Principal], engine: SSLEngine): String = { 81 | logger.debug(s"chooseEngineServerAlias: keyType = ${keyType.toSeq}, issuers = ${issuersToString(issuers)}, engine = $engine") 82 | 83 | withKeyManagers { (keyManager: X509KeyManager) => 84 | keyManager match { 85 | case extendedKeyManager: X509ExtendedKeyManager => 86 | val clientAlias = extendedKeyManager.chooseEngineServerAlias(keyType, issuers, engine) 87 | if (clientAlias != null) { 88 | logger.debug(s"chooseEngineServerAlias: using clientAlias $clientAlias with keyManager $extendedKeyManager") 89 | return clientAlias 90 | } 91 | case _ => 92 | // do nothing 93 | } 94 | } 95 | null 96 | } 97 | 98 | def getServerAliases(keyType: String, issuers: Array[Principal]): Array[String] = { 99 | logger.debug(s"getServerAliases: keyType = $keyType, issuers = ${issuersToString(issuers)}") 100 | 101 | val serverAliases = ArrayBuffer[String]() 102 | withKeyManagers { keyManager => 103 | val aliases = keyManager.getServerAliases(keyType, issuers) 104 | if (aliases != null) { 105 | serverAliases ++= aliases 106 | } 107 | } 108 | logger.debug(s"getServerAliases: serverAliases = $serverAliases") 109 | 110 | nullIfEmpty(serverAliases.toArray) 111 | } 112 | 113 | def chooseServerAlias(keyType: String, issuers: Array[Principal], socket: Socket): String = { 114 | logger.debug(s"chooseServerAlias: keyType = $keyType, issuers = ${issuersToString(issuers)}, socket = $socket") 115 | withKeyManagers { keyManager => 116 | val serverAlias = keyManager.chooseServerAlias(keyType, issuers, socket) 117 | if (serverAlias != null) { 118 | logger.debug(s"chooseServerAlias: using serverAlias $serverAlias with keyManager $keyManager") 119 | return serverAlias 120 | } 121 | } 122 | null 123 | } 124 | 125 | def getCertificateChain(alias: String): Array[X509Certificate] = { 126 | logger.debug(s"getCertificateChain: alias = $alias") 127 | withKeyManagers { keyManager => 128 | val chain = keyManager.getCertificateChain(alias) 129 | if (chain != null && chain.length > 0) { 130 | logger.debug(s"getCertificateChain: chain ${debugChain(chain)} with keyManager $keyManager") 131 | return chain 132 | } 133 | } 134 | null 135 | } 136 | 137 | def getPrivateKey(alias: String): PrivateKey = { 138 | logger.debug(s"getPrivateKey: alias = $alias") 139 | withKeyManagers { keyManager => 140 | val privateKey = keyManager.getPrivateKey(alias) 141 | if (privateKey != null) { 142 | logger.debug(s"getPrivateKey: privateKey $privateKey with keyManager $keyManager") 143 | return privateKey 144 | } 145 | } 146 | null 147 | } 148 | 149 | private def withKeyManagers[T](block: (X509KeyManager => T)): Seq[CertificateException] = { 150 | val exceptionList = ArrayBuffer[CertificateException]() 151 | keyManagers.foreach { keyManager => 152 | try { 153 | block(keyManager) 154 | } catch { 155 | case certEx: CertificateException => 156 | exceptionList.append(certEx) 157 | } 158 | } 159 | exceptionList.toSeq 160 | } 161 | 162 | private def nullIfEmpty[T](array: Array[T]) = if (array.size == 0) null else array 163 | 164 | private def issuersToString(issuers: Array[Principal]) = 165 | if (issuers != null) issuers.mkString("[", ", ", "]") else null 166 | 167 | override def toString = { 168 | s"CompositeX509KeyManager(keyManagers = [$keyManagers])" 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/CompositeX509TrustManager.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import javax.net.ssl.X509TrustManager 8 | import java.security.cert._ 9 | 10 | import com.typesafe.sslconfig.util.LoggerFactory 11 | 12 | import scala.collection.mutable.ArrayBuffer 13 | import scala.util.control.NonFatal 14 | import java.security.GeneralSecurityException 15 | 16 | /** 17 | * A trust manager that is a composite of several smaller trust managers. It is responsible for verifying the 18 | * credentials received from a peer. 19 | */ 20 | class CompositeX509TrustManager(mkLogger: LoggerFactory, trustManagers: Seq[X509TrustManager]) extends X509TrustManager { 21 | 22 | private val logger = mkLogger(getClass) 23 | 24 | def getAcceptedIssuers: Array[X509Certificate] = { 25 | logger.debug("getAcceptedIssuers: ") 26 | val certificates = ArrayBuffer[X509Certificate]() 27 | val exceptionList = withTrustManagers { 28 | trustManager => 29 | certificates ++= trustManager.getAcceptedIssuers 30 | } 31 | // getAcceptedIssuers should never throw an exception. 32 | if (!exceptionList.isEmpty) { 33 | val msg = exceptionList(0).getMessage 34 | throw new CompositeCertificateException(msg, exceptionList.toArray) 35 | } 36 | certificates.toArray 37 | } 38 | 39 | // In 1.7, sun.security.ssl.X509TrustManagerImpl extends from javax.net.ssl.X509ExtendedTrustManager. 40 | // The two X509ExtendedTrustManager contain different method signatures, and both are available in 1.7, which means 41 | // it's really hard to keep something backwards compatible if something is calling trustManager.asInstanceOf[X509ExtendedTrustManager] 42 | // internally. For now, we have to trust that the internal API holds to the X509TrustManager interface. 43 | // 44 | //def checkClientTrusted(chain: Array[X509Certificate], authType: String, hostname: String, algorithm: String): Unit = ??? 45 | //def checkServerTrusted(chain: Array[X509Certificate], authType: String, hostname: String, algorithm: String): Unit = ??? 46 | 47 | def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = { 48 | logger.debug(s"checkClientTrusted: chain = ${debugChain(chain)}") 49 | 50 | val anchor: TrustAnchor = new TrustAnchor(chain(chain.length - 1), null) 51 | 52 | var trusted = false 53 | val exceptionList = withTrustManagers { 54 | trustManager => 55 | trustManager.checkClientTrusted(chain, authType) 56 | logger.debug(s"checkClientTrusted: trustManager $trustManager found a match for ${debugChain(chain)}") 57 | trusted = true 58 | } 59 | 60 | if (!trusted) { 61 | val msg = "No trust manager was able to validate this certificate chain." 62 | throw new CompositeCertificateException(msg, exceptionList.toArray) 63 | } 64 | } 65 | 66 | def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = { 67 | logger.debug(s"checkServerTrusted: chain = ${debugChain(chain)}, authType = $authType") 68 | 69 | var trusted = false 70 | val exceptionList = withTrustManagers { 71 | trustManager => 72 | // always run through the trust manager before making any decisions 73 | trustManager.checkServerTrusted(chain, authType) 74 | logger.debug(s"checkServerTrusted: trustManager $trustManager using authType $authType found a match for ${debugChain(chain).toSeq}") 75 | trusted = true 76 | } 77 | 78 | if (!trusted) { 79 | val msg = s"No trust manager was able to validate this certificate chain: # of exceptions = ${exceptionList.size}" 80 | throw new CompositeCertificateException(msg, exceptionList.toArray) 81 | } 82 | } 83 | 84 | private def withTrustManagers(block: (X509TrustManager => Unit)): Seq[Throwable] = { 85 | val exceptionList = ArrayBuffer[Throwable]() 86 | trustManagers.foreach { 87 | trustManager => 88 | try { 89 | block(trustManager) 90 | } catch { 91 | case e: CertPathBuilderException => 92 | logger.debug(s"No path found to certificate: this usually means the CA is not in the trust store. Cause: $e") 93 | exceptionList.append(e) 94 | case e: GeneralSecurityException => 95 | logger.debug(s"General security exception. Cause: $e") 96 | exceptionList.append(e) 97 | case NonFatal(e) => 98 | logger.debug(s"Unexpected exception! Cause: $e") 99 | exceptionList.append(e) 100 | } 101 | } 102 | exceptionList.toSeq 103 | } 104 | 105 | override def toString = { 106 | s"CompositeX509TrustManager(trustManagers = [$trustManagers])" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/Debug.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | /** 8 | * @see http://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html 9 | */ 10 | @deprecated("Setting system properties in JSSE after JVM initialization is unreliable. Please set the java.security.debug system property at startup.", "0.4.0") 11 | class JavaSecurityDebugBuilder(c: SSLDebugConfig) { 12 | 13 | def build(): String = { 14 | val b = new StringBuilder() 15 | if (c.certpath) { 16 | b.append(" certpath") 17 | } 18 | 19 | if (c.ocsp) { 20 | // http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7u40-b43/sun/security/provider/certpath/OCSPResponse.java#132 21 | b.append(" ocsp") 22 | } 23 | 24 | b.toString() 25 | } 26 | 27 | } 28 | 29 | /** 30 | * A builder for setting the system property options in "javax.net.debug" and in "java.security.debug' (in 31 | * the case of "certpath"). 32 | * 33 | * @see http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug 34 | * @see http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/ReadDebug.html 35 | */ 36 | @deprecated("Setting system properties in JSSE after JVM initialization is unreliable. Please set the javax.net.debug system property at startup.", "0.4.0") 37 | class JavaxNetDebugBuilder(c: SSLDebugConfig) { 38 | 39 | def build(): String = { 40 | if (c.all) return "all" 41 | buildSSL(c) 42 | } 43 | 44 | protected def buildSSL(sslDebugConfig: SSLDebugConfig): String = { 45 | import sslDebugConfig._ 46 | 47 | val b = new StringBuilder() 48 | 49 | record.map { 50 | r => 51 | b.append(" record") 52 | if (r.packet) { 53 | b.append(" packet") 54 | } 55 | if (r.plaintext) { 56 | b.append(" plaintext") 57 | } 58 | } 59 | 60 | handshake.map { 61 | h => 62 | b.append(" handshake") 63 | if (h.data) { 64 | b.append(" data") 65 | } 66 | if (h.verbose) { 67 | b.append(" verbose") 68 | } 69 | } 70 | 71 | if (keygen) { 72 | b.append(" keygen") 73 | } 74 | 75 | if (session) { 76 | b.append(" session") 77 | } 78 | 79 | if (defaultctx) { 80 | b.append(" defaultctx") 81 | } 82 | 83 | if (sslctx) { 84 | b.append(" sslctx") 85 | } 86 | 87 | if (sessioncache) { 88 | b.append(" sessioncache") 89 | } 90 | 91 | if (keymanager) { 92 | b.append(" keymanager") 93 | } 94 | 95 | if (trustmanager) { 96 | b.append(" trustmanager") 97 | } 98 | 99 | if (pluggability) { 100 | b.append(" pluggability") 101 | } 102 | 103 | if ((!b.isEmpty) || ssl) { 104 | b.append(" ssl") 105 | } 106 | 107 | b.toString() 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/DefaultHostnameVerifier.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.security.Principal 8 | import java.security.cert.{ Certificate, CertificateException, X509Certificate } 9 | 10 | import com.typesafe.sslconfig.util.LoggerFactory 11 | import javax.net.ssl.{ HostnameVerifier, SSLPeerUnverifiedException, SSLSession } 12 | import javax.security.auth.kerberos.KerberosPrincipal 13 | import sun.security.util.HostnameChecker 14 | 15 | @deprecated("DefaultHostnameVerifier has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 16 | class DefaultHostnameVerifier(mkLogger: LoggerFactory) extends HostnameVerifier { 17 | private val logger = mkLogger(getClass) 18 | 19 | def hostnameChecker: HostnameChecker = { 20 | logger.warn("DefaultHostnameVerifier has been deprecated and does nothing. Please use the javax.net.debug system property.") 21 | HostnameChecker.getInstance(HostnameChecker.TYPE_TLS) 22 | } 23 | 24 | def matchKerberos(hostname: String, principal: Principal) = { 25 | logger.warn("DefaultHostnameVerifier has been deprecated and does nothing. Please use the javax.net.debug system property.") 26 | true 27 | } 28 | 29 | def isKerberos(principal: Principal): Boolean = { 30 | logger.warn("DefaultHostnameVerifier has been deprecated and does nothing. Please use the javax.net.debug system property.") 31 | true 32 | } 33 | 34 | def verify(hostname: String, session: SSLSession): Boolean = { 35 | logger.warn("DefaultHostnameVerifier has been deprecated and does nothing. Please use the javax.net.debug system property.") 36 | true 37 | } 38 | 39 | /** INTERNAL API */ 40 | def matchCertificates(hostname: String, peerCertificates: Array[Certificate]): Boolean = { 41 | logger.warn("DefaultHostnameVerifier has been deprecated and does nothing. Please use the javax.net.debug system property.") 42 | true 43 | } 44 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/DisabledComplainingHostnameVerifier.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import javax.net.ssl.{ SSLSession, HostnameVerifier } 8 | 9 | import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger } 10 | 11 | /** 12 | * Add a disabled but complaining hostname verifier. 13 | */ 14 | class DisabledComplainingHostnameVerifier(mkLogger: LoggerFactory) extends HostnameVerifier { 15 | 16 | private val logger = mkLogger(getClass) 17 | 18 | private val defaultHostnameVerifier = new NoopHostnameVerifier 19 | 20 | override def verify(hostname: String, sslSession: SSLSession): Boolean = { 21 | val hostNameMatches = defaultHostnameVerifier.verify(hostname, sslSession) 22 | if (!hostNameMatches) { 23 | // TODO fix config paths 24 | val msg = 25 | s"Hostname verification failed on hostname $hostname, " + 26 | "but the connection was accepted because ssl-config.loose.disableHostnameVerification is enabled. " + 27 | "Please fix the X.509 certificate on the host to remove this warning." 28 | logger.warn(msg) 29 | } 30 | true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeChainedKeyStore.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.io._ 8 | import java.math.BigInteger 9 | import java.security.cert.X509Certificate 10 | import java.security.interfaces.RSAPublicKey 11 | import java.security.{ KeyPair, KeyPairGenerator, KeyStore, SecureRandom } 12 | import java.util.Date 13 | 14 | import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger } 15 | import javax.net.ssl.KeyManagerFactory 16 | import sun.security.util.ObjectIdentifier 17 | import sun.security.x509._ 18 | 19 | /** 20 | * A fake key store with a selfsigned CA and a certificate issued by that CA. Includes a `trustedCertEntry` for 21 | * each of the two certificates. 22 | * 23 | * {{{ 24 | * Your keystore contains 4 entries 25 | * 26 | * sslconfig-user-trust, Oct 4, 2018, trustedCertEntry, 27 | * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 28 | * sslconfig-user, Oct 4, 2018, PrivateKeyEntry, 29 | * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 30 | * sslconfig-CA-trust, Oct 4, 2018, trustedCertEntry, 31 | * Certificate fingerprint (SHA1): 9B:78:6B:4F:E4:B6:4D:EF:3E:3E:06:32:7A:53:83:28:96:7F:12:C7 32 | * sslconfig-CA, Oct 4, 2018, PrivateKeyEntry, 33 | * Certificate fingerprint (SHA1): 9B:78:6B:4F:E4:B6:4D:EF:3E:3E:06:32:7A:53:83:28:96:7F:12:C7 34 | * }}} 35 | * 36 | * Was: play.core.server.ssl.FakeKeyStore 37 | */ 38 | object FakeChainedKeyStore { 39 | private val EMPTY_PASSWORD = Array.emptyCharArray 40 | 41 | object CA { 42 | 43 | object Alias { 44 | // These two constants use a weird capitalization but that's what keystore uses internally (see class scaladoc) 45 | val trustedCertEntry = "sslconfig-CA-trust" 46 | val PrivateKeyEntry = "sslconfig-CA" 47 | } 48 | 49 | val DistinguishedName = "CN=certification.authority, OU=Unit Testing, O=Mavericks, L=SSL Config Base 1, ST=Cyberspace, C=CY" 50 | val keyPassword: Array[Char] = EMPTY_PASSWORD 51 | } 52 | 53 | object User { 54 | 55 | object Alias { 56 | // These two constants use a weird capitalization but that's what keystore uses internally (see class scaladoc) 57 | val trustedCertEntry = "sslconfig-user-trust" 58 | val PrivateKeyEntry = "sslconfig-user" 59 | } 60 | 61 | val DistinguishedName = "CN=localhost, OU=Unit Testing, O=Mavericks, L=SSL Config Base 1, ST=Cyberspace, C=CY" 62 | val keyPassword: Array[Char] = EMPTY_PASSWORD 63 | } 64 | 65 | object KeystoreSettings { 66 | val GeneratedKeyStore: String = fileInDevModeDir("chained.keystore") 67 | val SignatureAlgorithmName = "SHA256withRSA" 68 | val KeyPairAlgorithmName = "RSA" 69 | val KeyPairKeyLength = 2048 // 2048 is the NIST acceptable key length until 2030 70 | val KeystoreType = "JKS" 71 | val keystorePassword: Array[Char] = EMPTY_PASSWORD 72 | } 73 | 74 | private def fileInDevModeDir(filename: String): String = { 75 | "target" + File.separatorChar + "dev-mode" + File.separatorChar + filename 76 | } 77 | 78 | /** 79 | * Generate a fresh KeyStore object in memory. This KeyStore 80 | * is not saved to disk. If you want that, then call `keyManagerFactory`. 81 | * 82 | * This method is public only for consumption by Play/Lagom. 83 | */ 84 | def generateKeyStore: KeyStore = { 85 | // Create a new KeyStore 86 | val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) 87 | 88 | // Generate the key pair 89 | val keyPairGenerator = KeyPairGenerator.getInstance(KeystoreSettings.KeyPairAlgorithmName) 90 | keyPairGenerator.initialize(KeystoreSettings.KeyPairKeyLength) 91 | val keyPair = keyPairGenerator.generateKeyPair() 92 | val certificateAuthorityKeyPair = keyPairGenerator.generateKeyPair() 93 | 94 | val cacert: X509Certificate = createCertificateAuthority(certificateAuthorityKeyPair) 95 | // Generate a self signed certificate 96 | val cert = createUserCertificate(keyPair, certificateAuthorityKeyPair) 97 | 98 | // Create the key store, first set the store pass 99 | keyStore.load(null, KeystoreSettings.keystorePassword) 100 | keyStore.setKeyEntry(CA.Alias.PrivateKeyEntry, keyPair.getPrivate, CA.keyPassword, Array(cacert)) 101 | keyStore.setCertificateEntry(CA.Alias.trustedCertEntry, cacert) 102 | keyStore.setKeyEntry(User.Alias.PrivateKeyEntry, keyPair.getPrivate, User.keyPassword, Array(cert)) 103 | keyStore.setCertificateEntry(User.Alias.trustedCertEntry, cert) 104 | keyStore 105 | } 106 | 107 | private[ssl] def createUserCertificate(userKeyPair: KeyPair, certificateAuthorityKeyPair: KeyPair): X509Certificate = { 108 | val certInfo = new X509CertInfo() 109 | 110 | // Serial number and version 111 | certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, new SecureRandom()))) 112 | certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) 113 | 114 | // Validity 115 | val validFrom = new Date() 116 | val validTo = new Date(validFrom.getTime + 50l * 365l * 24l * 60l * 60l * 1000l) 117 | val validity = new CertificateValidity(validFrom, validTo) 118 | certInfo.set(X509CertInfo.VALIDITY, validity) 119 | 120 | // Subject and issuer 121 | val certificateAuthorityName = new X500Name(CA.DistinguishedName) 122 | certInfo.set(X509CertInfo.ISSUER, certificateAuthorityName) 123 | val owner = new X500Name(User.DistinguishedName) 124 | certInfo.set(X509CertInfo.SUBJECT, owner) 125 | 126 | // Key and algorithm 127 | certInfo.set(X509CertInfo.KEY, new CertificateX509Key(userKeyPair.getPublic)) 128 | val algorithm = AlgorithmId.get("SHA256WithRSA") 129 | certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithm)) 130 | 131 | // Create a new certificate and sign it 132 | val cert = new X509CertImpl(certInfo) 133 | cert.sign(userKeyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) 134 | 135 | // Since the signature provider may have a different algorithm ID to what we think it should be, 136 | // we need to reset the algorithm ID, and resign the certificate 137 | val actualAlgorithm = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] 138 | certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, actualAlgorithm) 139 | val newCert = new X509CertImpl(certInfo) 140 | newCert.sign(certificateAuthorityKeyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) 141 | newCert 142 | } 143 | 144 | private def createCertificateAuthority(keyPair: KeyPair): X509Certificate = { 145 | val certInfo = new X509CertInfo() 146 | // Serial number and version 147 | certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, new SecureRandom()))) 148 | certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) 149 | 150 | // Validity 151 | val validFrom = new Date() 152 | val validTo = new Date(validFrom.getTime + 50l * 365l * 24l * 60l * 60l * 1000l) // 50 years 153 | val validity = new CertificateValidity(validFrom, validTo) 154 | certInfo.set(X509CertInfo.VALIDITY, validity) 155 | 156 | // Subject and issuer 157 | val owner = new X500Name(CA.DistinguishedName) 158 | certInfo.set(X509CertInfo.SUBJECT, owner) 159 | certInfo.set(X509CertInfo.ISSUER, owner) 160 | 161 | // Key and algorithm 162 | certInfo.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic)) 163 | val algorithm = AlgorithmId.get("SHA256WithRSA") 164 | certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithm)) 165 | 166 | val caExtension = new CertificateExtensions 167 | caExtension.set(BasicConstraintsExtension.NAME, new BasicConstraintsExtension( /* isCritical */ true, /* isCA */ true, 0)) 168 | certInfo.set(X509CertInfo.EXTENSIONS, caExtension) 169 | 170 | // Create a new certificate and sign it 171 | val cert = new X509CertImpl(certInfo) 172 | cert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) 173 | 174 | // Since the signature provider may have a different algorithm ID to what we think it should be, 175 | // we need to reset the algorithm ID, and resign the certificate 176 | val actualAlgorithm = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] 177 | certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, actualAlgorithm) 178 | val newCert = new X509CertImpl(certInfo) 179 | newCert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) 180 | newCert 181 | } 182 | 183 | } 184 | 185 | /** 186 | * A fake key store 187 | * 188 | * Was: play.core.server.ssl.FakeKeyStore 189 | */ 190 | final class FakeChainedKeyStore(mkLogger: LoggerFactory) { 191 | 192 | import FakeChainedKeyStore._ 193 | 194 | private val logger: NoDepsLogger = mkLogger(getClass) 195 | 196 | /** 197 | * @param appPath a file descriptor to the root folder of the project (the root, not a particular module). 198 | */ 199 | def getKeyStoreFilePath(appPath: File) = new File(appPath, KeystoreSettings.GeneratedKeyStore) 200 | 201 | private[ssl] def shouldGenerate(keyStoreFile: File): Boolean = { 202 | import scala.collection.JavaConverters._ 203 | 204 | if (!keyStoreFile.exists()) { 205 | return true 206 | } 207 | 208 | // Should regenerate if we find an unacceptably weak key in there. 209 | val store = loadKeyStore(keyStoreFile) 210 | store.aliases().asScala.exists { alias => 211 | Option(store.getCertificate(alias)).exists(c => certificateTooWeak(c)) 212 | } 213 | } 214 | 215 | private def loadKeyStore(file: File): KeyStore = { 216 | val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) 217 | val in = java.nio.file.Files.newInputStream(file.toPath) 218 | try { 219 | keyStore.load(in, KeystoreSettings.keystorePassword) 220 | } finally { 221 | closeQuietly(in) 222 | } 223 | keyStore 224 | } 225 | 226 | private[ssl] def certificateTooWeak(c: java.security.cert.Certificate): Boolean = { 227 | val key: RSAPublicKey = c.getPublicKey.asInstanceOf[RSAPublicKey] 228 | key.getModulus.bitLength < 2048 || c.asInstanceOf[X509CertImpl].getSigAlgName != KeystoreSettings.SignatureAlgorithmName 229 | } 230 | 231 | /** Public only for consumption by Play/Lagom. */ 232 | def createKeyStore(appPath: File): KeyStore = { 233 | val keyStoreFile = getKeyStoreFilePath(appPath) 234 | val keyStoreDir = keyStoreFile.getParentFile 235 | 236 | createKeystoreParentDirectory(keyStoreDir) 237 | 238 | val keyStore: KeyStore = synchronized(if (shouldGenerate(keyStoreFile)) { 239 | logger.info(s"Generating HTTPS key pair in ${keyStoreFile.getAbsolutePath} - this may take some time. If nothing happens, try moving the mouse/typing on the keyboard to generate some entropy.") 240 | 241 | val freshKeyStore: KeyStore = generateKeyStore 242 | val out = java.nio.file.Files.newOutputStream(keyStoreFile.toPath) 243 | try { 244 | freshKeyStore.store(out, Array.emptyCharArray) 245 | } finally { 246 | closeQuietly(out) 247 | } 248 | freshKeyStore 249 | } else { 250 | // Load a KeyStore from a file 251 | val loadedKeyStore = loadKeyStore(keyStoreFile) 252 | logger.info(s"HTTPS key pair generated in ${keyStoreFile.getAbsolutePath}.") 253 | loadedKeyStore 254 | }) 255 | keyStore 256 | } 257 | 258 | private def createKeystoreParentDirectory(keyStoreDir: File) = { 259 | if (keyStoreDir.mkdirs()) { 260 | logger.debug(s"Parent directory for keystore successfully created at ${keyStoreDir.getAbsolutePath}") 261 | } else if (keyStoreDir.exists() && keyStoreDir.isDirectory) { 262 | // File.mkdirs returns false when the directory already exists. 263 | logger.debug(s"No need to create $keyStoreDir since it already exists.") 264 | } else if (keyStoreDir.exists() && keyStoreDir.isFile) { 265 | // File.mkdirs also returns false when there is a file for that path. 266 | // A consumer will then fail to write the keystore file later, so we fail fast here. 267 | throw new IllegalStateException(s"$keyStoreDir exists, but it is NOT a directory, making it not possible to generate a key store file.") 268 | } else { 269 | // Not being able to create a directory inside target folder is weird, but if it happens 270 | // a consumer will then fail to write the keystore file later, so we fail fast here. 271 | throw new IllegalStateException(s"Failed to create $keyStoreDir. Check if there is permission to create such folder.") 272 | } 273 | } 274 | 275 | /** Public only for consumption by Play/Lagom. */ 276 | def keyManagerFactory(appPath: File): KeyManagerFactory = { 277 | val keyStore = createKeyStore(appPath) 278 | 279 | // Load the key and certificate into a key manager factory 280 | val kmf = KeyManagerFactory.getInstance("SunX509") 281 | kmf.init(keyStore, Array.emptyCharArray) 282 | kmf 283 | } 284 | 285 | /** 286 | * Close the given closeable quietly. 287 | * 288 | * Logs any IOExceptions encountered. 289 | */ 290 | def closeQuietly(closeable: Closeable) = { 291 | try { 292 | if (closeable != null) { 293 | closeable.close() 294 | } 295 | } catch { 296 | case e: IOException => logger.warn(s"Error closing stream. Cause: $e") 297 | } 298 | } 299 | 300 | } 301 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeKeyStore.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.security.{ KeyPair, KeyPairGenerator, KeyStore, SecureRandom } 8 | 9 | import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger } 10 | import sun.security.x509._ 11 | import sun.security.util.ObjectIdentifier 12 | import java.util.Date 13 | import java.math.BigInteger 14 | import java.security.cert.X509Certificate 15 | import java.io._ 16 | 17 | import javax.net.ssl.KeyManagerFactory 18 | import java.security.interfaces.RSAPublicKey 19 | 20 | /** 21 | * A fake key store with a single, selfsigned certificate and keypair. Includes also a `trustedCertEntry` for 22 | * that certificate. 23 | * 24 | * {{{ 25 | * Your keystore contains 2 entries 26 | * 27 | * sslconfig-selfsigned-trust, Oct 4, 2018, trustedCertEntry, 28 | * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 29 | * sslconfig-selfsigned, Oct 4, 2018, PrivateKeyEntry, 30 | * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 31 | * }}} 32 | * 33 | * Was: play.core.server.ssl.FakeKeyStore 34 | */ 35 | object FakeKeyStore { 36 | 37 | private val EMPTY_PASSWORD = Array.emptyCharArray 38 | 39 | object SelfSigned { 40 | 41 | object Alias { 42 | // These two constants use a weird capitalization but that's what keystore uses internally (see class scaladoc) 43 | val trustedCertEntry = "sslconfig-selfsigned-trust" 44 | val PrivateKeyEntry = "sslconfig-selfsigned" 45 | } 46 | 47 | val DistinguishedName = "CN=localhost, OU=Unit Testing (self-signed), O=Mavericks, L=SSL Config Base 1, ST=Cyberspace, C=CY" 48 | val keyPassword: Array[Char] = EMPTY_PASSWORD 49 | } 50 | 51 | object KeystoreSettings { 52 | val GeneratedKeyStore: String = fileInDevModeDir("selfsigned.keystore") 53 | val SignatureAlgorithmName = "SHA256withRSA" 54 | val KeyPairAlgorithmName = "RSA" 55 | val KeyPairKeyLength = 2048 // 2048 is the NIST acceptable key length until 2030 56 | val KeystoreType = "JKS" 57 | val keystorePassword: Array[Char] = EMPTY_PASSWORD 58 | } 59 | 60 | private def fileInDevModeDir(filename: String): String = { 61 | "target" + File.separatorChar + "dev-mode" + File.separatorChar + filename 62 | } 63 | 64 | /** 65 | * Generate a fresh KeyStore object in memory. This KeyStore 66 | * is not saved to disk. If you want that, then call `keyManagerFactory`. 67 | * 68 | * This method is public only for consumption by Play/Lagom. 69 | */ 70 | def generateKeyStore: KeyStore = { 71 | // Create a new KeyStore 72 | val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) 73 | 74 | // Generate the key pair 75 | val keyPairGenerator = KeyPairGenerator.getInstance(KeystoreSettings.KeyPairAlgorithmName) 76 | keyPairGenerator.initialize(KeystoreSettings.KeyPairKeyLength) 77 | val keyPair = keyPairGenerator.generateKeyPair() 78 | 79 | val cert = createSelfSignedCertificate(keyPair) 80 | 81 | // Create the key store, first set the store pass 82 | keyStore.load(null, KeystoreSettings.keystorePassword) 83 | keyStore.setKeyEntry(SelfSigned.Alias.PrivateKeyEntry, keyPair.getPrivate, SelfSigned.keyPassword, Array(cert)) 84 | keyStore.setCertificateEntry(SelfSigned.Alias.trustedCertEntry, cert) 85 | keyStore 86 | } 87 | 88 | def createSelfSignedCertificate(keyPair: KeyPair): X509Certificate = { 89 | val certInfo = new X509CertInfo() 90 | 91 | // Serial number and version 92 | certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, new SecureRandom()))) 93 | certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) 94 | 95 | // Validity 96 | val validFrom = new Date() 97 | val validTo = new Date(validFrom.getTime + 50l * 365l * 24l * 60l * 60l * 1000l) 98 | val validity = new CertificateValidity(validFrom, validTo) 99 | certInfo.set(X509CertInfo.VALIDITY, validity) 100 | 101 | // Subject and issuer 102 | val owner = new X500Name(SelfSigned.DistinguishedName) 103 | certInfo.set(X509CertInfo.SUBJECT, owner) 104 | certInfo.set(X509CertInfo.ISSUER, owner) 105 | 106 | // Key and algorithm 107 | certInfo.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic)) 108 | val algorithm = AlgorithmId.get("SHA256WithRSA") 109 | certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithm)) 110 | 111 | // Create a new certificate and sign it 112 | val cert = new X509CertImpl(certInfo) 113 | cert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) 114 | 115 | // Since the signature provider may have a different algorithm ID to what we think it should be, 116 | // we need to reset the algorithm ID, and resign the certificate 117 | val actualAlgorithm = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] 118 | certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, actualAlgorithm) 119 | val newCert = new X509CertImpl(certInfo) 120 | newCert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) 121 | newCert 122 | } 123 | 124 | } 125 | 126 | /** 127 | * A fake key store 128 | * 129 | * Was: play.core.server.ssl.FakeKeyStore 130 | */ 131 | final class FakeKeyStore(mkLogger: LoggerFactory) { 132 | 133 | import FakeKeyStore._ 134 | 135 | private val logger: NoDepsLogger = mkLogger(getClass) 136 | 137 | /** 138 | * @param appPath a file descriptor to the root folder of the project (the root, not a particular module). 139 | */ 140 | def getKeyStoreFilePath(appPath: File) = new File(appPath, KeystoreSettings.GeneratedKeyStore) 141 | 142 | private[ssl] def shouldGenerate(keyStoreFile: File): Boolean = { 143 | import scala.collection.JavaConverters._ 144 | 145 | if (!keyStoreFile.exists()) { 146 | return true 147 | } 148 | 149 | // Should regenerate if we find an unacceptably weak key in there. 150 | val store = loadKeyStore(keyStoreFile) 151 | store.aliases().asScala.exists { alias => 152 | Option(store.getCertificate(alias)).exists(c => certificateTooWeak(c)) 153 | } 154 | } 155 | 156 | private def loadKeyStore(file: File): KeyStore = { 157 | val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) 158 | val in = java.nio.file.Files.newInputStream(file.toPath) 159 | try { 160 | keyStore.load(in, "".toCharArray) 161 | } finally { 162 | closeQuietly(in) 163 | } 164 | keyStore 165 | } 166 | 167 | private[ssl] def certificateTooWeak(c: java.security.cert.Certificate): Boolean = { 168 | val key: RSAPublicKey = c.getPublicKey.asInstanceOf[RSAPublicKey] 169 | key.getModulus.bitLength < KeystoreSettings.KeyPairKeyLength || c.asInstanceOf[X509CertImpl].getSigAlgName != KeystoreSettings.SignatureAlgorithmName 170 | } 171 | 172 | /** Public only for consumption by Play/Lagom. */ 173 | def createKeyStore(appPath: File): KeyStore = { 174 | val keyStoreFile = getKeyStoreFilePath(appPath) 175 | val keyStoreDir = keyStoreFile.getParentFile 176 | 177 | createKeystoreParentDirectory(keyStoreDir) 178 | 179 | val keyStore: KeyStore = synchronized(if (shouldGenerate(keyStoreFile)) { 180 | logger.info(s"Generating HTTPS key pair in ${keyStoreFile.getAbsolutePath} - this may take some time. If nothing happens, try moving the mouse/typing on the keyboard to generate some entropy.") 181 | 182 | val freshKeyStore: KeyStore = generateKeyStore 183 | val out = java.nio.file.Files.newOutputStream(keyStoreFile.toPath) 184 | try { 185 | freshKeyStore.store(out, KeystoreSettings.keystorePassword) 186 | } finally { 187 | closeQuietly(out) 188 | } 189 | freshKeyStore 190 | } else { 191 | // Load a KeyStore from a file 192 | val loadedKeyStore = loadKeyStore(keyStoreFile) 193 | logger.info(s"HTTPS key pair generated in ${keyStoreFile.getAbsolutePath}.") 194 | loadedKeyStore 195 | }) 196 | keyStore 197 | } 198 | 199 | private def createKeystoreParentDirectory(keyStoreDir: File): Unit = { 200 | if (keyStoreDir.mkdirs()) { 201 | logger.debug(s"Parent directory for keystore successfully created at ${keyStoreDir.getAbsolutePath}") 202 | } else if (keyStoreDir.exists() && keyStoreDir.isDirectory) { 203 | // File.mkdirs returns false when the directory already exists. 204 | logger.debug(s"No need to create $keyStoreDir since it already exists.") 205 | } else if (keyStoreDir.exists() && keyStoreDir.isFile) { 206 | // File.mkdirs also returns false when there is a file for that path. 207 | // A consumer will then fail to write the keystore file later, so we fail fast here. 208 | throw new IllegalStateException(s"$keyStoreDir exists, but it is NOT a directory, making it not possible to generate a key store file.") 209 | } else { 210 | // Not being able to create a directory inside target folder is weird, but if it happens 211 | // a consumer will then fail to write the keystore file later, so we fail fast here. 212 | throw new IllegalStateException(s"Failed to create $keyStoreDir. Check if there is permission to create such folder.") 213 | } 214 | } 215 | 216 | /** Public only for consumption by Play/Lagom. */ 217 | def keyManagerFactory(appPath: File): KeyManagerFactory = { 218 | val keyStore = createKeyStore(appPath) 219 | 220 | // Load the key and certificate into a key manager factory 221 | val kmf = KeyManagerFactory.getInstance("SunX509") 222 | kmf.init(keyStore, KeystoreSettings.keystorePassword) 223 | kmf 224 | } 225 | 226 | /** 227 | * Close the given closeable quietly. 228 | * 229 | * Logs any IOExceptions encountered. 230 | */ 231 | def closeQuietly(closeable: Closeable): Unit = { 232 | try { 233 | if (closeable != null) { 234 | closeable.close() 235 | } 236 | } catch { 237 | case e: IOException => logger.warn(s"Error closing stream. Cause: $e") 238 | } 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeSSLTools.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.security.KeyStore 8 | 9 | import javax.net.ssl._ 10 | 11 | object FakeSSLTools { 12 | /** 13 | * NOT FOR PRODUCTION USE. Builds a "TLS" `SSLContext` and `X509TrustManager` initializing both with the keys and 14 | * certificates in the provided `KeyStore`. This means the `SSLContext` will produce `SSLEngine`'s, `SSLSocket``s 15 | * and `SSLServerSocket`'s that will use a `KeyPair` from the `KeyStore`, and will trust any `trustedCertEntry` 16 | * in the `KeyStore`. 17 | * 18 | * This is a naïve implementation for testing purposes only. 19 | */ 20 | def buildContextAndTrust(keyStore: KeyStore): (SSLContext, X509TrustManager) = { 21 | val kmf: KeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm) 22 | kmf.init(keyStore, Array.emptyCharArray) 23 | val kms: Array[KeyManager] = kmf.getKeyManagers 24 | 25 | val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm) 26 | tmf.init(keyStore) 27 | val tms: Array[TrustManager] = tmf.getTrustManagers 28 | 29 | val x509TrustManager: X509TrustManager = tms(0).asInstanceOf[X509TrustManager] 30 | 31 | val sslContext: SSLContext = SSLContext.getInstance("TLS") 32 | sslContext.init(kms, tms, null) 33 | 34 | (sslContext, x509TrustManager) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/KeyStore.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.io._ 8 | import java.security.KeyStore 9 | import java.security.cert._ 10 | 11 | trait KeyStoreBuilder { 12 | def build(): KeyStore 13 | } 14 | 15 | object KeystoreFormats { 16 | 17 | def loadCertificates(certs: TraversableOnce[Certificate]): KeyStore = { 18 | val keystore = KeyStore.getInstance(KeyStore.getDefaultType) 19 | keystore.load(null) 20 | certs.foreach { cert => 21 | val alias = cert.getSubjectX500Principal.getName 22 | keystore.setCertificateEntry(alias, cert) 23 | } 24 | keystore 25 | } 26 | 27 | } 28 | 29 | import com.typesafe.sslconfig.ssl.KeystoreFormats._ 30 | 31 | /** 32 | * Builds a keystore from a string containing PEM encoded certificates, using CertificateFactory internally. 33 | * 34 | * @see java.security.cert.CertificateFactory 35 | */ 36 | class StringBasedKeyStoreBuilder(data: String) extends KeyStoreBuilder { 37 | 38 | def build(): KeyStore = { 39 | val certs = readCertificates(data) 40 | val store = loadCertificates(certs) 41 | store 42 | 43 | } 44 | 45 | def readCertificates(certificateString: String): Seq[Certificate] = { 46 | val cf = CertificateFactory.getInstance("X.509") 47 | // CertificateFactory throws EOF on whitespace after end cert, which is very common in triple quoted strings. 48 | val trimmedString = certificateString.trim() 49 | val is = new ByteArrayInputStream(trimmedString.getBytes("UTF-8")) 50 | val bis = new BufferedInputStream(is) 51 | val buffer = new scala.collection.mutable.ListBuffer[Certificate]() 52 | while (bis.available() > 0) { 53 | val cert = cf.generateCertificate(bis) 54 | buffer.append(cert) 55 | } 56 | buffer.toList 57 | } 58 | 59 | } 60 | 61 | /** 62 | * Builds a keystore from a file containing PEM encoded certificates, using CertificateFactory internally. 63 | * 64 | * @see java.security.cert.CertificateFactory 65 | */ 66 | class FileBasedKeyStoreBuilder( 67 | keyStoreType: String, 68 | filePath: String, 69 | password: Option[Array[Char]]) extends KeyStoreBuilder { 70 | 71 | def build(): KeyStore = { 72 | val file = new File(filePath) 73 | 74 | require(file.exists, s"Key store file $filePath does not exist!") 75 | require(file.canRead, s"Cannot read from key store file $filePath!") 76 | 77 | keyStoreType match { 78 | case "PEM" => 79 | val certs = readCertificates(file) 80 | loadCertificates(certs) 81 | case otherFormat => 82 | buildFromKeystoreFile(otherFormat, file) 83 | } 84 | } 85 | 86 | def buildFromKeystoreFile(storeType: String, file: File): KeyStore = { 87 | val inputStream = new BufferedInputStream(java.nio.file.Files.newInputStream(file.toPath)) 88 | try { 89 | val storeType = keyStoreType 90 | val store = KeyStore.getInstance(storeType) 91 | store.load(inputStream, password.orNull) 92 | store 93 | } finally { 94 | inputStream.close() 95 | } 96 | } 97 | 98 | def readCertificates(file: File): Iterable[Certificate] = { 99 | import scala.collection.JavaConverters._ 100 | val cf = CertificateFactory.getInstance("X.509") 101 | val fis = java.nio.file.Files.newInputStream(file.toPath) 102 | val bis = new BufferedInputStream(fis) 103 | 104 | try cf.generateCertificates(bis).asScala finally bis.close() 105 | } 106 | 107 | } 108 | 109 | class FileOnClasspathBasedKeyStoreBuilder( 110 | keyStoreType: String, 111 | filePath: String, 112 | password: Option[Array[Char]]) extends KeyStoreBuilder { 113 | 114 | def build(): KeyStore = { 115 | 116 | val is = getClass.getClassLoader.getResourceAsStream(filePath) 117 | require(is != null, s"Key store file $filePath was not found on the class path!") 118 | 119 | keyStoreType match { 120 | case "PEM" => 121 | val certs = readCertificates(is) 122 | loadCertificates(certs) 123 | case otherFormat => 124 | buildFromKeystoreFile(otherFormat, is) 125 | } 126 | 127 | } 128 | 129 | def buildFromKeystoreFile(storeType: String, is: InputStream): KeyStore = { 130 | val inputStream = new BufferedInputStream(is) 131 | try { 132 | val storeType = keyStoreType 133 | val store = KeyStore.getInstance(storeType) 134 | store.load(inputStream, password.orNull) 135 | store 136 | } finally { 137 | inputStream.close() 138 | } 139 | } 140 | 141 | def readCertificates(is: InputStream): Iterable[Certificate] = { 142 | import scala.collection.JavaConverters._ 143 | val cf = CertificateFactory.getInstance("X.509") 144 | val bis = new BufferedInputStream(is) 145 | 146 | try cf.generateCertificates(bis).asScala finally bis.close() 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/MonkeyPatcher.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.lang.reflect.Field 8 | 9 | @deprecated("MonkeyPatcher has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 10 | trait MonkeyPatcher { 11 | def monkeyPatchField(field: Field, newObject: AnyRef): Unit = () 12 | } 13 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/NoopHostnameVerifier.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import javax.net.ssl.{ HostnameVerifier, SSLSession } 8 | 9 | final class NoopHostnameVerifier extends HostnameVerifier { 10 | def verify(hostname: String, sslSession: SSLSession): Boolean = true 11 | } 12 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/Protocols.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | object Protocols { 8 | 9 | /** 10 | * Protocols which are known to be insecure. 11 | */ 12 | val deprecatedProtocols = Set("SSL", "SSLv2Hello", "SSLv3") 13 | 14 | val recommendedProtocols = Array("TLSv1.2", "TLSv1.1", "TLSv1") 15 | 16 | // Use 1.2 as a default in 1.7 17 | // https://docs.fedoraproject.org/en-US/Fedora_Security_Team//html/Defensive_Coding/sect-Defensive_Coding-TLS-Client-OpenJDK.html 18 | def recommendedProtocol = "TLSv1.2" 19 | 20 | } 21 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/SystemConfiguration.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.security.Security 8 | 9 | import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger } 10 | 11 | /** 12 | * Configures global system properties on the JSSE implementation, if defined. 13 | * 14 | * WARNING: This class sets system properties to configure JSSE code which typically uses static initialization on 15 | * load. Because of this, if classes are loaded in BEFORE this code has a chance to operate, you may find that this 16 | * code works inconsistently. The solution is to set the system properties on the command line explicitly (or in the 17 | * case of "ocsp.enable", in the security property file). 18 | */ 19 | class SystemConfiguration(mkLogger: LoggerFactory) { 20 | 21 | val logger = mkLogger(getClass) 22 | 23 | def configure(config: SSLConfigSettings): Unit = { 24 | config.loose.allowUnsafeRenegotiation.foreach(configureUnsafeRenegotiation) 25 | config.loose.allowLegacyHelloMessages.foreach(configureAllowLegacyHelloMessages) 26 | config.checkRevocation.foreach(configureCheckRevocation) 27 | } 28 | 29 | def configureUnsafeRenegotiation(allowUnsafeRenegotiation: Boolean): Unit = { 30 | System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", allowUnsafeRenegotiation.toString) 31 | logger.debug(s"configureUnsafeRenegotiation: sun.security.ssl.allowUnsafeRenegotiation = ${allowUnsafeRenegotiation.toString}") 32 | } 33 | 34 | def configureAllowLegacyHelloMessages(allowLegacyHelloMessages: Boolean): Unit = { 35 | System.setProperty("sun.security.ssl.allowLegacyHelloMessages", allowLegacyHelloMessages.toString) 36 | logger.debug(s"configureAllowLegacyHelloMessages: sun.security.ssl.allowLegacyHelloMessages = ${allowLegacyHelloMessages.toString}") 37 | } 38 | 39 | def configureCheckRevocation(checkRevocation: Boolean): Unit = { 40 | // http://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html#AppC 41 | // https://blogs.oracle.com/xuelei/entry/enable_ocsp_checking 42 | 43 | // 1.7: PXIXCertPathValidator.populateVariables, it is dynamic so no override needed. 44 | Security.setProperty("ocsp.enable", checkRevocation.toString) 45 | logger.debug(s"configureCheckRevocation: ocsp.enable = ${checkRevocation.toString}") 46 | System.setProperty("com.sun.security.enableCRLDP", checkRevocation.toString) 47 | logger.debug(s"configureCheckRevocation: com.sun.security.enableCRLDP = ${checkRevocation.toString}") 48 | System.setProperty("com.sun.net.ssl.checkRevocation", checkRevocation.toString) 49 | } 50 | 51 | /** 52 | * For use in testing. 53 | */ 54 | def clearProperties(): Unit = { 55 | Security.setProperty("ocsp.enable", "false") 56 | System.clearProperty("com.sun.security.enableCRLDP") 57 | System.clearProperty("com.sun.net.ssl.checkRevocation") 58 | 59 | System.clearProperty("sun.security.ssl.allowLegacyHelloMessages") 60 | System.clearProperty("sun.security.ssl.allowUnsafeRenegotiation") 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/debug/ClassFinder.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.debug 6 | 7 | import com.typesafe.sslconfig.util.NoDepsLogger 8 | 9 | @deprecated("ClassFinder has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 10 | trait ClassFinder { 11 | def logger: NoDepsLogger 12 | def initialResource: String 13 | def isValidClass(className: String): Boolean 14 | def findClasses: Set[Class[_]] = Set.empty 15 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/debug/DebugConfiguration.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.debug 6 | 7 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 8 | import com.typesafe.sslconfig.util.LoggerFactory 9 | 10 | @deprecated("DebugConfiguration has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 11 | class DebugConfiguration(mkLogger: LoggerFactory) { 12 | 13 | private val logger = mkLogger(getClass) 14 | 15 | def configure(d: SSLDebugConfig): Unit = { 16 | logger.warn("DebugConfiguration has been deprecated and does nothing. Please use the javax.net.debug system property.") 17 | } 18 | 19 | def configureJavaxNetDebug(d: SSLDebugConfig): Unit = { 20 | logger.warn("DebugConfiguration has been deprecated and does nothing. Please use the javax.net.debug system property.") 21 | } 22 | 23 | def configureJavaSecurityDebug(d: SSLDebugConfig): Unit = { 24 | logger.warn("DebugConfiguration has been deprecated and does nothing. Please use the javax.net.debug system property.") 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/debug/FixCertpathDebugLogging.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.debug 6 | 7 | import java.security.AccessController 8 | import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger } 9 | 10 | import scala.util.control.NonFatal 11 | import sun.security.util.Debug 12 | 13 | @deprecated("FixCertpathDebugLogging has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 14 | class FixCertpathDebugLogging(mkLogger: LoggerFactory) { 15 | val logger = mkLogger("com.typesafe.sslconfig.ssl.debug.FixCertpathDebugLogging") 16 | 17 | @deprecated("MonkeyPatchSunSecurityUtilDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 18 | class MonkeyPatchSunSecurityUtilDebugAction(val newDebug: Debug, val newOptions: String) extends FixLoggingAction { 19 | val logger = mkLogger("com.typesafe.sslconfig.ssl.debug.FixCertpathDebugLogging.MonkeyPatchSunSecurityUtilDebugAction") 20 | 21 | val initialResource = "/sun/security/provider/certpath/Builder.class" 22 | 23 | val debugType = classOf[Debug] 24 | 25 | def isValidClass(className: String): Boolean = { 26 | logger.warn("MonkeyPatchSunSecurityUtilDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 27 | if (className.startsWith("java.security.cert")) return true 28 | if (className.startsWith("sun.security.provider.certpath")) return true 29 | if (className.equals("sun.security.x509.InhibitAnyPolicyExtension")) return true 30 | false 31 | } 32 | 33 | def isUsingDebug: Boolean = { 34 | logger.warn("MonkeyPatchSunSecurityUtilDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 35 | (newOptions != null) && newOptions.contains("certpath") 36 | } 37 | 38 | def run(): Unit = { 39 | logger.warn("MonkeyPatchSunSecurityUtilDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 40 | } 41 | } 42 | 43 | @deprecated("SunSecurityUtilDebugLogger has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 44 | class SunSecurityUtilDebugLogger(logger: NoDepsLogger) extends sun.security.util.Debug { 45 | override def println(message: String): Unit = () 46 | override def println(): Unit = () 47 | } 48 | 49 | def apply(newOptions: String, debugOption: Option[Debug] = None): Unit = { 50 | logger.warn("FixCertpathDebugLogging has been deprecated and does nothing. Please use the javax.net.debug system property.") 51 | } 52 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/debug/FixInternalDebugLogging.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.debug 6 | 7 | import com.typesafe.sslconfig.util.LoggerFactory 8 | 9 | @deprecated("FixInternalDebugLogging has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 10 | class FixInternalDebugLogging(mkLogger: LoggerFactory) { 11 | private val logger = mkLogger("com.typesafe.sslconfig.ssl.debug.FixInternalDebugLogging") 12 | 13 | @deprecated("MonkeyPatchInternalSslDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 14 | class MonkeyPatchInternalSslDebugAction(val newOptions: String) extends FixLoggingAction { 15 | override val logger = mkLogger("com.typesafe.sslconfig.ssl.debug.FixInternalDebugLogging.MonkeyPatchInternalSslDebugAction") 16 | 17 | val initialResource = "/sun/security/ssl/Debug.class" 18 | val debugClassName = "sun.security.ssl.Debug" 19 | 20 | def isValidClass(className: String): Boolean = { 21 | logger.warn("MonkeyPatchInternalSslDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 22 | if (className.startsWith("com.sun.net.ssl.internal.ssl")) return true 23 | if (className.startsWith("sun.security.ssl")) return true 24 | false 25 | } 26 | 27 | def isUsingDebug: Boolean = { 28 | logger.warn("MonkeyPatchInternalSslDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 29 | (newOptions != null) && (!newOptions.isEmpty) 30 | } 31 | 32 | def run(): Unit = { 33 | logger.warn("MonkeyPatchInternalSslDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 34 | } 35 | 36 | def apply(newOptions: String): Unit = { 37 | logger.warn("MonkeyPatchInternalSslDebugAction has been deprecated and does nothing. Please use the javax.net.debug system property.") 38 | } 39 | } 40 | 41 | def apply(newOptions: String): Unit = { 42 | logger.warn("FixInternalDebugLogging has been deprecated and does nothing. Please use the javax.net.debug system property.") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/debug/FixLoggingAction.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.debug 6 | 7 | import java.lang.reflect.Field 8 | import java.security.PrivilegedExceptionAction 9 | import com.typesafe.sslconfig.ssl.MonkeyPatcher 10 | 11 | @deprecated("FixLoggingAction has been deprecated and does nothing. Please use the javax.net.debug system property.", "0.4.0") 12 | abstract class FixLoggingAction extends PrivilegedExceptionAction[Unit] with MonkeyPatcher with ClassFinder { 13 | def newOptions: String 14 | 15 | def isValidField(field: Field, definedType: Class[_]): Boolean = { 16 | logger.warn("DebugConfiguration has been deprecated and does nothing. Please use the javax.net.debug system property.") 17 | false 18 | } 19 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig 6 | 7 | import java.security.cert.{ CertPathValidatorResult, Certificate, PKIXCertPathValidatorResult, X509Certificate } 8 | 9 | import scala.util.Properties.javaVmName 10 | 11 | package object ssl { 12 | 13 | import scala.language.implicitConversions 14 | 15 | implicit def certificate2X509Certificate(cert: java.security.cert.Certificate): X509Certificate = { 16 | cert.asInstanceOf[X509Certificate] 17 | } 18 | 19 | implicit def arrayCertsToListCerts(chain: Array[Certificate]): java.util.List[Certificate] = { 20 | import scala.collection.JavaConverters._ 21 | chain.toList.asJava 22 | } 23 | 24 | implicit def certResult2PKIXResult(result: CertPathValidatorResult): PKIXCertPathValidatorResult = { 25 | result.asInstanceOf[PKIXCertPathValidatorResult] 26 | } 27 | 28 | def debugChain(chain: Array[X509Certificate]): Seq[String] = { 29 | chain.map(debugCert) 30 | } 31 | 32 | private[sslconfig] def debugCert(cert: X509Certificate): String = { 33 | s"X509Certificate(serialNumber = ${cert.getSerialNumber.toString(16)}, subject = ${cert.getSubjectDN.getName})" 34 | } 35 | 36 | def isOpenJdk: Boolean = javaVmName contains "OpenJDK" 37 | 38 | } 39 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TraceLogger.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.security.Principal 8 | import java.security.cert.X509Certificate 9 | 10 | import com.typesafe.sslconfig.ssl 11 | import com.typesafe.sslconfig.util.{ LoggerFactory, NoDepsLogger } 12 | 13 | import scala.util.control.NonFatal 14 | 15 | private[sslconfig] trait TraceLogger { 16 | 17 | def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean 18 | 19 | def tracer[T, E <: AnyRef](methodName: String, parameters: Map[String, Any], function: () => T)(implicit loggerFactory: LoggerFactory): T = { 20 | val logger = loggerFactory(getClass) 21 | val methodParams = parameters.mapValues(mapValue).mkString(",") 22 | val enabled = isLogEnabled(methodName, parameters) 23 | if (enabled) { 24 | entry(logger, methodName, methodParams) 25 | } 26 | try { 27 | val result = function() 28 | if (enabled) { 29 | exit(logger, result, methodName, methodParams) 30 | } 31 | result 32 | } catch { 33 | case NonFatal(e) => 34 | if (enabled) { 35 | exception(logger, e, methodName, methodParams) 36 | } 37 | throw e 38 | } 39 | } 40 | 41 | private def mapValue(value: Any): String = { 42 | value match { 43 | case v: Array[X509Certificate] => 44 | s"Array(${ssl.debugChain(v).mkString(", ")})" 45 | case v: Array[Principal] => 46 | s"Array(${v.mkString(", ")})" 47 | case v: Array[_] => 48 | s"Array(${v.mkString(", ")})" 49 | case v: Any => 50 | v.toString 51 | case null => 52 | null 53 | } 54 | } 55 | 56 | private def entry(logger: NoDepsLogger, methodName: String, methodParams: String): Unit = { 57 | logger.warn(s"entry: $methodName($methodParams)") 58 | } 59 | 60 | private def exit[R](logger: NoDepsLogger, result: R, methodName: String, methodParams: String): R = { 61 | logger.warn(s"exit: $methodName($methodParams) = ${mapValue(result)}") 62 | result 63 | } 64 | 65 | private def exit(logger: NoDepsLogger, methodName: String, methodParams: String): Unit = { 66 | logger.warn(s"exit: $methodName($methodParams)") 67 | } 68 | 69 | private def exception(logger: NoDepsLogger, e: Throwable, methodName: String, methodParams: String): Unit = { 70 | logger.error(s"exception: $methodName($methodParams)", e) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TracingSSLContextSpi.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.security._ 8 | 9 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 10 | import com.typesafe.sslconfig.util.LoggerFactory 11 | import javax.net.ssl._ 12 | 13 | private[sslconfig] class TracingSSLContext(context: SSLContext, debugConfig: SSLDebugConfig)(implicit loggerFactory: LoggerFactory) extends SSLContext( 14 | new TracingSSLContextSpi(context, debugConfig), context.getProvider, context.getProtocol 15 | ) 16 | 17 | private[tracing] class TracingSSLContextSpi(ctx: => SSLContext, debug: SSLDebugConfig)(implicit loggerFactory: LoggerFactory) extends SSLContextSpi with TraceLogger { 18 | 19 | def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean = debug.all || debug.sslctx 20 | 21 | override def engineInit(keyManagers: Array[KeyManager], trustManagers: Array[TrustManager], secureRandom: SecureRandom): Unit = { 22 | tracer("init", Map("keyManagers" -> keyManagers, "trustManagers" -> trustManagers, "secureRandom" -> secureRandom), 23 | () => ctx.init(keyManagers, trustManagers, secureRandom)) 24 | } 25 | 26 | override def engineGetSocketFactory(): SSLSocketFactory = { 27 | tracer("getSocketFactory", Map(), () => new TracingSSLSocketFactory(ctx.getSocketFactory, debug)) 28 | } 29 | 30 | override def engineGetServerSocketFactory(): SSLServerSocketFactory = { 31 | tracer("getServerSocketFactory", Map(), () => new TracingSSLServerSocketFactory(ctx.getServerSocketFactory, debug)) 32 | } 33 | 34 | override def engineCreateSSLEngine(): SSLEngine = { 35 | tracer("createSSLEngine", Map(), () => { 36 | val engine = ctx.createSSLEngine() 37 | new TracingSSLEngine(engine, debug) 38 | }) 39 | } 40 | 41 | override def engineCreateSSLEngine(host: String, port: Int): SSLEngine = { 42 | tracer("createSSLEngine", Map("host" -> host, "port" -> port), () => { 43 | val engine = ctx.createSSLEngine(host, port) 44 | new TracingSSLEngine(engine, debug) 45 | }) 46 | } 47 | 48 | override def engineGetServerSessionContext(): SSLSessionContext = { 49 | tracer("getServerSessionContext", Map(), () => ctx.getServerSessionContext) 50 | } 51 | 52 | override def engineGetClientSessionContext(): SSLSessionContext = { 53 | tracer("getClientSessionContext", Map(), () => ctx.getClientSessionContext) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TracingSSLEngine.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.nio.ByteBuffer 8 | 9 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 10 | import com.typesafe.sslconfig.util.LoggerFactory 11 | import javax.net.ssl.{ SSLEngine, SSLEngineResult, SSLSession } 12 | 13 | private[sslconfig] class TracingSSLEngine(engine: => SSLEngine, debug: SSLDebugConfig)(implicit loggerFactory: LoggerFactory) extends SSLEngine with TraceLogger { 14 | 15 | override def wrap(srcs: Array[ByteBuffer], offset: Int, length: Int, dst: ByteBuffer): SSLEngineResult = { 16 | tracer("wrap", Map("srcs" -> srcs, "offset" -> offset, "length" -> length, "dst" -> dst), () => engine.wrap(srcs, offset, length, dst)) 17 | } 18 | 19 | override def unwrap(src: ByteBuffer, dsts: Array[ByteBuffer], offset: Int, length: Int): SSLEngineResult = { 20 | tracer("wrap", Map("src" -> src, "dsts" -> dsts, "offset" -> offset, "length" -> length), () => engine.unwrap(src, dsts, offset, length)) 21 | } 22 | 23 | override def getDelegatedTask: Runnable = { 24 | tracer("getDelegatedTask", Map(), () => engine.getDelegatedTask) 25 | } 26 | 27 | override def closeInbound(): Unit = { 28 | tracer("closeInbound", Map(), () => engine.closeInbound()) 29 | } 30 | 31 | override def isInboundDone: Boolean = { 32 | tracer("isInboundDone", Map(), () => engine.isInboundDone) 33 | } 34 | 35 | override def closeOutbound(): Unit = { 36 | tracer("isInboundDone", Map(), () => engine.closeOutbound()) 37 | } 38 | 39 | override def isOutboundDone: Boolean = { 40 | tracer("isOutboundDone", Map(), () => engine.isOutboundDone) 41 | } 42 | 43 | override def getSupportedCipherSuites: Array[String] = { 44 | tracer("getSupportedCipherSuites", Map(), () => engine.getSupportedCipherSuites) 45 | } 46 | 47 | override def getEnabledCipherSuites: Array[String] = { 48 | tracer("getEnabledCipherSuites", Map(), () => engine.getEnabledCipherSuites) 49 | } 50 | 51 | override def setEnabledCipherSuites(suites: Array[String]): Unit = { 52 | tracer("setEnabledCipherSuites", Map("suites" -> suites), () => engine.setEnabledCipherSuites(suites)) 53 | } 54 | 55 | override def getSupportedProtocols: Array[String] = { 56 | tracer("getSupportedProtocols", Map(), () => engine.getSupportedProtocols) 57 | } 58 | 59 | override def getEnabledProtocols: Array[String] = { 60 | tracer("getEnabledProtocols", Map(), () => engine.getEnabledProtocols) 61 | } 62 | 63 | override def setEnabledProtocols(protocols: Array[String]): Unit = { 64 | tracer("setEnabledProtocols", Map("protocols" -> protocols), () => engine.setEnabledProtocols(protocols)) 65 | } 66 | 67 | override def getSession: SSLSession = { 68 | tracer("getSession", Map(), () => engine.getSession) 69 | } 70 | 71 | override def beginHandshake(): Unit = { 72 | tracer("beginHandshake", Map(), () => engine.beginHandshake()) 73 | } 74 | 75 | override def getHandshakeStatus: SSLEngineResult.HandshakeStatus = { 76 | tracer("getHandshakeStatus", Map(), () => engine.getHandshakeStatus) 77 | } 78 | 79 | override def setUseClientMode(clientMode: Boolean): Unit = { 80 | tracer("setUseClientMode", Map("clientMode" -> clientMode), () => engine.setUseClientMode(clientMode)) 81 | } 82 | 83 | override def getUseClientMode: Boolean = { 84 | tracer("getUseClientMode", Map(), () => engine.getUseClientMode) 85 | } 86 | 87 | override def setNeedClientAuth(needClientAuth: Boolean): Unit = { 88 | tracer("setNeedClientAuth", Map("needClientAuth" -> needClientAuth), () => engine.setNeedClientAuth(needClientAuth)) 89 | } 90 | 91 | override def getNeedClientAuth: Boolean = { 92 | tracer("getNeedClientAuth", Map(), () => engine.getNeedClientAuth) 93 | } 94 | 95 | override def setWantClientAuth(wantClientAuth: Boolean): Unit = { 96 | tracer("setWantClientAuth", Map("wantClientAuth" -> wantClientAuth), () => engine.setWantClientAuth(wantClientAuth)) 97 | } 98 | 99 | override def getWantClientAuth: Boolean = { 100 | tracer("getWantClientAuth", Map(), () => engine.getWantClientAuth) 101 | } 102 | 103 | override def setEnableSessionCreation(enableSessionCreation: Boolean): Unit = { 104 | tracer("setEnableSessionCreation", Map("enableSessionCreation" -> enableSessionCreation), () => engine.setEnableSessionCreation(enableSessionCreation)) 105 | } 106 | 107 | override def getEnableSessionCreation: Boolean = { 108 | tracer("getEnableSessionCreation", Map(), () => engine.getEnableSessionCreation()) 109 | } 110 | 111 | override def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean = debug.all || debug.ssl 112 | } 113 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TracingSSLServerSocketFactory.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.net.{ InetAddress, ServerSocket } 8 | 9 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 10 | import com.typesafe.sslconfig.util.LoggerFactory 11 | import javax.net.ssl.SSLServerSocketFactory 12 | 13 | private[sslconfig] class TracingSSLServerSocketFactory(factory: => SSLServerSocketFactory, debug: SSLDebugConfig)(implicit lf: LoggerFactory) extends SSLServerSocketFactory with TraceLogger { 14 | 15 | override def createServerSocket = { 16 | tracer("createServerSocket", Map(), () => factory.createServerSocket()) 17 | } 18 | 19 | override def createServerSocket(port: Int): ServerSocket = { 20 | tracer("createServerSocket", Map("port" -> port), () => factory.createServerSocket(port)) 21 | } 22 | 23 | override def createServerSocket(port: Int, backlog: Int): ServerSocket = { 24 | tracer("createServerSocket", Map("port" -> port, "backlog" -> backlog), () => factory.createServerSocket(port, backlog)) 25 | } 26 | 27 | override def createServerSocket(port: Int, backlog: Int, ifAddress: InetAddress): ServerSocket = { 28 | tracer("createServerSocket", Map("port" -> port, "backlog" -> backlog, "ifAddress" -> ifAddress), () => factory.createServerSocket(port, backlog, ifAddress)) 29 | } 30 | 31 | override def getDefaultCipherSuites: Array[String] = { 32 | tracer("getDefaultCipherSuites", Map(), () => factory.getDefaultCipherSuites) 33 | } 34 | 35 | override def getSupportedCipherSuites: Array[String] = { 36 | tracer("getSupportedCipherSuites", Map(), () => factory.getSupportedCipherSuites) 37 | } 38 | 39 | override def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean = debug.all || debug.ssl 40 | } 41 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TracingSSLSocketFactory.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.io.InputStream 8 | import java.net.{ InetAddress, Socket } 9 | 10 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 11 | import com.typesafe.sslconfig.util.LoggerFactory 12 | import javax.net.ssl.SSLSocketFactory 13 | 14 | private[sslconfig] class TracingSSLSocketFactory(factory: SSLSocketFactory, debug: SSLDebugConfig)(implicit loggerFactory: LoggerFactory) extends SSLSocketFactory with TraceLogger { 15 | override def createSocket(host: String, port: Int): Socket = { 16 | tracer("createSocket", Map("host" -> host, "port" -> port), () => factory.createSocket(host, port)) 17 | } 18 | 19 | override def createSocket(): Socket = { 20 | tracer("createSocket", Map(), () => factory.createSocket()) 21 | } 22 | 23 | override def createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket = { 24 | tracer("createSocket", Map("s" -> s, "host" -> host, "port" -> port, "autoClose" -> autoClose), () => factory.createSocket(s, host, port, autoClose)) 25 | } 26 | 27 | override def createSocket(s: Socket, consumed: InputStream, autoClose: Boolean): Socket = { 28 | tracer("createSocket", Map("s" -> s, "consumed" -> consumed, "autoClose" -> autoClose), () => factory.createSocket(s, consumed, autoClose)) 29 | } 30 | 31 | override def createSocket(host: String, port: Int, localHost: InetAddress, localPort: Int): Socket = { 32 | tracer("createSocket", Map("host" -> host, "port" -> port, "localHost" -> localHost, "localPort" -> localPort), 33 | () => factory.createSocket(host, port, localHost, localPort)) 34 | } 35 | 36 | override def createSocket(host: InetAddress, port: Int): Socket = { 37 | tracer("createSocket", Map("host" -> host, "port" -> port), () => factory.createSocket(host, port)) 38 | } 39 | 40 | override def createSocket(address: InetAddress, port: Int, localAddress: InetAddress, localPort: Int): Socket = { 41 | tracer("createSocket", Map("address" -> address, "port" -> port, "localAddress" -> localAddress, "localPort" -> localPort), 42 | () => factory.createSocket(address, port, localAddress, localPort)) 43 | } 44 | 45 | override def getDefaultCipherSuites: Array[String] = { 46 | tracer("getDefaultCipherSuites", Map(), () => factory.getDefaultCipherSuites) 47 | } 48 | 49 | override def getSupportedCipherSuites: Array[String] = { 50 | tracer("getSupportedCipherSuites", Map(), () => factory.getSupportedCipherSuites) 51 | } 52 | 53 | override def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean = debug.all || debug.ssl 54 | } 55 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TracingX509ExtendedKeyManager.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.net.Socket 8 | import java.security.cert.X509Certificate 9 | import java.security.{ Principal, PrivateKey } 10 | 11 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 12 | import com.typesafe.sslconfig.util.LoggerFactory 13 | import javax.net.ssl.{ KeyManager, SSLEngine, X509ExtendedKeyManager } 14 | 15 | private[sslconfig] class TracingX509ExtendedKeyManager(supplier: => X509ExtendedKeyManager, debug: SSLDebugConfig)(implicit loggerFactory: LoggerFactory) extends X509ExtendedKeyManager with TraceLogger { 16 | 17 | override def chooseEngineClientAlias(keyTypes: Array[String], issuers: Array[Principal], engine: SSLEngine): String = { 18 | tracer("chooseEngineClientAlias", Map("keyTypes" -> keyTypes, "issuers" -> issuers, "engine" -> engine), 19 | () => supplier.chooseEngineClientAlias(keyTypes, issuers, engine)) 20 | } 21 | 22 | override def chooseEngineServerAlias(keyTypes: String, issuers: Array[Principal], engine: SSLEngine): String = { 23 | tracer("chooseEngineServerAlias", Map("keyTypes" -> keyTypes, "issuers" -> issuers, "engine" -> engine), () => supplier.chooseEngineServerAlias(keyTypes, issuers, engine)) 24 | } 25 | 26 | override def getClientAliases(keyType: String, issuers: Array[Principal]): Array[String] = { 27 | tracer("getClientAliases", Map("keyType" -> keyType, "issuers" -> issuers), () => supplier.getClientAliases(keyType, issuers)) 28 | } 29 | 30 | override def chooseClientAlias(keyTypes: Array[String], issuers: Array[Principal], socket: Socket): String = { 31 | tracer("chooseClientAlias", Map("keyTypes" -> keyTypes, "issuers" -> issuers, "socket" -> socket), () => supplier.chooseClientAlias(keyTypes, issuers, socket)) 32 | } 33 | 34 | override def getServerAliases(keyType: String, issuers: Array[Principal]): Array[String] = { 35 | tracer("getServerAliases", Map("keyType" -> keyType, "issuers" -> issuers), () => supplier.getServerAliases(keyType, issuers)) 36 | } 37 | 38 | override def chooseServerAlias(keyType: String, issuers: Array[Principal], socket: Socket): String = { 39 | tracer("chooseServerAlias", Map("keyType" -> keyType, "issuers" -> issuers, "socket" -> socket), () => supplier.chooseServerAlias(keyType, issuers, socket)) 40 | } 41 | 42 | override def getCertificateChain(alias: String): Array[X509Certificate] = { 43 | tracer("getCertificateChain", Map("alias" -> alias), () => supplier.getCertificateChain(alias)) 44 | } 45 | 46 | override def getPrivateKey(alias: String): PrivateKey = { 47 | tracer("getPrivateKey", Map("alias" -> alias), () => supplier.getPrivateKey(alias)) 48 | } 49 | 50 | override def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean = debug.all || debug.keymanager 51 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/tracing/TracingX509ExtendedTrustManager.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl.tracing 6 | 7 | import java.net.Socket 8 | import java.security.cert.{ CertificateException, X509Certificate } 9 | 10 | import com.typesafe.sslconfig.ssl.SSLDebugConfig 11 | import com.typesafe.sslconfig.util.LoggerFactory 12 | import javax.net.ssl.{ SSLEngine, X509ExtendedTrustManager } 13 | 14 | private[sslconfig] class TracingX509ExtendedTrustManager(supplier: => X509ExtendedTrustManager, debug: SSLDebugConfig)(implicit loggerFactory: LoggerFactory) extends X509ExtendedTrustManager with TraceLogger { 15 | @throws[CertificateException] 16 | override def checkClientTrusted(chain: Array[X509Certificate], authType: String, socket: Socket): Unit = { 17 | tracer("checkClientTrusted", Map("chain" -> chain, "authType" -> authType, "socket" -> socket), () => supplier.checkClientTrusted(chain, authType, socket)) 18 | } 19 | 20 | @throws[CertificateException] 21 | override def checkServerTrusted(chain: Array[X509Certificate], authType: String, socket: Socket): Unit = { 22 | tracer("checkServerTrusted", Map("chain" -> chain, "authType" -> authType, "socket" -> socket), () => supplier.checkServerTrusted(chain, authType, socket)) 23 | } 24 | 25 | @throws[CertificateException] 26 | override def checkClientTrusted(chain: Array[X509Certificate], authType: String, engine: SSLEngine): Unit = { 27 | tracer("checkClientTrusted", Map("chain" -> chain, "authType" -> authType, "engine" -> engine), () => supplier.checkClientTrusted(chain, authType, engine)) 28 | } 29 | 30 | @throws[CertificateException] 31 | override def checkServerTrusted(chain: Array[X509Certificate], authType: String, engine: SSLEngine): Unit = { 32 | tracer("checkServerTrusted", Map("chain" -> chain, "authType" -> authType, "engine" -> engine), () => supplier.checkServerTrusted(chain, authType, engine)) 33 | } 34 | 35 | @throws[CertificateException] 36 | override def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = { 37 | tracer("checkClientTrusted", Map("chain" -> chain, "authType" -> authType), () => supplier.checkClientTrusted(chain, authType)) 38 | } 39 | 40 | @throws[CertificateException] 41 | override def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = { 42 | tracer("checkServerTrusted", Map("chain" -> chain, "authType" -> authType), () => supplier.checkServerTrusted(chain, authType)) 43 | } 44 | 45 | override def getAcceptedIssuers: Array[X509Certificate] = { 46 | tracer("getAcceptedIssuers", Map(), () => supplier.getAcceptedIssuers) 47 | } 48 | 49 | override def isLogEnabled(methodName: String, parameters: Map[String, Any]): Boolean = debug.all || debug.trustmanager 50 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/util/Configuration.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.util 6 | 7 | import java.util.concurrent.TimeUnit 8 | 9 | import com.typesafe.config._ 10 | 11 | import scala.collection.JavaConverters._ 12 | import scala.collection.immutable 13 | import scala.concurrent.duration.FiniteDuration 14 | 15 | /** Based on PlayConfig, adds some helper methods over underlying Config. */ 16 | class EnrichedConfig(val underlying: Config) { 17 | 18 | /** 19 | * Get the config at the given path. 20 | */ 21 | def get[A](path: String)(implicit loader: ConfigLoader[A]): A = { 22 | loader.load(underlying, path) 23 | } 24 | 25 | def getSeq[A](path: String)(implicit loader: ConfigLoader[immutable.Seq[A]]): immutable.Seq[A] = { 26 | loader.load(underlying, path) 27 | } 28 | 29 | /** 30 | * Get an optional configuration item. 31 | * 32 | * If the value of the item is null, this will return None, otherwise returns Some. 33 | * 34 | * @throws com.typesafe.config.ConfigException.Missing if the value is undefined (as opposed to null) this will still 35 | * throw an exception. 36 | */ 37 | def getOptional[A: ConfigLoader](path: String): Option[A] = { 38 | try { 39 | Option(get(path)) 40 | } catch { 41 | case e: ConfigException.Missing => None 42 | } 43 | } 44 | 45 | /** 46 | * Get a prototyped sequence of objects. 47 | * 48 | * Each object in the sequence will fallback to the object loaded from prototype.$path. 49 | */ 50 | def getPrototypedSeq(path: String, prototypePath: String = "prototype.$path"): immutable.Seq[EnrichedConfig] = { 51 | val prototype = underlying.getConfig(prototypePath.replace("$path", path)) 52 | get[Seq[Config]](path).map { config => 53 | new EnrichedConfig(config.withFallback(prototype)) 54 | }.toList 55 | } 56 | 57 | /** 58 | * Get a prototyped map of objects. 59 | * 60 | * Each value in the map will fallback to the object loaded from prototype.$path. 61 | */ 62 | def getPrototypedMap(path: String, prototypePath: String = "prototype.$path"): Map[String, EnrichedConfig] = { 63 | val prototype = if (prototypePath.isEmpty) { 64 | underlying 65 | } else { 66 | underlying.getConfig(prototypePath.replace("$path", path)) 67 | } 68 | get[Map[String, Config]](path).map { 69 | case (key, config) => key -> new EnrichedConfig(config.withFallback(prototype)) 70 | }.toMap 71 | } 72 | 73 | /** 74 | * Get an optional deprecated configuration item. 75 | * 76 | * If the deprecated configuration item is defined, it will be returned, and a warning will be logged. 77 | * 78 | * Otherwise, the configuration from path will be looked up. 79 | * 80 | * If the value of the item is null, this will return None, otherwise returns Some. 81 | */ 82 | def getOptionalDeprecated[A: ConfigLoader](path: String, deprecated: String): Option[A] = { 83 | if (underlying.hasPath(deprecated)) { 84 | reportDeprecation(path, deprecated) 85 | getOptional[A](deprecated) 86 | } else { 87 | getOptional[A](path) 88 | } 89 | } 90 | 91 | /** 92 | * Get a deprecated configuration item. 93 | * 94 | * If the deprecated configuration item is defined, it will be returned, and a warning will be logged. 95 | * 96 | * Otherwise, the configuration from path will be looked up. 97 | */ 98 | def getDeprecated[A: ConfigLoader](path: String, deprecated: String): A = { 99 | if (underlying.hasPath(deprecated)) { 100 | reportDeprecation(path, deprecated) 101 | get[A](deprecated) 102 | } else { 103 | get[A](path) 104 | } 105 | } 106 | 107 | /** 108 | * Get a deprecated configuration. 109 | * 110 | * If the deprecated configuration is defined, it will be returned, falling back to the new configuration, and a 111 | * warning will be logged. 112 | * 113 | * Otherwise, the configuration from path will be looked up and used as is. 114 | */ 115 | def getDeprecatedWithFallback(path: String, deprecated: String, parent: String = ""): EnrichedConfig = { 116 | val config = get[Config](path) 117 | val merged = if (underlying.hasPath(deprecated)) { 118 | reportDeprecation(path, deprecated) 119 | get[Config](deprecated).withFallback(config) 120 | } else config 121 | new EnrichedConfig(merged) 122 | } 123 | 124 | /** 125 | * Creates a configuration error for a specific configuration key. 126 | * 127 | * For example: 128 | * {{{ 129 | * val configuration = Configuration.load() 130 | * throw configuration.reportError("engine.connectionUrl", "Cannot connect!") 131 | * }}} 132 | * 133 | * @param path the configuration key, related to this error 134 | * @param message the error message 135 | * @param e the related exception 136 | * @return a configuration exception 137 | */ 138 | def reportError(path: String, message: String, e: Option[Throwable] = None) = { 139 | //Configuration.configError(if (underlying.hasPath(path)) underlying.getValue(path).origin else underlying.root.origin, message, e) 140 | e.get 141 | } 142 | 143 | /** 144 | * Get the immediate subkeys of this configuration. 145 | */ 146 | def subKeys: Set[String] = underlying.root().keySet().asScala.toSet 147 | 148 | def reportDeprecation(path: String, deprecated: String): Unit = { 149 | val origin = underlying.getValue(deprecated).origin 150 | //Logger.warn(s"${origin.description}: $deprecated is deprecated, use $path instead") 151 | } 152 | } 153 | 154 | object EnrichedConfig { 155 | def apply(underlying: Config) = new EnrichedConfig(underlying) 156 | } 157 | 158 | /** 159 | * A config loader 160 | */ 161 | trait ConfigLoader[A] { self => 162 | def load(config: Config, path: String): A 163 | def map[B](f: A => B): ConfigLoader[B] = new ConfigLoader[B] { 164 | def load(config: Config, path: String): B = { 165 | f(self.load(config, path)) 166 | } 167 | } 168 | } 169 | 170 | object ConfigLoader { 171 | 172 | def apply[A](f: Config => String => A): ConfigLoader[A] = new ConfigLoader[A] { 173 | def load(config: Config, path: String): A = f(config)(path) 174 | } 175 | 176 | import scala.collection.JavaConverters._ 177 | 178 | private def toScala[A](as: java.util.List[A]): immutable.Seq[A] = as.asScala.toVector 179 | 180 | implicit val stringLoader: ConfigLoader[String] = ConfigLoader(_.getString) 181 | implicit val seqStringLoader: ConfigLoader[immutable.Seq[String]] = ConfigLoader(_.getStringList).map(toScala) 182 | 183 | implicit val intLoader: ConfigLoader[Int] = ConfigLoader(_.getInt) 184 | implicit val seqIntLoader: ConfigLoader[immutable.Seq[Int]] = ConfigLoader(_.getIntList).map(toScala(_).map(_.toInt)) 185 | 186 | implicit val booleanLoader: ConfigLoader[Boolean] = ConfigLoader(_.getBoolean) 187 | implicit val seqBooleanLoader: ConfigLoader[immutable.Seq[Boolean]] = ConfigLoader(_.getBooleanList).map(toScala(_).map(_.booleanValue())) 188 | 189 | implicit val finiteDurationLoader: ConfigLoader[FiniteDuration] = ConfigLoader(config => config.getDuration(_, TimeUnit.MILLISECONDS)) 190 | .map(millis => FiniteDuration(millis, TimeUnit.MILLISECONDS)) 191 | implicit val seqFiniteDurationLoader: ConfigLoader[Seq[FiniteDuration]] = ConfigLoader(config => config.getDurationList(_, TimeUnit.MILLISECONDS)) 192 | .map(toScala(_).map(millis => FiniteDuration(millis, TimeUnit.MILLISECONDS))) 193 | 194 | implicit val doubleLoader: ConfigLoader[Double] = ConfigLoader(_.getDouble) 195 | implicit val seqDoubleLoader: ConfigLoader[immutable.Seq[java.lang.Double]] = ConfigLoader(_.getDoubleList).map(toScala) 196 | 197 | implicit val longLoader: ConfigLoader[Long] = ConfigLoader(_.getLong) 198 | implicit val seqLongLoader: ConfigLoader[immutable.Seq[java.lang.Long]] = ConfigLoader(_.getLongList).map(toScala) 199 | 200 | implicit val configLoader: ConfigLoader[Config] = ConfigLoader(_.getConfig) 201 | implicit val seqConfigLoader: ConfigLoader[Seq[Config]] = ConfigLoader(_.getConfigList).map(_.asScala.toSeq) 202 | 203 | implicit val playConfigLoader: ConfigLoader[EnrichedConfig] = configLoader.map(new EnrichedConfig(_)) 204 | implicit val seqEnrichedConfigLoader: ConfigLoader[Seq[EnrichedConfig]] = seqConfigLoader.map(_.map(new EnrichedConfig(_))) 205 | 206 | implicit def mapLoader[A](implicit valueLoader: ConfigLoader[A]): ConfigLoader[Map[String, A]] = new ConfigLoader[Map[String, A]] { 207 | def load(config: Config, path: String): Map[String, A] = { 208 | val obj = config.getObject(path) 209 | val conf = obj.toConfig 210 | obj.keySet().asScala.map { key => 211 | key -> valueLoader.load(conf, key) 212 | }.toMap 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/util/LoggerFactory.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.util 6 | 7 | trait LoggerFactory { 8 | def apply(clazz: Class[_]): NoDepsLogger 9 | def apply(name: String): NoDepsLogger 10 | } -------------------------------------------------------------------------------- /ssl-config-core/src/main/scala/com/typesafe/sslconfig/util/NoDepsLogger.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.util 6 | 7 | /** 8 | * Simple logger interface in order to keep the core of this library zero-dependencies. 9 | */ 10 | abstract class NoDepsLogger { 11 | 12 | def isDebugEnabled: Boolean 13 | 14 | def debug(msg: String): Unit 15 | 16 | def info(msg: String): Unit 17 | 18 | def warn(msg: String): Unit 19 | 20 | def error(msg: String): Unit 21 | def error(msg: String, throwable: Throwable): Unit 22 | } 23 | 24 | object NoopLogger { 25 | private val _noop = new NoopLogger 26 | 27 | def factory(): LoggerFactory = new LoggerFactory { 28 | override def apply(clazz: Class[_]) = _noop 29 | override def apply(name: String) = _noop 30 | } 31 | } 32 | final class NoopLogger extends NoDepsLogger { 33 | 34 | override def debug(msg: String): Unit = () 35 | 36 | override def info(msg: String): Unit = () 37 | 38 | override def warn(msg: String): Unit = () 39 | 40 | override def error(msg: String): Unit = () 41 | override def error(msg: String, throwable: Throwable): Unit = () 42 | 43 | override def isDebugEnabled: Boolean = false 44 | } 45 | object PrintlnLogger { 46 | def factory(): LoggerFactory = new LoggerFactory { 47 | override def apply(clazz: Class[_]) = new PrintlnLogger(clazz.getName) 48 | override def apply(name: String) = new PrintlnLogger(name) 49 | } 50 | } 51 | final class PrintlnLogger(name: String) extends NoDepsLogger { 52 | 53 | override def debug(msg: String): Unit = println(s"[DEBUG][$name] $msg") 54 | 55 | override def info(msg: String): Unit = println(s"[INFO][$name] $msg") 56 | 57 | override def warn(msg: String): Unit = println(s"[WARN][$name] $msg") 58 | 59 | override def error(msg: String): Unit = println(s"[ERROR][$name] $msg") 60 | override def error(msg: String, throwable: Throwable): Unit = println(s"[ERROR][$name] $msg") 61 | 62 | override def isDebugEnabled: Boolean = true 63 | } -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CertificateGenerator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.math.BigInteger 8 | import java.security._ 9 | import java.security.cert._ 10 | import java.util.Date 11 | 12 | import org.joda.time.Instant 13 | import sun.security.x509._ 14 | 15 | import scala.concurrent.duration.{ FiniteDuration, _ } 16 | 17 | /** 18 | * Used for testing only. This relies on internal sun.security packages, so cannot be used in OpenJDK. 19 | */ 20 | object CertificateGenerator { 21 | 22 | // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator 23 | // http://www.keylength.com/en/4/ 24 | 25 | /** 26 | * Generates a certificate using RSA (which is available in 1.6). 27 | */ 28 | def generateRSAWithSHA256(keySize: Int = 2048, from: Instant = Instant.now, duration: FiniteDuration = 365.days): X509Certificate = { 29 | val dn = "CN=localhost, OU=Unit Testing, O=Mavericks, L=Moon Base 1, ST=Cyberspace, C=CY" 30 | val to = from plus duration.toMillis 31 | 32 | val keyGen = KeyPairGenerator.getInstance("RSA") 33 | keyGen.initialize(keySize, new SecureRandom()) 34 | val pair = keyGen.generateKeyPair() 35 | generateCertificate(dn, pair, from.toDate, to.toDate, "SHA256withRSA") 36 | } 37 | 38 | def generateRSAWithSHA1(keySize: Int = 2048, from: Instant = Instant.now, duration: FiniteDuration = 365.days): X509Certificate = { 39 | val dn = "CN=localhost, OU=Unit Testing, O=Mavericks, L=Moon Base 1, ST=Cyberspace, C=CY" 40 | val to = from plus duration.toMillis 41 | 42 | val keyGen = KeyPairGenerator.getInstance("RSA") 43 | keyGen.initialize(keySize, new SecureRandom()) 44 | val pair = keyGen.generateKeyPair() 45 | generateCertificate(dn, pair, from.toDate, to.toDate, "SHA1withRSA") 46 | } 47 | 48 | def toPEM(certificate: X509Certificate) = { 49 | val encoder = java.util.Base64.getMimeEncoder() 50 | val certBegin = "-----BEGIN CERTIFICATE-----\n" 51 | val certEnd = "-----END CERTIFICATE-----" 52 | 53 | val derCert = certificate.getEncoded() 54 | val pemCertPre = encoder.encodeToString(derCert) 55 | val pemCert = certBegin + pemCertPre + certEnd 56 | pemCert 57 | } 58 | 59 | def generateRSAWithMD5(keySize: Int = 2048, from: Instant = Instant.now, duration: FiniteDuration = 365.days): X509Certificate = { 60 | val dn = "CN=localhost, OU=Unit Testing, O=Mavericks, L=Moon Base 1, ST=Cyberspace, C=CY" 61 | val to = from plus duration.toMillis 62 | 63 | val keyGen = KeyPairGenerator.getInstance("RSA") 64 | keyGen.initialize(keySize, new SecureRandom()) 65 | val pair = keyGen.generateKeyPair() 66 | generateCertificate(dn, pair, from.toDate, to.toDate, "MD5WithRSA") 67 | } 68 | 69 | private[sslconfig] def generateCertificate(dn: String, pair: KeyPair, from: Date, to: Date, algorithm: String): X509Certificate = { 70 | 71 | val info: X509CertInfo = new X509CertInfo 72 | val interval: CertificateValidity = new CertificateValidity(from, to) 73 | // I have no idea why 64 bits specifically are used for the certificate serial number. 74 | val sn: BigInteger = new BigInteger(64, new SecureRandom) 75 | val owner: X500Name = new X500Name(dn) 76 | 77 | info.set(X509CertInfo.VALIDITY, interval) 78 | info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn)) 79 | info.set(X509CertInfo.SUBJECT, owner) 80 | info.set(X509CertInfo.ISSUER, owner) 81 | info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic)) 82 | info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) 83 | 84 | info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(AlgorithmId.get(algorithm))) 85 | var cert: X509CertImpl = new X509CertImpl(info) 86 | val privkey: PrivateKey = pair.getPrivate 87 | cert.sign(privkey, algorithm) 88 | val algos = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] 89 | info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algos) 90 | cert = new X509CertImpl(info) 91 | cert.sign(privkey, algorithm) 92 | cert 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509TrustManagerSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import com.typesafe.sslconfig.util.NoopLogger 8 | import org.specs2.mutable._ 9 | import org.specs2.mock.Mockito 10 | 11 | import javax.net.ssl.X509TrustManager 12 | import java.security.cert.{ CertificateException, X509Certificate } 13 | 14 | object CompositeX509TrustManagerSpec extends Specification with Mockito { 15 | 16 | val mkLogger = NoopLogger.factory() 17 | 18 | "CompositeX509TrustManager" should { 19 | 20 | "with checkClientTrusted" should { 21 | 22 | "throws exception" in { 23 | val mockTrustManager1 = mock[X509TrustManager] 24 | val mockTrustManager2 = mock[X509TrustManager] 25 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager1, mockTrustManager2)) 26 | 27 | val certificate = CertificateGenerator.generateRSAWithSHA256() 28 | val chain = Array[X509Certificate](certificate) 29 | val authType = "" 30 | 31 | mockTrustManager1.checkClientTrusted(chain, authType) throws new CertificateException("fake1") 32 | mockTrustManager2.checkClientTrusted(chain, authType) throws new CertificateException("fake2") 33 | 34 | trustManager.checkClientTrusted(chain, authType).must(throwA[CompositeCertificateException].like { 35 | case e: CompositeCertificateException => 36 | val sourceExceptions = e.getSourceExceptions 37 | sourceExceptions(0).getMessage must be_==("fake1") 38 | sourceExceptions(1).getMessage must be_==("fake2") 39 | }) 40 | } 41 | 42 | "returns true" in { 43 | val mockTrustManager = mock[X509TrustManager] 44 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) 45 | 46 | val certificate = CertificateGenerator.generateRSAWithSHA256() 47 | val chain = Array[X509Certificate](certificate) 48 | val authType = "" 49 | 50 | trustManager.checkClientTrusted(chain, authType) must not(throwA[Throwable].like { 51 | case e: CompositeCertificateException => 52 | val sourceExceptions = e.getSourceExceptions 53 | sourceExceptions(0).getMessage must be_==("fake") 54 | }) 55 | } 56 | 57 | "returns true eventually" in { 58 | val mockTrustManager1 = mock[X509TrustManager] 59 | val mockTrustManager2 = mock[X509TrustManager] 60 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager1, mockTrustManager2)) 61 | 62 | val certificate = CertificateGenerator.generateRSAWithSHA256() 63 | val chain = Array[X509Certificate](certificate) 64 | val authType = "" 65 | 66 | mockTrustManager1.checkClientTrusted(chain, authType) throws new CertificateException("fake1") 67 | mockTrustManager2.checkClientTrusted(chain, authType) 68 | 69 | trustManager.checkClientTrusted(chain, authType) must not(throwA[Throwable]) 70 | } 71 | } 72 | 73 | "getAcceptedIssuers" should { 74 | "work fine" in { 75 | val mockTrustManager = mock[X509TrustManager] 76 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) 77 | val certificate = CertificateGenerator.generateRSAWithSHA256() 78 | mockTrustManager.getAcceptedIssuers returns Array[X509Certificate](certificate) 79 | 80 | val acceptedIssuers = trustManager.getAcceptedIssuers 81 | acceptedIssuers(0) must_== certificate 82 | } 83 | 84 | "throw exception when input exception" in { 85 | val mockTrustManager = mock[X509TrustManager] 86 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) 87 | mockTrustManager.getAcceptedIssuers throws new RuntimeException("fake") 88 | 89 | trustManager.getAcceptedIssuers.must(throwA[CompositeCertificateException].like { 90 | case e: CompositeCertificateException => 91 | val sourceExceptions = e.getSourceExceptions 92 | sourceExceptions(0).getMessage must be_==("fake") 93 | }) 94 | } 95 | } 96 | 97 | "checkServerTrusted" should { 98 | 99 | "work fine" in { 100 | val mockTrustManager = mock[X509TrustManager] 101 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) 102 | val certificate = CertificateGenerator.generateRSAWithSHA256() 103 | val chain = Array[X509Certificate](certificate) 104 | val authType = "" 105 | 106 | trustManager.checkServerTrusted(chain, authType) must not(throwA[Throwable]) 107 | } 108 | 109 | "throw an exception when nothing works" in { 110 | val mockTrustManager1 = mock[X509TrustManager] 111 | val mockTrustManager2 = mock[X509TrustManager] 112 | val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager1, mockTrustManager2)) 113 | 114 | val certificate = CertificateGenerator.generateRSAWithSHA256() 115 | val chain = Array[X509Certificate](certificate) 116 | val authType = "" 117 | 118 | mockTrustManager1.checkServerTrusted(chain, authType) throws new CertificateException("fake1") 119 | mockTrustManager2.checkServerTrusted(chain, authType) throws new CertificateException("fake2") 120 | 121 | trustManager.checkServerTrusted(chain, authType).must(throwA[CompositeCertificateException].like { 122 | case e: CompositeCertificateException => 123 | val sourceExceptions = e.getSourceExceptions 124 | sourceExceptions(0).getMessage must be_==("fake1") 125 | sourceExceptions(1).getMessage must be_==("fake2") 126 | }) 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/ConfigSSLContextBuilderSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.net.Socket 8 | import java.security._ 9 | import java.security.cert.{ CertPathValidatorException, X509Certificate } 10 | 11 | import javax.net.ssl._ 12 | import com.typesafe.sslconfig.util.NoopLogger 13 | import org.specs2.mock._ 14 | import org.specs2.mutable._ 15 | 16 | class ConfigSSLContextBuilderSpec extends Specification with Mockito { 17 | 18 | val CACERTS = s"${System.getProperty("java.home")}/lib/security/cacerts" 19 | 20 | val mkLogger = NoopLogger.factory() 21 | 22 | "ConfigSSLContentBuilder" should { 23 | 24 | "should have the right protocol by default" in { 25 | val info = SSLConfigSettings() 26 | 27 | val keyManagerFactory = mockKeyManagerFactory 28 | val trustManagerFactory = mockTrustManagerFactory 29 | 30 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 31 | 32 | val actual: SSLContext = builder.build() 33 | actual.getProtocol must_== Protocols.recommendedProtocol 34 | } 35 | 36 | "with protocol" should { 37 | 38 | "should default to Protocols.recommendedProtocols" in { 39 | val info = SSLConfigSettings() 40 | 41 | val keyManagerFactory = mockKeyManagerFactory 42 | val trustManagerFactory = mockTrustManagerFactory 43 | 44 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 45 | 46 | val actual: SSLContext = builder.build() 47 | actual.getProtocol must_== Protocols.recommendedProtocol 48 | } 49 | 50 | "should have an explicit protocol if defined" in { 51 | val info = SSLConfigSettings().withProtocol("TLS") 52 | 53 | val keyManagerFactory = mockKeyManagerFactory 54 | val trustManagerFactory = mockTrustManagerFactory 55 | 56 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 57 | 58 | val actual: SSLContext = builder.build() 59 | actual.getProtocol must_== "TLS" 60 | } 61 | } 62 | 63 | "build a key manager" in { 64 | val info = SSLConfigSettings() 65 | val keyManagerFactory = mockKeyManagerFactory 66 | val trustManagerFactory = mockTrustManagerFactory 67 | 68 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 69 | 70 | val keyStore = KeyStore.getInstance("PKCS12") 71 | val keyPairGenerator = KeyPairGenerator.getInstance("RSA") 72 | keyPairGenerator.initialize(2048) // 2048 is the NIST acceptable key length until 2030 73 | val keyPair = keyPairGenerator.generateKeyPair() 74 | val cert = FakeKeyStore.createSelfSignedCertificate(keyPair) 75 | val password = "changeit" // cannot have a null password for PKCS12 in 1.6 76 | keyStore.load(null, password.toCharArray) 77 | keyStore.setKeyEntry("playgenerated", keyPair.getPrivate, password.toCharArray, Array(cert)) 78 | 79 | val tempFile = java.io.File.createTempFile("privatekeystore", ".p12") 80 | val out = java.nio.file.Files.newOutputStream(tempFile.toPath) 81 | try { 82 | keyStore.store(out, password.toCharArray) 83 | } finally { 84 | out.close() 85 | } 86 | val filePath = tempFile.getAbsolutePath 87 | val keyStoreConfig = KeyStoreConfig(None, Some(filePath)).withStoreType("PKCS12").withPassword(Some(password)) 88 | 89 | val actual = builder.buildKeyManager(keyStoreConfig, SSLDebugConfig()) 90 | actual must beAnInstanceOf[X509KeyManager] 91 | } 92 | 93 | "build a trust manager" in { 94 | val info = SSLConfigSettings() 95 | val keyManagerFactory = mockKeyManagerFactory 96 | val trustManagerFactory = mockTrustManagerFactory 97 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 98 | 99 | val trustManagerConfig = TrustManagerConfig() 100 | val checkRevocation = false 101 | val revocationLists = None 102 | 103 | val actual = builder.buildCompositeTrustManager(trustManagerConfig, checkRevocation, revocationLists, SSLDebugConfig()) 104 | actual must beAnInstanceOf[javax.net.ssl.X509TrustManager] 105 | } 106 | 107 | "build a composite key manager" in { 108 | val info = SSLConfigSettings() 109 | val keyManagerFactory = mockKeyManagerFactory 110 | val trustManagerFactory = mockTrustManagerFactory 111 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 112 | 113 | val keyManagerConfig = new KeyManagerConfig() 114 | 115 | val actual = builder.buildCompositeKeyManager(keyManagerConfig, SSLDebugConfig()) 116 | actual must beAnInstanceOf[CompositeX509KeyManager] 117 | } 118 | 119 | "build a composite trust manager" in { 120 | val info = SSLConfigSettings() 121 | val keyManagerFactory = mockKeyManagerFactory 122 | val trustManagerFactory = mockTrustManagerFactory 123 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 124 | 125 | val trustManagerConfig = TrustManagerConfig() 126 | val checkRevocation = false 127 | val revocationLists = None 128 | 129 | val actual = builder.buildCompositeTrustManager( 130 | trustManagerConfig, 131 | checkRevocation, 132 | revocationLists, SSLDebugConfig()) 133 | actual must beAnInstanceOf[CompositeX509TrustManager] 134 | } 135 | 136 | "build a composite trust manager with data" in { 137 | val info = SSLConfigSettings() 138 | val keyManagerFactory = new DefaultKeyManagerFactoryWrapper(KeyManagerFactory.getDefaultAlgorithm) 139 | val trustManagerFactory = new DefaultTrustManagerFactoryWrapper(TrustManagerFactory.getDefaultAlgorithm) 140 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 141 | 142 | val certificate = CertificateGenerator.generateRSAWithSHA256() 143 | val certificateData = CertificateGenerator.toPEM(certificate) 144 | 145 | val trustStoreConfig = TrustStoreConfig(Some(certificateData), None).withStoreType("PEM") 146 | val trustManagerConfig = TrustManagerConfig().withTrustStoreConfigs(List(trustStoreConfig)) 147 | 148 | val checkRevocation = false 149 | val revocationLists = None 150 | 151 | val actual = builder.buildCompositeTrustManager(trustManagerConfig, checkRevocation, revocationLists, SSLDebugConfig()) 152 | 153 | actual must beAnInstanceOf[CompositeX509TrustManager] 154 | val issuers = actual.getAcceptedIssuers 155 | issuers.size must beEqualTo(1) 156 | } 157 | 158 | "build a file based keystore builder" in { 159 | val info = SSLConfigSettings() 160 | val keyManagerFactory = mock[KeyManagerFactoryWrapper] 161 | val trustManagerFactory = mock[TrustManagerFactoryWrapper] 162 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 163 | 164 | val storeType = KeyStore.getDefaultType 165 | val filePath = "derp" 166 | 167 | val actual = builder.fileBuilder(storeType, filePath, None) 168 | actual must beAnInstanceOf[FileBasedKeyStoreBuilder] 169 | } 170 | 171 | "build a file on classpath based keystore builder" in { 172 | val info = SSLConfigSettings() 173 | val keyManagerFactory = mock[KeyManagerFactoryWrapper] 174 | val trustManagerFactory = mock[TrustManagerFactoryWrapper] 175 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 176 | 177 | val storeType = KeyStore.getDefaultType 178 | val filePath = "derp" 179 | 180 | val actual = builder.fileOnClasspathBuilder(storeType, filePath, None) 181 | actual must beAnInstanceOf[FileOnClasspathBasedKeyStoreBuilder] 182 | 183 | } 184 | 185 | "build a string based keystore builder" in { 186 | val info = SSLConfigSettings() 187 | val keyManagerFactory = mock[KeyManagerFactoryWrapper] 188 | val trustManagerFactory = mock[TrustManagerFactoryWrapper] 189 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 190 | 191 | val data = "derp" 192 | 193 | val actual = builder.stringBuilder(data) 194 | actual must beAnInstanceOf[StringBasedKeyStoreBuilder] 195 | } 196 | 197 | "validate success of the keystore with a private key" in { 198 | val keyStore = KeyStore.getInstance("PKCS12") 199 | 200 | // Generate the key pair 201 | val keyPairGenerator = KeyPairGenerator.getInstance("RSA") 202 | keyPairGenerator.initialize(2048) // 2048 is the NIST acceptable key length until 2030 203 | val keyPair = keyPairGenerator.generateKeyPair() 204 | 205 | // Generate a self signed certificate 206 | val cert = FakeKeyStore.createSelfSignedCertificate(keyPair) 207 | 208 | val password = "changeit" // null passwords throw exception in 1.6 209 | keyStore.load(null, password.toCharArray) 210 | keyStore.setKeyEntry("playgenerated", keyPair.getPrivate, password.toCharArray, Array(cert)) 211 | 212 | val keyManagerFactory = mock[KeyManagerFactoryWrapper] 213 | val trustManagerFactory = mock[TrustManagerFactoryWrapper] 214 | 215 | val ksc = KeyStoreConfig(None, Some("path")).withPassword(Some(password)) 216 | val keyManagerConfig = KeyManagerConfig().withKeyStoreConfigs(List(ksc)) 217 | 218 | val info = SSLConfigSettings().withKeyManagerConfig(keyManagerConfig) 219 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 220 | builder.validateStoreContainsPrivateKeys(ksc, keyStore) must beTrue 221 | } 222 | 223 | "validate a failure of the keystore without a private key" in { 224 | // must be JKS, PKCS12 does not support trusted certificate entries in 1.6 at least 225 | // KeyStoreException: : TrustedCertEntry not supported (PKCS12KeyStore.java:620) 226 | // val keyStore = KeyStore.getInstance("PKCS12") 227 | val keyStore = KeyStore.getInstance(KeyStore.getDefaultType) 228 | 229 | // Generate the key pair 230 | val keyPairGenerator = KeyPairGenerator.getInstance("RSA") 231 | keyPairGenerator.initialize(2048) // 2048 is the NIST acceptable key length until 2030 232 | val keyPair = keyPairGenerator.generateKeyPair() 233 | 234 | // Generate a self signed certificate 235 | val cert = FakeKeyStore.createSelfSignedCertificate(keyPair) 236 | 237 | val password = "changeit" // null passwords throw exception in 1.6 in PKCS12 238 | keyStore.load(null, password.toCharArray) 239 | // Don't add the private key here, instead add a public cert only. 240 | keyStore.setCertificateEntry("playgeneratedtrusted", cert) 241 | 242 | val keyManagerFactory = mock[KeyManagerFactoryWrapper] 243 | val trustManagerFactory = mock[TrustManagerFactoryWrapper] 244 | 245 | val ksc = KeyStoreConfig(None, Some("path")).withPassword(Some(password)) 246 | val keyManagerConfig = KeyManagerConfig().withKeyStoreConfigs(List(ksc)) 247 | 248 | val info = SSLConfigSettings().withKeyManagerConfig(keyManagerConfig) 249 | val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) 250 | 251 | builder.validateStoreContainsPrivateKeys(ksc, keyStore) must beFalse 252 | } 253 | } 254 | 255 | private def mockTrustManagerFactory = { 256 | new TrustManagerFactoryWrapper { 257 | override def init(spec: ManagerFactoryParameters): Unit = {} 258 | 259 | override def getTrustManagers: Array[TrustManager] = Array( 260 | new X509ExtendedTrustManager { 261 | override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String, socket: Socket): Unit = ??? 262 | 263 | override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String, socket: Socket): Unit = ??? 264 | 265 | override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String, sslEngine: SSLEngine): Unit = ??? 266 | 267 | override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String, sslEngine: SSLEngine): Unit = ??? 268 | 269 | override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = ??? 270 | 271 | override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = ??? 272 | 273 | override def getAcceptedIssuers: Array[X509Certificate] = ??? 274 | } 275 | ) 276 | } 277 | } 278 | 279 | private def mockKeyManagerFactory = { 280 | new KeyManagerFactoryWrapper { 281 | override def init(keystore: KeyStore, password: Array[Char]): Unit = {} 282 | 283 | override def getKeyManagers: Array[KeyManager] = { 284 | Array(new X509ExtendedKeyManager { 285 | override def getClientAliases(s: String, principals: Array[Principal]): Array[String] = ??? 286 | 287 | override def chooseClientAlias(strings: Array[String], principals: Array[Principal], socket: Socket): String = ??? 288 | 289 | override def getServerAliases(s: String, principals: Array[Principal]): Array[String] = ??? 290 | 291 | override def chooseServerAlias(s: String, principals: Array[Principal], socket: Socket): String = ??? 292 | 293 | override def getCertificateChain(s: String): Array[X509Certificate] = ??? 294 | 295 | override def getPrivateKey(s: String): PrivateKey = ??? 296 | }) 297 | } 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/FakeKeyStoreSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import java.nio.file.Files 8 | 9 | import com.typesafe.sslconfig.util.NoopLogger 10 | import org.specs2.mutable.Specification 11 | 12 | import scala.util.Try 13 | 14 | class FakeKeyStoreSpec extends Specification { 15 | val mkLogger = NoopLogger.factory() 16 | 17 | "FakeKeyStore construction" should { 18 | "return true on MD5 cert" in { 19 | val weakCert = CertificateGenerator.generateRSAWithMD5() 20 | val actual = new FakeKeyStore(mkLogger).certificateTooWeak(weakCert) 21 | actual must beTrue 22 | } 23 | "return false on SHA256withRSA" in { 24 | val strongCert = CertificateGenerator.generateRSAWithSHA256() 25 | val actual = new FakeKeyStore(mkLogger).certificateTooWeak(strongCert) 26 | actual must beFalse 27 | } 28 | 29 | "build a keystore with a selfsigned certificate and trust on that certificate" in { 30 | // create and persist a key store 31 | val fakeKeyStore = new FakeKeyStore(mkLogger) 32 | val basePath = Files.createTempDirectory("fake-keystore-spec-").toFile 33 | val ksPath = fakeKeyStore.getKeyStoreFilePath(basePath) 34 | fakeKeyStore.createKeyStore(basePath) 35 | 36 | // load the persisted key store 37 | val fakeKeyStore2 = new FakeKeyStore(mkLogger) 38 | val keyStore = fakeKeyStore2.createKeyStore(basePath) 39 | try { 40 | import scala.collection.JavaConverters._ 41 | val certificates = keyStore.aliases().asScala.flatMap { 42 | alias => 43 | Try(keyStore.getCertificate(alias)).toOption 44 | } 45 | certificates.size must be_==(2) // the self-signed and the trusted 46 | } finally { 47 | ksPath.delete() 48 | } 49 | } 50 | 51 | "build a keystore that's compatible with sslconfig.KeyStore" in { 52 | // create and persist a key store 53 | val fakeKeyStore = new FakeKeyStore(mkLogger) 54 | val basePath = Files.createTempDirectory("fake-keystore-spec-").toFile 55 | val ksPath = fakeKeyStore.getKeyStoreFilePath(basePath) 56 | fakeKeyStore.createKeyStore(basePath) 57 | 58 | // load the persisted key store using sslconfig.KeyStore 59 | val keyStore = new FileBasedKeyStoreBuilder( 60 | FakeKeyStore.KeystoreSettings.KeystoreType, 61 | ksPath.getAbsolutePath, 62 | Some(FakeKeyStore.KeystoreSettings.keystorePassword) 63 | ).build() 64 | try { 65 | import scala.collection.JavaConverters._ 66 | val certificates = keyStore.aliases().asScala.flatMap { 67 | alias => 68 | Try(keyStore.getCertificate(alias)).toOption 69 | } 70 | certificates.size must be_==(2) // the self-signed and the trusted 71 | } finally { 72 | ksPath.delete() 73 | } 74 | } 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/KeyStoreSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import org.specs2.mutable._ 8 | 9 | class KeyStoreSpec extends Specification { 10 | 11 | "StringBasedKeyStoreBuilder" should { 12 | 13 | "create several certificate" in { 14 | val builder = new StringBasedKeyStoreBuilder( 15 | """-----BEGIN CERTIFICATE----- 16 | |MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV 17 | |UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy 18 | |dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 19 | |MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx 20 | |dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B 21 | |AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f 22 | |BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A 23 | |cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC 24 | |AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ 25 | |MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm 26 | |aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw 27 | |ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj 28 | |IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF 29 | |MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA 30 | |A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y 31 | |7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh 32 | |1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 33 | |-----END CERTIFICATE----- 34 | |-----BEGIN CERTIFICATE----- 35 | |MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg 36 | |Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG 37 | |A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz 38 | |MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL 39 | |Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0 40 | |IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u 41 | |sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql 42 | |HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID 43 | |AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW 44 | |M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF 45 | |NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ 46 | |-----END CERTIFICATE----- 47 | |-----BEGIN CERTIFICATE----- 48 | |MIIDPDCCAqWgAwIBAgIQEj3w59oqIkekOIngiu7JZzANBgkqhkiG9w0BAQUFADCB0TELMAkGA1UE 49 | |BhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMRowGAYDVQQK 50 | |ExFUaGF3dGUgQ29uc3VsdGluZzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZp 51 | |c2lvbjEkMCIGA1UEAxMbVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIENBMSswKQYJKoZIhvcNAQkB 52 | |FhxwZXJzb25hbC1mcmVlbWFpbEB0aGF3dGUuY29tMB4XDTk2MDEwMTAwMDAwMFoXDTIxMDEwMTIz 53 | |NTk1OVowgdExCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNh 54 | |cGUgVG93bjEaMBgGA1UEChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRp 55 | |b24gU2VydmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFpbCBD 56 | |QTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhhd3RlLmNvbTCBnzANBgkqhkiG 57 | |9w0BAQEFAAOBjQAwgYkCgYEA1GnX1LCUZFtx6UfYDFG26nKRsIRefS0Nj3sS34UldSh0OkIsYyef 58 | |lXtL734Zhx2G6qPduc6WZBrCFG5ErHzmj+hND3EfQDimAKOHePb5lIZererAXnbr2RSjXW56fAyl 59 | |S1V/Bhkpf56aJtVquzgkCGqYx7Hao5iR/Xnb5VrEHLkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB 60 | |/zANBgkqhkiG9w0BAQUFAAOBgQAemGDU5fJUYLA9GoFkR/dbo9lvwykLp9KpgUn2w22FFChFRAH0 61 | |cVyVLhQPGivRqWvBX2c9FvFyIK++FsoOMF/Jy6WTLMNnVB5yIoojdmyUHVFSbJ3E4EcC18y/8IB7 62 | |GG4l3GJh1qb+wR1/2bP9jVxFEFrGZWSa6yz1A0/WSGL7Lg== 63 | |-----END CERTIFICATE----- 64 | |""".stripMargin) 65 | 66 | val certs = builder.build() 67 | certs.size() must be_==(3) 68 | } 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/LoggingSSLFactorySpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import com.typesafe.config.ConfigFactory 8 | import com.typesafe.sslconfig.util._ 9 | import javax.net.ssl._ 10 | import org.specs2.mutable.Specification 11 | 12 | import scala.collection.mutable 13 | 14 | class LoggingSSLFactorySpec extends Specification { 15 | 16 | "logger" should { 17 | "work" in { 18 | val input = 19 | """ 20 | |ssl-config { 21 | | debug { 22 | | ssl = true # setting this will turn on debugging for SSLEngine / SSLSocketFactory 23 | | pluggability = true # print out a warning 24 | | } 25 | | trustManager = { 26 | | stores = [ 27 | | { 28 | | path: ${java.home}/lib/security/cacerts, 29 | | password = "changeit", 30 | | } 31 | | ] 32 | | } 33 | |} 34 | """.stripMargin 35 | val config = ConfigFactory.systemProperties().withFallback(ConfigFactory.parseString(input).withFallback(ConfigFactory.defaultReference())).resolve() 36 | 37 | //val str = config.root.render(ConfigRenderOptions.defaults()) 38 | //println(str) 39 | 40 | val messagesList = mutable.Buffer[String]() 41 | val loggerFactory: LoggerFactory = new LoggerFactory { 42 | override def apply(clazz: Class[_]) = new PrintlnLogger(messagesList) 43 | override def apply(name: String) = new PrintlnLogger(messagesList) 44 | } 45 | val parser = new SSLConfigParser(EnrichedConfig(config.getConfig("ssl-config")), getClass.getClassLoader, Some(loggerFactory)) 46 | val info = parser.parse() 47 | 48 | val keyManagerFactory: KeyManagerFactoryWrapper = new DefaultKeyManagerFactoryWrapper(KeyManagerFactory.getDefaultAlgorithm) 49 | val trustManagerFactory: TrustManagerFactoryWrapper = new DefaultTrustManagerFactoryWrapper(TrustManagerFactory.getDefaultAlgorithm) 50 | 51 | val builder = new ConfigSSLContextBuilder(loggerFactory, info, keyManagerFactory, trustManagerFactory) 52 | val context = builder.build() 53 | 54 | val factory = context.getSocketFactory 55 | val socket = factory.createSocket() 56 | 57 | messagesList must contain("entry: createSocket()") 58 | messagesList must contain("pluggability is a deprecated debug setting and has no effect!") 59 | } 60 | } 61 | 62 | final class PrintlnLogger(list: mutable.Buffer[String]) extends NoDepsLogger { 63 | override def debug(msg: String): Unit = () 64 | 65 | override def info(msg: String): Unit = () 66 | 67 | override def warn(msg: String): Unit = { 68 | list.append(msg) 69 | } 70 | 71 | override def error(msg: String): Unit = () 72 | 73 | override def error(msg: String, throwable: Throwable): Unit = () 74 | 75 | override def isDebugEnabled: Boolean = true 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/SSLConfigParserSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import com.typesafe.sslconfig.util.EnrichedConfig 8 | import org.specs2.mutable._ 9 | 10 | import com.typesafe.config.ConfigFactory 11 | 12 | object SSLConfigParserSpec extends Specification { 13 | 14 | // We can get horrible concurrent modification exceptions in the logger if we run 15 | // several WithApplication at the same time. Usually happens in the build. 16 | sequential 17 | 18 | "SSLConfigParser" should { 19 | 20 | def parseThis(input: String) = { 21 | val config = ConfigFactory.parseString(input).withFallback(ConfigFactory.defaultReference().getConfig("ssl-config")) 22 | val parser = new SSLConfigParser(EnrichedConfig(config), getClass.getClassLoader, None) 23 | parser.parse() 24 | } 25 | 26 | "parse ws.ssl base section" in { 27 | val actual = parseThis(""" 28 | |default = true 29 | |protocol = TLSv1.1 30 | |checkRevocation = true 31 | |revocationLists = [ "http://example.com" ] 32 | |// hostnameVerifierClass = "com.ning.http.util.DefaultHostnameVerifier" // TODO do we need this one? 33 | |enabledCipherSuites = [ TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA ] 34 | |enabledProtocols = [ TLSv1.2, TLSv1.1, SSLv3 ] 35 | """.stripMargin) 36 | 37 | actual.default must beTrue 38 | actual.protocol must_== "TLSv1.1" 39 | actual.checkRevocation must beSome(true) 40 | actual.revocationLists must beSome.which { 41 | _ must beEqualTo(Seq(new java.net.URL("http://example.com"))) 42 | } 43 | // actual.hostnameVerifierClass must_== classOf[com.ning.http.util.DefaultHostnameVerifier] 44 | actual.enabledCipherSuites must beSome.which(_ must containTheSameElementsAs(Seq("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"))) 45 | actual.enabledProtocols must beSome.which(_ must containTheSameElementsAs(Seq("TLSv1.2", "TLSv1.1", "SSLv3"))) 46 | actual.secureRandom must beNone 47 | } 48 | 49 | "parse ssl-config.loose section" in { 50 | val actual = parseThis(""" 51 | |loose = { 52 | | allowLegacyHelloMessages = true 53 | | allowUnsafeRenegotiation = true 54 | | disableHostnameVerification = true 55 | | acceptAnyCertificate = true 56 | |} 57 | """.stripMargin) 58 | actual.loose.allowLegacyHelloMessages must beSome(true) 59 | actual.loose.allowUnsafeRenegotiation must beSome(true) 60 | actual.loose.disableHostnameVerification must beTrue 61 | actual.loose.acceptAnyCertificate must beTrue 62 | } 63 | 64 | "say debug is disabled if all debug is disabled" in { 65 | parseThis("").debug.enabled must beFalse 66 | } 67 | 68 | "parse ssl-config.debug section" in { 69 | val actual = parseThis(""" 70 | |debug = { 71 | | ssl = true 72 | | sslctx = true 73 | | keymanager = true 74 | | trustmanager = true 75 | |} 76 | """.stripMargin) 77 | 78 | actual.debug.enabled must beTrue 79 | 80 | actual.debug.all must beFalse 81 | 82 | actual.debug.ssl must beTrue 83 | actual.debug.sslctx must beTrue 84 | actual.debug.keymanager must beTrue 85 | actual.debug.trustmanager must beTrue 86 | } 87 | 88 | "parse ssl-config.debug section with all" in { 89 | val actual = parseThis(""" 90 | |debug = { 91 | | all = true 92 | |} 93 | """.stripMargin) 94 | 95 | actual.debug.enabled must beTrue 96 | 97 | // everything else is false, all wins everything. 98 | actual.debug.all must beTrue 99 | } 100 | 101 | "parse ssl-config.debug section with ssl" in { 102 | val actual = parseThis(""" 103 | |debug = { 104 | | ssl = true 105 | |} 106 | """.stripMargin) 107 | actual.debug.enabled must beTrue 108 | actual.debug.ssl must beTrue 109 | } 110 | 111 | "parse ssl-config.trustBuilder section" in { 112 | val info = parseThis(""" 113 | |trustManager = { 114 | | algorithm = "trustme" 115 | | stores = [ 116 | | { type: "storeType", path: "trusted", password: "changeit" } 117 | | ] 118 | |} 119 | """.stripMargin) 120 | 121 | val tmc = info.trustManagerConfig 122 | tmc.algorithm must_== "trustme" 123 | val tsi = tmc.trustStoreConfigs(0) 124 | tsi.filePath must beSome.which(_ must beEqualTo("trusted")) 125 | tsi.storeType must_== "storeType" 126 | tsi.password must beSome.which(_ must beEqualTo("changeit")) 127 | } 128 | 129 | "parse ssl-config.keyManager section" in { 130 | val info = parseThis(""" 131 | |keyManager = { 132 | | password = "changeit" 133 | | algorithm = "keyStore" 134 | | stores = [ 135 | | { 136 | | type: "storeType", 137 | | path: "cacerts", 138 | | password: "password1" 139 | | }, 140 | | { type: "PEM", data = "data", password: "changeit" } 141 | | ] 142 | |} 143 | """.stripMargin) 144 | 145 | val kmc = info.keyManagerConfig 146 | kmc.algorithm must_== "keyStore" 147 | kmc.keyStoreConfigs.size must_== 2 148 | val fileStoreConfig = kmc.keyStoreConfigs(0) 149 | fileStoreConfig.filePath must beSome.which(_ must beEqualTo("cacerts")) 150 | fileStoreConfig.storeType must_== "storeType" 151 | fileStoreConfig.password must beSome.which { 152 | _ must beEqualTo("password1") 153 | } 154 | val stringStoreConfig = kmc.keyStoreConfigs(1) 155 | stringStoreConfig.data must beSome.which(_ must beEqualTo("data")) 156 | } 157 | 158 | "fail on ssl-config.keyManager with no path defined" in { 159 | parseThis(""" 160 | |keyManager = { 161 | | algorithm = "keyStore" 162 | | stores = [ 163 | | { type: "storeType", password: "password1" } 164 | | ] 165 | |} 166 | """.stripMargin).must(throwAn[AssertionError]) 167 | } 168 | 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/SystemPropertiesSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 - 2020 Lightbend Inc. 3 | */ 4 | 5 | package com.typesafe.sslconfig.ssl 6 | 7 | import com.typesafe.sslconfig.util.NoopLogger 8 | import org.specs2.mutable._ 9 | import java.security.Security 10 | 11 | object SystemPropertiesSpec extends Specification with After { 12 | 13 | sequential 14 | 15 | def after = sp.clearProperties() 16 | 17 | val mkLogger = NoopLogger.factory() 18 | val sp = new SystemConfiguration(mkLogger) 19 | 20 | "SystemProperties" should { 21 | 22 | "disableCheckRevocation should not be set normally" in { 23 | val config = SSLConfigSettings().withCheckRevocation(None) 24 | 25 | val originalOscp = Security.getProperty("ocsp.enable") 26 | 27 | sp.configure(config) 28 | 29 | // http://stackoverflow.com/a/8507905/5266 30 | Security.getProperty("ocsp.enable") must_== originalOscp 31 | System.getProperty("com.sun.security.enableCRLDP") must beNull 32 | System.getProperty("com.sun.net.ssl.checkRevocation") must beNull 33 | } 34 | 35 | "disableCheckRevocation is set explicitly" in { 36 | val config = SSLConfigSettings().withCheckRevocation(Some(true)) 37 | 38 | sp.configure(config) 39 | 40 | // http://stackoverflow.com/a/8507905/5266 41 | Security.getProperty("ocsp.enable") must be("true") 42 | System.getProperty("com.sun.security.enableCRLDP") must be("true") 43 | System.getProperty("com.sun.net.ssl.checkRevocation") must be("true") 44 | } 45 | 46 | // @see http://www.oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html 47 | "allowLegacyHelloMessages is not set" in { 48 | val config = SSLConfigSettings().withLoose(SSLLooseConfig().withAllowLegacyHelloMessages(None)) 49 | 50 | sp.configure(config) 51 | 52 | System.getProperty("sun.security.ssl.allowLegacyHelloMessages") must beNull 53 | } 54 | 55 | // @see http://www.oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html 56 | "allowLegacyHelloMessages is set" in { 57 | val config = SSLConfigSettings().withLoose(SSLLooseConfig().withAllowLegacyHelloMessages(Some(true))) 58 | 59 | sp.configure(config) 60 | 61 | System.getProperty("sun.security.ssl.allowLegacyHelloMessages") must be("true") 62 | } 63 | 64 | // @see http://www.oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html 65 | "allowUnsafeRenegotiation not set" in { 66 | val config = SSLConfigSettings().withLoose(SSLLooseConfig().withAllowUnsafeRenegotiation(None)) 67 | 68 | sp.configure(config) 69 | 70 | System.getProperty("sun.security.ssl.allowUnsafeRenegotiation") must beNull 71 | } 72 | 73 | // @see http://www.oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html 74 | "allowUnsafeRenegotiation is set" in { 75 | val config = SSLConfigSettings().withLoose(SSLLooseConfig().withAllowUnsafeRenegotiation(Some(true))) 76 | 77 | sp.configure(config) 78 | 79 | System.getProperty("sun.security.ssl.allowUnsafeRenegotiation") must be("true") 80 | } 81 | 82 | } 83 | 84 | } 85 | --------------------------------------------------------------------------------