diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7429225a..3f25ee59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,12 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] scala: [2.13.17, 2.12.20, 3.3.7] - java: [temurin@8, temurin@11] + java: + - temurin@8 + - temurin@11 + - temurin@17 + - temurin@21 + - temurin@25 exclude: - java: temurin@8 os: macos-latest @@ -64,6 +69,30 @@ jobs: java-version: 11 cache: sbt + - name: Setup Java (temurin@17) + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + cache: sbt + + - name: Setup Java (temurin@21) + if: matrix.java == 'temurin@21' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: sbt + + - name: Setup Java (temurin@25) + if: matrix.java == 'temurin@25' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + cache: sbt + - name: Setup sbt uses: sbt/setup-sbt@v1 @@ -133,6 +162,30 @@ jobs: java-version: 11 cache: sbt + - name: Setup Java (temurin@17) + if: matrix.java == 'temurin@17' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 17 + cache: sbt + + - name: Setup Java (temurin@21) + if: matrix.java == 'temurin@21' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: sbt + + - name: Setup Java (temurin@25) + if: matrix.java == 'temurin@25' + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 25 + cache: sbt + - name: Setup sbt uses: sbt/setup-sbt@v1 diff --git a/build.sbt b/build.sbt index b414c612..040d0935 100644 --- a/build.sbt +++ b/build.sbt @@ -31,6 +31,23 @@ lazy val sslConfigCore = project .exclude[IncompatibleResultTypeProblem]("com.typesafe.sslconfig.ssl.SSLConfigSettings.$default$7"), ProblemFilters .exclude[IncompatibleResultTypeProblem]("com.typesafe.sslconfig.ssl.SSLConfigSettings.$default$8"), + ProblemFilters + .exclude[IncompatibleResultTypeProblem]("com.typesafe.sslconfig.ssl.SSLConfigSettings.$default$9"), + ProblemFilters + .exclude[IncompatibleResultTypeProblem]("com.typesafe.sslconfig.ssl.SSLDebugConfig.$default$4"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.AlgorithmConstraint"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore$CA$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore$CA$Alias$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore$KeystoreSettings$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore$User$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeChainedKeyStore$User$Alias$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeKeyStore"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeKeyStore$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeKeyStore$KeystoreSettings$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeKeyStore$SelfSigned$"), + ProblemFilters.exclude[MissingClassProblem]("com.typesafe.sslconfig.ssl.FakeKeyStore$SelfSigned$Alias$"), ), libraryDependencies ++= Dependencies.sslConfigCore, libraryDependencies ++= Dependencies.testDependencies, @@ -99,9 +116,9 @@ ThisBuild / githubWorkflowOSes := Seq("ubuntu-latest", "macos-latest", "windows- ThisBuild / githubWorkflowJavaVersions := Seq( JavaSpec.temurin("8"), JavaSpec.temurin("11"), - // JavaSpec.temurin("17"), // can't test currently because until we drop usage of sun.security.x509.* - // JavaSpec.temurin("21"), - // JavaSpec.temurin("25"), + JavaSpec.temurin("17"), + JavaSpec.temurin("21"), + JavaSpec.temurin("25"), ) ThisBuild / githubWorkflowBuildMatrixExclusions += MatrixExclude(Map("java" -> "temurin@8", "os" -> "macos-latest")) diff --git a/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeChainedKeyStore.scala b/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeChainedKeyStore.scala deleted file mode 100644 index b60a3655..00000000 --- a/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeChainedKeyStore.scala +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (C) 2015 - 2025 Lightbend Inc. - */ - -package com.typesafe.sslconfig.ssl - -import java.io._ -import java.math.BigInteger -import java.security.cert.X509Certificate -import java.security.interfaces.RSAPublicKey -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.KeyStore -import java.security.SecureRandom -import java.util.Date -import javax.net.ssl.KeyManagerFactory - -import com.typesafe.sslconfig.util.LoggerFactory -import com.typesafe.sslconfig.util.NoDepsLogger -import sun.security.x509._ - -/** - * A fake key store with a selfsigned CA and a certificate issued by that CA. Includes a `trustedCertEntry` for - * each of the two certificates. - * - * {{{ - * Your keystore contains 4 entries - * - * sslconfig-user-trust, Oct 4, 2018, trustedCertEntry, - * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 - * sslconfig-user, Oct 4, 2018, PrivateKeyEntry, - * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 - * sslconfig-CA-trust, Oct 4, 2018, trustedCertEntry, - * Certificate fingerprint (SHA1): 9B:78:6B:4F:E4:B6:4D:EF:3E:3E:06:32:7A:53:83:28:96:7F:12:C7 - * sslconfig-CA, Oct 4, 2018, PrivateKeyEntry, - * Certificate fingerprint (SHA1): 9B:78:6B:4F:E4:B6:4D:EF:3E:3E:06:32:7A:53:83:28:96:7F:12:C7 - * }}} - * - * Was: play.core.server.ssl.FakeKeyStore - */ -@deprecated( - "Uses internal sun.security.x509 classes. " + - "Works in Java 17 only with the `--add-exports=java.base/sun.security.x509=ALL-UNNAMED` flag. " + - "Does not work at all anymore with Java 21 and newer. " + - "To create certificates from code, use alternatives like Bouncy Castle instead.", - "0.7.0" -) -object FakeChainedKeyStore { - private val EMPTY_PASSWORD = Array.emptyCharArray - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object CA { - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object Alias { - // These two constants use a weird capitalization but that's what keystore uses internally (see class scaladoc) - val trustedCertEntry = "sslconfig-CA-trust" - val PrivateKeyEntry = "sslconfig-CA" - } - - val DistinguishedName = - "CN=certification.authority, OU=Unit Testing, O=Mavericks, L=SSL Config Base 1, ST=Cyberspace, C=CY" - val keyPassword: Array[Char] = EMPTY_PASSWORD - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object User { - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object Alias { - // These two constants use a weird capitalization but that's what keystore uses internally (see class scaladoc) - val trustedCertEntry = "sslconfig-user-trust" - val PrivateKeyEntry = "sslconfig-user" - } - - val DistinguishedName = "CN=localhost, OU=Unit Testing, O=Mavericks, L=SSL Config Base 1, ST=Cyberspace, C=CY" - val keyPassword: Array[Char] = EMPTY_PASSWORD - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object KeystoreSettings { - val GeneratedKeyStore: String = fileInDevModeDir("chained.keystore") - val SignatureAlgorithmName = "SHA256withRSA" - val KeyPairAlgorithmName = "RSA" - val KeyPairKeyLength = 2048 // 2048 is the NIST acceptable key length until 2030 - val KeystoreType = "JKS" - val keystorePassword: Array[Char] = EMPTY_PASSWORD - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def fileInDevModeDir(filename: String): String = { - "target" + File.separatorChar + "dev-mode" + File.separatorChar + filename - } - - /** - * Generate a fresh KeyStore object in memory. This KeyStore - * is not saved to disk. If you want that, then call `keyManagerFactory`. - * - * This method is public only for consumption by Play/Lagom. - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def generateKeyStore: KeyStore = { - // Create a new KeyStore - val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) - - // Generate the key pair - val keyPairGenerator = KeyPairGenerator.getInstance(KeystoreSettings.KeyPairAlgorithmName) - keyPairGenerator.initialize(KeystoreSettings.KeyPairKeyLength) - val keyPair = keyPairGenerator.generateKeyPair() - val certificateAuthorityKeyPair = keyPairGenerator.generateKeyPair() - - val cacert: X509Certificate = createCertificateAuthority(certificateAuthorityKeyPair) - // Generate a self signed certificate - val cert = createUserCertificate(keyPair, certificateAuthorityKeyPair) - - // Create the key store, first set the store pass - keyStore.load(null, KeystoreSettings.keystorePassword) - keyStore.setKeyEntry(CA.Alias.PrivateKeyEntry, keyPair.getPrivate, CA.keyPassword, Array(cacert)) - keyStore.setCertificateEntry(CA.Alias.trustedCertEntry, cacert) - keyStore.setKeyEntry(User.Alias.PrivateKeyEntry, keyPair.getPrivate, User.keyPassword, Array(cert)) - keyStore.setCertificateEntry(User.Alias.trustedCertEntry, cert) - keyStore - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private[ssl] def createUserCertificate( - userKeyPair: KeyPair, - certificateAuthorityKeyPair: KeyPair - ): X509Certificate = { - val certInfo = new X509CertInfo() - - // Serial number and version - certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, new SecureRandom()))) - certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) - - // Validity - val validFrom = new Date() - val validTo = new Date(validFrom.getTime + 50L * 365L * 24L * 60L * 60L * 1000L) - val validity = new CertificateValidity(validFrom, validTo) - certInfo.set(X509CertInfo.VALIDITY, validity) - - // Subject and issuer - val certificateAuthorityName = new X500Name(CA.DistinguishedName) - certInfo.set(X509CertInfo.ISSUER, certificateAuthorityName) - val owner = new X500Name(User.DistinguishedName) - certInfo.set(X509CertInfo.SUBJECT, owner) - - // Key and algorithm - certInfo.set(X509CertInfo.KEY, new CertificateX509Key(userKeyPair.getPublic)) - val algorithm = AlgorithmId.get("SHA256WithRSA") - certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithm)) - - // Create a new certificate and sign it - val cert = new X509CertImpl(certInfo) - cert.sign(userKeyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) - - // Since the signature provider may have a different algorithm ID to what we think it should be, - // we need to reset the algorithm ID, and resign the certificate - val actualAlgorithm = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] - certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, actualAlgorithm) - val newCert = new X509CertImpl(certInfo) - newCert.sign(certificateAuthorityKeyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) - newCert - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def createCertificateAuthority(keyPair: KeyPair): X509Certificate = { - val certInfo = new X509CertInfo() - // Serial number and version - certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, new SecureRandom()))) - certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) - - // Validity - val validFrom = new Date() - val validTo = new Date(validFrom.getTime + 50L * 365L * 24L * 60L * 60L * 1000L) // 50 years - val validity = new CertificateValidity(validFrom, validTo) - certInfo.set(X509CertInfo.VALIDITY, validity) - - // Subject and issuer - val owner = new X500Name(CA.DistinguishedName) - certInfo.set(X509CertInfo.SUBJECT, owner) - certInfo.set(X509CertInfo.ISSUER, owner) - - // Key and algorithm - certInfo.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic)) - val algorithm = AlgorithmId.get("SHA256WithRSA") - certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithm)) - - val caExtension = new CertificateExtensions - caExtension.set( - BasicConstraintsExtension.NAME, - new BasicConstraintsExtension( /* isCritical */ true, /* isCA */ true, 0) - ) - certInfo.set(X509CertInfo.EXTENSIONS, caExtension) - - // Create a new certificate and sign it - val cert = new X509CertImpl(certInfo) - cert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) - - // Since the signature provider may have a different algorithm ID to what we think it should be, - // we need to reset the algorithm ID, and resign the certificate - val actualAlgorithm = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] - certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, actualAlgorithm) - val newCert = new X509CertImpl(certInfo) - newCert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) - newCert - } - -} - -/** - * A fake key store - * - * Was: play.core.server.ssl.FakeKeyStore - */ -@deprecated( - "Uses internal sun.security.x509 classes. " + - "Works in Java 17 only with the `--add-exports=java.base/sun.security.x509=ALL-UNNAMED` flag. " + - "Does not work at all anymore with Java 21 and newer. " + - "To create certificates from code, use alternatives like Bouncy Castle instead.", - "0.7.0" -) -final class FakeChainedKeyStore(mkLogger: LoggerFactory) { - - import FakeChainedKeyStore._ - - private val logger: NoDepsLogger = mkLogger(getClass) - - /** - * @param appPath a file descriptor to the root folder of the project (the root, not a particular module). - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def getKeyStoreFilePath(appPath: File) = new File(appPath, KeystoreSettings.GeneratedKeyStore) - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private[ssl] def shouldGenerate(keyStoreFile: File): Boolean = { - import com.typesafe.sslconfig.Compat.CollectionConverters._ - - if (!keyStoreFile.exists()) { - return true - } - - // Should regenerate if we find an unacceptably weak key in there. - val store = loadKeyStore(keyStoreFile) - store.aliases().asScala.exists { alias => - Option(store.getCertificate(alias)).exists(c => certificateTooWeak(c)) - } - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def loadKeyStore(file: File): KeyStore = { - val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) - val in = java.nio.file.Files.newInputStream(file.toPath) - try { - keyStore.load(in, KeystoreSettings.keystorePassword) - } finally { - closeQuietly(in) - } - keyStore - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private[ssl] def certificateTooWeak(c: java.security.cert.Certificate): Boolean = { - val key: RSAPublicKey = c.getPublicKey.asInstanceOf[RSAPublicKey] - key.getModulus.bitLength < 2048 || c - .asInstanceOf[X509CertImpl] - .getSigAlgName != KeystoreSettings.SignatureAlgorithmName - } - - /** Public only for consumption by Play/Lagom. */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def createKeyStore(appPath: File): KeyStore = { - val keyStoreFile = getKeyStoreFilePath(appPath) - val keyStoreDir = keyStoreFile.getParentFile - - createKeystoreParentDirectory(keyStoreDir) - - val keyStore: KeyStore = synchronized(if (shouldGenerate(keyStoreFile)) { - 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." - ) - - val freshKeyStore: KeyStore = generateKeyStore - val out = java.nio.file.Files.newOutputStream(keyStoreFile.toPath) - try { - freshKeyStore.store(out, Array.emptyCharArray) - } finally { - closeQuietly(out) - } - freshKeyStore - } else { - // Load a KeyStore from a file - val loadedKeyStore = loadKeyStore(keyStoreFile) - logger.info(s"HTTPS key pair generated in ${keyStoreFile.getAbsolutePath}.") - loadedKeyStore - }) - keyStore - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def createKeystoreParentDirectory(keyStoreDir: File) = { - if (keyStoreDir.mkdirs()) { - logger.debug(s"Parent directory for keystore successfully created at ${keyStoreDir.getAbsolutePath}") - } else if (keyStoreDir.exists() && keyStoreDir.isDirectory) { - // File.mkdirs returns false when the directory already exists. - logger.debug(s"No need to create $keyStoreDir since it already exists.") - } else if (keyStoreDir.exists() && keyStoreDir.isFile) { - // File.mkdirs also returns false when there is a file for that path. - // A consumer will then fail to write the keystore file later, so we fail fast here. - throw new IllegalStateException( - s"$keyStoreDir exists, but it is NOT a directory, making it not possible to generate a key store file." - ) - } else { - // Not being able to create a directory inside target folder is weird, but if it happens - // a consumer will then fail to write the keystore file later, so we fail fast here. - throw new IllegalStateException( - s"Failed to create $keyStoreDir. Check if there is permission to create such folder." - ) - } - } - - /** Public only for consumption by Play/Lagom. */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def keyManagerFactory(appPath: File): KeyManagerFactory = { - val keyStore = createKeyStore(appPath) - - // Load the key and certificate into a key manager factory - val kmf = KeyManagerFactory.getInstance("SunX509") - kmf.init(keyStore, Array.emptyCharArray) - kmf - } - - /** - * Close the given closeable quietly. - * - * Logs any IOExceptions encountered. - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def closeQuietly(closeable: Closeable) = { - try { - if (closeable != null) { - closeable.close() - } - } catch { - case e: IOException => logger.warn(s"Error closing stream. Cause: $e") - } - } - -} diff --git a/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeKeyStore.scala b/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeKeyStore.scala deleted file mode 100644 index 9e7f36fc..00000000 --- a/ssl-config-core/src/main/scala/com/typesafe/sslconfig/ssl/FakeKeyStore.scala +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2015 - 2025 Lightbend Inc. - */ - -package com.typesafe.sslconfig.ssl - -import java.io._ -import java.math.BigInteger -import java.security.cert.X509Certificate -import java.security.interfaces.RSAPublicKey -import java.security.KeyPair -import java.security.KeyPairGenerator -import java.security.KeyStore -import java.security.SecureRandom -import java.util.Date -import javax.net.ssl.KeyManagerFactory - -import com.typesafe.sslconfig.util.LoggerFactory -import com.typesafe.sslconfig.util.NoDepsLogger -import sun.security.x509._ - -/** - * A fake key store with a single, selfsigned certificate and keypair. Includes also a `trustedCertEntry` for - * that certificate. - * - * {{{ - * Your keystore contains 2 entries - * - * sslconfig-selfsigned-trust, Oct 4, 2018, trustedCertEntry, - * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 - * sslconfig-selfsigned, Oct 4, 2018, PrivateKeyEntry, - * Certificate fingerprint (SHA1): 19:2D:20:F0:36:59:E3:AD:C1:AA:55:82:0D:D2:94:5D:B3:75:3F:F8 - * }}} - * - * Was: play.core.server.ssl.FakeKeyStore - */ -@deprecated( - "Uses internal sun.security.x509 classes. " + - "Works in Java 17 only with the `--add-exports=java.base/sun.security.x509=ALL-UNNAMED` flag. " + - "Does not work at all anymore with Java 21 and newer. " + - "To create certificates from code, use alternatives like Bouncy Castle instead.", - "0.7.0" -) -object FakeKeyStore { - - private val EMPTY_PASSWORD = Array.emptyCharArray - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object SelfSigned { - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object Alias { - // These two constants use a weird capitalization but that's what keystore uses internally (see class scaladoc) - val trustedCertEntry = "sslconfig-selfsigned-trust" - val PrivateKeyEntry = "sslconfig-selfsigned" - } - - val DistinguishedName = - "CN=localhost, OU=Unit Testing (self-signed), O=Mavericks, L=SSL Config Base 1, ST=Cyberspace, C=CY" - val keyPassword: Array[Char] = EMPTY_PASSWORD - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - object KeystoreSettings { - val GeneratedKeyStore: String = fileInDevModeDir("selfsigned.keystore") - val SignatureAlgorithmName = "SHA256withRSA" - val KeyPairAlgorithmName = "RSA" - val KeyPairKeyLength = 2048 // 2048 is the NIST acceptable key length until 2030 - val KeystoreType = "JKS" - val keystorePassword: Array[Char] = EMPTY_PASSWORD - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def fileInDevModeDir(filename: String): String = { - "target" + File.separatorChar + "dev-mode" + File.separatorChar + filename - } - - /** - * Generate a fresh KeyStore object in memory. This KeyStore - * is not saved to disk. If you want that, then call `keyManagerFactory`. - * - * This method is public only for consumption by Play/Lagom. - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def generateKeyStore: KeyStore = { - // Create a new KeyStore - val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) - - // Generate the key pair - val keyPairGenerator = KeyPairGenerator.getInstance(KeystoreSettings.KeyPairAlgorithmName) - keyPairGenerator.initialize(KeystoreSettings.KeyPairKeyLength) - val keyPair = keyPairGenerator.generateKeyPair() - - val cert = createSelfSignedCertificate(keyPair) - - // Create the key store, first set the store pass - keyStore.load(null, KeystoreSettings.keystorePassword) - keyStore.setKeyEntry(SelfSigned.Alias.PrivateKeyEntry, keyPair.getPrivate, SelfSigned.keyPassword, Array(cert)) - keyStore.setCertificateEntry(SelfSigned.Alias.trustedCertEntry, cert) - keyStore - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def createSelfSignedCertificate(keyPair: KeyPair): X509Certificate = { - val certInfo = new X509CertInfo() - - // Serial number and version - certInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, new SecureRandom()))) - certInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) - - // Validity - val validFrom = new Date() - val validTo = new Date(validFrom.getTime + 50L * 365L * 24L * 60L * 60L * 1000L) - val validity = new CertificateValidity(validFrom, validTo) - certInfo.set(X509CertInfo.VALIDITY, validity) - - // Subject and issuer - val owner = new X500Name(SelfSigned.DistinguishedName) - certInfo.set(X509CertInfo.SUBJECT, owner) - certInfo.set(X509CertInfo.ISSUER, owner) - - // Key and algorithm - certInfo.set(X509CertInfo.KEY, new CertificateX509Key(keyPair.getPublic)) - val algorithm = AlgorithmId.get("SHA256WithRSA") - certInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithm)) - - // Create a new certificate and sign it - val cert = new X509CertImpl(certInfo) - cert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) - - // Since the signature provider may have a different algorithm ID to what we think it should be, - // we need to reset the algorithm ID, and resign the certificate - val actualAlgorithm = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] - certInfo.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, actualAlgorithm) - val newCert = new X509CertImpl(certInfo) - newCert.sign(keyPair.getPrivate, KeystoreSettings.SignatureAlgorithmName) - newCert - } - -} - -/** - * A fake key store - * - * Was: play.core.server.ssl.FakeKeyStore - */ -@deprecated( - "Uses internal sun.security.x509 classes. " + - "Works in Java 17 only with the `--add-exports=java.base/sun.security.x509=ALL-UNNAMED` flag. " + - "Does not work at all anymore with Java 21 and newer. " + - "To create certificates from code, use alternatives like Bouncy Castle instead.", - "0.7.0" -) -final class FakeKeyStore(mkLogger: LoggerFactory) { - - import FakeKeyStore._ - - private val logger: NoDepsLogger = mkLogger(getClass) - - /** - * @param appPath a file descriptor to the root folder of the project (the root, not a particular module). - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def getKeyStoreFilePath(appPath: File) = new File(appPath, KeystoreSettings.GeneratedKeyStore) - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private[ssl] def shouldGenerate(keyStoreFile: File): Boolean = { - import com.typesafe.sslconfig.Compat.CollectionConverters._ - - if (!keyStoreFile.exists()) { - return true - } - - // Should regenerate if we find an unacceptably weak key in there. - val store = loadKeyStore(keyStoreFile) - store.aliases().asScala.exists { alias => - Option(store.getCertificate(alias)).exists(c => certificateTooWeak(c)) - } - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def loadKeyStore(file: File): KeyStore = { - val keyStore: KeyStore = KeyStore.getInstance(KeystoreSettings.KeystoreType) - val in = java.nio.file.Files.newInputStream(file.toPath) - try { - keyStore.load(in, "".toCharArray) - } finally { - closeQuietly(in) - } - keyStore - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private[ssl] def certificateTooWeak(c: java.security.cert.Certificate): Boolean = { - val key: RSAPublicKey = c.getPublicKey.asInstanceOf[RSAPublicKey] - key.getModulus.bitLength < KeystoreSettings.KeyPairKeyLength || c - .asInstanceOf[X509CertImpl] - .getSigAlgName != KeystoreSettings.SignatureAlgorithmName - } - - /** Public only for consumption by Play/Lagom. */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def createKeyStore(appPath: File): KeyStore = { - val keyStoreFile = getKeyStoreFilePath(appPath) - val keyStoreDir = keyStoreFile.getParentFile - - createKeystoreParentDirectory(keyStoreDir) - - val keyStore: KeyStore = synchronized(if (shouldGenerate(keyStoreFile)) { - 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." - ) - - val freshKeyStore: KeyStore = generateKeyStore - val out = java.nio.file.Files.newOutputStream(keyStoreFile.toPath) - try { - freshKeyStore.store(out, KeystoreSettings.keystorePassword) - } finally { - closeQuietly(out) - } - freshKeyStore - } else { - // Load a KeyStore from a file - val loadedKeyStore = loadKeyStore(keyStoreFile) - logger.info(s"HTTPS key pair generated in ${keyStoreFile.getAbsolutePath}.") - loadedKeyStore - }) - keyStore - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private def createKeystoreParentDirectory(keyStoreDir: File): Unit = { - if (keyStoreDir.mkdirs()) { - logger.debug(s"Parent directory for keystore successfully created at ${keyStoreDir.getAbsolutePath}") - } else if (keyStoreDir.exists() && keyStoreDir.isDirectory) { - // File.mkdirs returns false when the directory already exists. - logger.debug(s"No need to create $keyStoreDir since it already exists.") - } else if (keyStoreDir.exists() && keyStoreDir.isFile) { - // File.mkdirs also returns false when there is a file for that path. - // A consumer will then fail to write the keystore file later, so we fail fast here. - throw new IllegalStateException( - s"$keyStoreDir exists, but it is NOT a directory, making it not possible to generate a key store file." - ) - } else { - // Not being able to create a directory inside target folder is weird, but if it happens - // a consumer will then fail to write the keystore file later, so we fail fast here. - throw new IllegalStateException( - s"Failed to create $keyStoreDir. Check if there is permission to create such folder." - ) - } - } - - /** Public only for consumption by Play/Lagom. */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def keyManagerFactory(appPath: File): KeyManagerFactory = { - val keyStore = createKeyStore(appPath) - - // Load the key and certificate into a key manager factory - val kmf = KeyManagerFactory.getInstance("SunX509") - kmf.init(keyStore, KeystoreSettings.keystorePassword) - kmf - } - - /** - * Close the given closeable quietly. - * - * Logs any IOExceptions encountered. - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def closeQuietly(closeable: Closeable): Unit = { - try { - if (closeable != null) { - closeable.close() - } - } catch { - case e: IOException => logger.warn(s"Error closing stream. Cause: $e") - } - } - -} diff --git a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CertificateGenerator.scala b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CertificateGenerator.scala deleted file mode 100644 index a34de352..00000000 --- a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CertificateGenerator.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2015 - 2025 Lightbend Inc. - */ - -package com.typesafe.sslconfig.ssl - -import java.math.BigInteger -import java.security._ -import java.security.cert._ -import java.util.Date - -import scala.concurrent.duration._ -import scala.concurrent.duration.FiniteDuration - -import org.joda.time.Instant -import sun.security.x509._ - -/** - * Used for testing only. This relies on internal sun.security packages, so cannot be used in OpenJDK. - */ -@deprecated( - "Uses internal sun.security.x509 classes. " + - "Works in Java 17 only with the `--add-exports=java.base/sun.security.x509=ALL-UNNAMED` flag. " + - "Does not work at all anymore with Java 21 and newer. " + - "To create certificates from code, use alternatives like Bouncy Castle instead.", - "0.7.0" -) -object CertificateGenerator { - - // http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyPairGenerator - // http://www.keylength.com/en/4/ - - /** - * Generates a certificate using RSA (which is available in 1.6). - */ - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def generateRSAWithSHA256( - keySize: Int = 2048, - from: Instant = Instant.now, - duration: FiniteDuration = 365.days - ): X509Certificate = { - val dn = "CN=localhost, OU=Unit Testing, O=Mavericks, L=Moon Base 1, ST=Cyberspace, C=CY" - val to = from.plus(duration.toMillis) - - val keyGen = KeyPairGenerator.getInstance("RSA") - keyGen.initialize(keySize, new SecureRandom()) - val pair = keyGen.generateKeyPair() - generateCertificate(dn, pair, from.toDate, to.toDate, "SHA256withRSA") - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def generateRSAWithSHA1( - keySize: Int = 2048, - from: Instant = Instant.now, - duration: FiniteDuration = 365.days - ): X509Certificate = { - val dn = "CN=localhost, OU=Unit Testing, O=Mavericks, L=Moon Base 1, ST=Cyberspace, C=CY" - val to = from.plus(duration.toMillis) - - val keyGen = KeyPairGenerator.getInstance("RSA") - keyGen.initialize(keySize, new SecureRandom()) - val pair = keyGen.generateKeyPair() - generateCertificate(dn, pair, from.toDate, to.toDate, "SHA1withRSA") - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def toPEM(certificate: X509Certificate) = { - val encoder = java.util.Base64.getMimeEncoder() - val certBegin = "-----BEGIN CERTIFICATE-----\n" - val certEnd = "-----END CERTIFICATE-----" - - val derCert = certificate.getEncoded() - val pemCertPre = encoder.encodeToString(derCert) - val pemCert = certBegin + pemCertPre + certEnd - pemCert - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - def generateRSAWithMD5( - keySize: Int = 2048, - from: Instant = Instant.now, - duration: FiniteDuration = 365.days - ): X509Certificate = { - val dn = "CN=localhost, OU=Unit Testing, O=Mavericks, L=Moon Base 1, ST=Cyberspace, C=CY" - val to = from.plus(duration.toMillis) - - val keyGen = KeyPairGenerator.getInstance("RSA") - keyGen.initialize(keySize, new SecureRandom()) - val pair = keyGen.generateKeyPair() - generateCertificate(dn, pair, from.toDate, to.toDate, "MD5WithRSA") - } - - @deprecated("Uses internal sun.security.x509 classes. Java 17 requires add-exports flags; Java 21 fails.", "0.7.0") - private[sslconfig] def generateCertificate( - dn: String, - pair: KeyPair, - from: Date, - to: Date, - algorithm: String - ): X509Certificate = { - - val info: X509CertInfo = new X509CertInfo - val interval: CertificateValidity = new CertificateValidity(from, to) - // I have no idea why 64 bits specifically are used for the certificate serial number. - val sn: BigInteger = new BigInteger(64, new SecureRandom) - val owner: X500Name = new X500Name(dn) - - info.set(X509CertInfo.VALIDITY, interval) - info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn)) - info.set(X509CertInfo.SUBJECT, owner) - info.set(X509CertInfo.ISSUER, owner) - info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic)) - info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)) - - info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(AlgorithmId.get(algorithm))) - var cert: X509CertImpl = new X509CertImpl(info) - val privkey: PrivateKey = pair.getPrivate - cert.sign(privkey, algorithm) - val algos = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId] - info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algos) - cert = new X509CertImpl(info) - cert.sign(privkey, algorithm) - cert - } -} diff --git a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509KeyManagerSpec.scala b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509KeyManagerSpec.scala index 41a7d98f..70eadd87 100644 --- a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509KeyManagerSpec.scala +++ b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509KeyManagerSpec.scala @@ -279,18 +279,6 @@ object CompositeX509KeyManagerSpec extends Specification { } "getCertificateChain" should { - "work fine" in { - val mockKeyManager = mock(classOf[X509KeyManager]) - val keyManager = new CompositeX509KeyManager(mkLogger, Seq(mockKeyManager)) - val alias = "alias" - val cert = CertificateGenerator.generateRSAWithSHA256() - - when(mockKeyManager.getCertificateChain(alias)).thenReturn(Array(cert)) - - val certChain = keyManager.getCertificateChain(alias = alias) - certChain must be_==(Array(cert)) - } - "return null" in { val mockKeyManager = mock(classOf[X509KeyManager]) val keyManager = new CompositeX509KeyManager(mkLogger, Seq(mockKeyManager)) diff --git a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509TrustManagerSpec.scala b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509TrustManagerSpec.scala index fb9eb8f5..b8ae835e 100644 --- a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509TrustManagerSpec.scala +++ b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/CompositeX509TrustManagerSpec.scala @@ -18,73 +18,7 @@ object CompositeX509TrustManagerSpec extends Specification { "CompositeX509TrustManager" should { - "with checkClientTrusted" should { - - "throws exception" in { - val mockTrustManager1 = mock(classOf[X509TrustManager]) - val mockTrustManager2 = mock(classOf[X509TrustManager]) - val trustManager = - new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager1, mockTrustManager2)) - - val certificate = CertificateGenerator.generateRSAWithSHA256() - val chain = Array[X509Certificate](certificate) - val authType = "" - - when(mockTrustManager1.checkClientTrusted(chain, authType)).thenThrow(new CertificateException("fake1")) - when(mockTrustManager2.checkClientTrusted(chain, authType)).thenThrow(new CertificateException("fake2")) - - trustManager - .checkClientTrusted(chain, authType) - .must(throwA[CompositeCertificateException].like { - case e: CompositeCertificateException => - val sourceExceptions = e.getSourceExceptions - sourceExceptions(0).getMessage must be_==("fake1") - sourceExceptions(1).getMessage must be_==("fake2") - }) - } - - "returns true" in { - val mockTrustManager = mock(classOf[X509TrustManager]) - val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) - - val certificate = CertificateGenerator.generateRSAWithSHA256() - val chain = Array[X509Certificate](certificate) - val authType = "" - - trustManager.checkClientTrusted(chain, authType) must not(throwA[Throwable].like { - case e: CompositeCertificateException => - val sourceExceptions = e.getSourceExceptions - sourceExceptions(0).getMessage must be_==("fake") - }) - } - - "returns true eventually" in { - val mockTrustManager1 = mock(classOf[X509TrustManager]) - val mockTrustManager2 = mock(classOf[X509TrustManager]) - val trustManager = - new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager1, mockTrustManager2)) - - val certificate = CertificateGenerator.generateRSAWithSHA256() - val chain = Array[X509Certificate](certificate) - val authType = "" - - when(mockTrustManager1.checkClientTrusted(chain, authType)).thenThrow(new CertificateException("fake1")) - mockTrustManager2.checkClientTrusted(chain, authType) - - trustManager.checkClientTrusted(chain, authType) must not(throwA[Throwable]) - } - } - "getAcceptedIssuers" should { - "work fine" in { - val mockTrustManager = mock(classOf[X509TrustManager]) - val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) - val certificate = CertificateGenerator.generateRSAWithSHA256() - when(mockTrustManager.getAcceptedIssuers).thenReturn(Array[X509Certificate](certificate)) - - val acceptedIssuers = trustManager.getAcceptedIssuers - acceptedIssuers(0) must_== certificate - } "throw exception when input exception" in { val mockTrustManager = mock(classOf[X509TrustManager]) @@ -98,41 +32,5 @@ object CompositeX509TrustManagerSpec extends Specification { }) } } - - "checkServerTrusted" should { - - "work fine" in { - val mockTrustManager = mock(classOf[X509TrustManager]) - val trustManager = new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager)) - val certificate = CertificateGenerator.generateRSAWithSHA256() - val chain = Array[X509Certificate](certificate) - val authType = "" - - trustManager.checkServerTrusted(chain, authType) must not(throwA[Throwable]) - } - - "throw an exception when nothing works" in { - val mockTrustManager1 = mock(classOf[X509TrustManager]) - val mockTrustManager2 = mock(classOf[X509TrustManager]) - val trustManager = - new CompositeX509TrustManager(mkLogger, trustManagers = Seq(mockTrustManager1, mockTrustManager2)) - - val certificate = CertificateGenerator.generateRSAWithSHA256() - val chain = Array[X509Certificate](certificate) - val authType = "" - - when(mockTrustManager1.checkServerTrusted(chain, authType)).thenThrow(new CertificateException("fake1")) - when(mockTrustManager2.checkServerTrusted(chain, authType)).thenThrow(new CertificateException("fake2")) - - trustManager - .checkServerTrusted(chain, authType) - .must(throwA[CompositeCertificateException].like { - case e: CompositeCertificateException => - val sourceExceptions = e.getSourceExceptions - sourceExceptions(0).getMessage must be_==("fake1") - sourceExceptions(1).getMessage must be_==("fake2") - }) - } - } } } diff --git a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/ConfigSSLContextBuilderSpec.scala b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/ConfigSSLContextBuilderSpec.scala index 51da328c..bbea6563 100644 --- a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/ConfigSSLContextBuilderSpec.scala +++ b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/ConfigSSLContextBuilderSpec.scala @@ -60,36 +60,6 @@ class ConfigSSLContextBuilderSpec extends Specification { } } - "build a key manager" in { - val info = SSLConfigSettings() - val keyManagerFactory = mockKeyManagerFactory - val trustManagerFactory = mockTrustManagerFactory - - val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) - - val keyStore = KeyStore.getInstance("PKCS12") - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(2048) // 2048 is the NIST acceptable key length until 2030 - val keyPair = keyPairGenerator.generateKeyPair() - val cert = FakeKeyStore.createSelfSignedCertificate(keyPair) - val password = "changeit" // cannot have a null password for PKCS12 in 1.6 - keyStore.load(null, password.toCharArray) - keyStore.setKeyEntry("playgenerated", keyPair.getPrivate, password.toCharArray, Array(cert)) - - val tempFile = java.io.File.createTempFile("privatekeystore", ".p12") - val out = java.nio.file.Files.newOutputStream(tempFile.toPath) - try { - keyStore.store(out, password.toCharArray) - } finally { - out.close() - } - val filePath = tempFile.getAbsolutePath - val keyStoreConfig = KeyStoreConfig(None, Some(filePath)).withStoreType("PKCS12").withPassword(Some(password)) - - val actual = builder.buildKeyManager(keyStoreConfig, SSLDebugConfig()) - actual must beAnInstanceOf[X509KeyManager] - } - "build a trust manager" in { val info = SSLConfigSettings() val keyManagerFactory = mockKeyManagerFactory @@ -132,29 +102,6 @@ class ConfigSSLContextBuilderSpec extends Specification { actual must beAnInstanceOf[CompositeX509TrustManager] } - "build a composite trust manager with data" in { - val info = SSLConfigSettings() - val keyManagerFactory = new DefaultKeyManagerFactoryWrapper(KeyManagerFactory.getDefaultAlgorithm) - val trustManagerFactory = new DefaultTrustManagerFactoryWrapper(TrustManagerFactory.getDefaultAlgorithm) - val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) - - val certificate = CertificateGenerator.generateRSAWithSHA256() - val certificateData = CertificateGenerator.toPEM(certificate) - - val trustStoreConfig = TrustStoreConfig(Some(certificateData), None).withStoreType("PEM") - val trustManagerConfig = TrustManagerConfig().withTrustStoreConfigs(List(trustStoreConfig)) - - val checkRevocation = false - val revocationLists = None - - val actual = - builder.buildCompositeTrustManager(trustManagerConfig, checkRevocation, revocationLists, SSLDebugConfig()) - - actual must beAnInstanceOf[CompositeX509TrustManager] - val issuers = actual.getAcceptedIssuers - issuers.size must beEqualTo(1) - } - "build a file based keystore builder" in { val info = SSLConfigSettings() val keyManagerFactory = mock(classOf[KeyManagerFactoryWrapper]) @@ -193,63 +140,6 @@ class ConfigSSLContextBuilderSpec extends Specification { val actual = builder.stringBuilder(data) actual must beAnInstanceOf[StringBasedKeyStoreBuilder] } - - "validate success of the keystore with a private key" in { - val keyStore = KeyStore.getInstance("PKCS12") - - // Generate the key pair - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(2048) // 2048 is the NIST acceptable key length until 2030 - val keyPair = keyPairGenerator.generateKeyPair() - - // Generate a self signed certificate - val cert = FakeKeyStore.createSelfSignedCertificate(keyPair) - - val password = "changeit" // null passwords throw exception in 1.6 - keyStore.load(null, password.toCharArray) - keyStore.setKeyEntry("playgenerated", keyPair.getPrivate, password.toCharArray, Array(cert)) - - val keyManagerFactory = mock(classOf[KeyManagerFactoryWrapper]) - val trustManagerFactory = mock(classOf[TrustManagerFactoryWrapper]) - - val ksc = KeyStoreConfig(None, Some("path")).withPassword(Some(password)) - val keyManagerConfig = KeyManagerConfig().withKeyStoreConfigs(List(ksc)) - - val info = SSLConfigSettings().withKeyManagerConfig(keyManagerConfig) - val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) - builder.validateStoreContainsPrivateKeys(ksc, keyStore) must beTrue - } - - "validate a failure of the keystore without a private key" in { - // must be JKS, PKCS12 does not support trusted certificate entries in 1.6 at least - // KeyStoreException: : TrustedCertEntry not supported (PKCS12KeyStore.java:620) - // val keyStore = KeyStore.getInstance("PKCS12") - val keyStore = KeyStore.getInstance(KeyStore.getDefaultType) - - // Generate the key pair - val keyPairGenerator = KeyPairGenerator.getInstance("RSA") - keyPairGenerator.initialize(2048) // 2048 is the NIST acceptable key length until 2030 - val keyPair = keyPairGenerator.generateKeyPair() - - // Generate a self signed certificate - val cert = FakeKeyStore.createSelfSignedCertificate(keyPair) - - val password = "changeit" // null passwords throw exception in 1.6 in PKCS12 - keyStore.load(null, password.toCharArray) - // Don't add the private key here, instead add a public cert only. - keyStore.setCertificateEntry("playgeneratedtrusted", cert) - - val keyManagerFactory = mock(classOf[KeyManagerFactoryWrapper]) - val trustManagerFactory = mock(classOf[TrustManagerFactoryWrapper]) - - val ksc = KeyStoreConfig(None, Some("path")).withPassword(Some(password)) - val keyManagerConfig = KeyManagerConfig().withKeyStoreConfigs(List(ksc)) - - val info = SSLConfigSettings().withKeyManagerConfig(keyManagerConfig) - val builder = new ConfigSSLContextBuilder(mkLogger, info, keyManagerFactory, trustManagerFactory) - - builder.validateStoreContainsPrivateKeys(ksc, keyStore) must beFalse - } } private def mockTrustManagerFactory = { diff --git a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/FakeKeyStoreSpec.scala b/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/FakeKeyStoreSpec.scala deleted file mode 100644 index 4c26aebb..00000000 --- a/ssl-config-core/src/test/scala/com/typesafe/sslconfig/ssl/FakeKeyStoreSpec.scala +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2015 - 2025 Lightbend Inc. - */ - -package com.typesafe.sslconfig.ssl - -import java.nio.file.Files - -import scala.util.Try - -import com.typesafe.sslconfig.util.NoopLogger -import org.specs2.mutable.Specification - -class FakeKeyStoreSpec extends Specification { - val mkLogger = NoopLogger.factory() - - "FakeKeyStore construction" should { - "return true on MD5 cert" in { - val weakCert = CertificateGenerator.generateRSAWithMD5() - val actual = new FakeKeyStore(mkLogger).certificateTooWeak(weakCert) - actual must beTrue - } - "return false on SHA256withRSA" in { - val strongCert = CertificateGenerator.generateRSAWithSHA256() - val actual = new FakeKeyStore(mkLogger).certificateTooWeak(strongCert) - actual must beFalse - } - - "build a keystore with a selfsigned certificate and trust on that certificate" in { - // create and persist a key store - val fakeKeyStore = new FakeKeyStore(mkLogger) - val basePath = Files.createTempDirectory("fake-keystore-spec-").toFile - val ksPath = fakeKeyStore.getKeyStoreFilePath(basePath) - fakeKeyStore.createKeyStore(basePath) - - // load the persisted key store - val fakeKeyStore2 = new FakeKeyStore(mkLogger) - val keyStore = fakeKeyStore2.createKeyStore(basePath) - try { - import com.typesafe.sslconfig.Compat.CollectionConverters._ - val certificates = keyStore.aliases().asScala.flatMap { alias => - Try(keyStore.getCertificate(alias)).toOption - } - certificates.size must be_==(2) // the self-signed and the trusted - } finally { - ksPath.delete() - } - } - - "build a keystore that's compatible with sslconfig.KeyStore" in { - // create and persist a key store - val fakeKeyStore = new FakeKeyStore(mkLogger) - val basePath = Files.createTempDirectory("fake-keystore-spec-").toFile - val ksPath = fakeKeyStore.getKeyStoreFilePath(basePath) - fakeKeyStore.createKeyStore(basePath) - - // load the persisted key store using sslconfig.KeyStore - val keyStore = new FileBasedKeyStoreBuilder( - FakeKeyStore.KeystoreSettings.KeystoreType, - ksPath.getAbsolutePath, - Some(FakeKeyStore.KeystoreSettings.keystorePassword) - ).build() - try { - import com.typesafe.sslconfig.Compat.CollectionConverters._ - val certificates = keyStore.aliases().asScala.flatMap { alias => - Try(keyStore.getCertificate(alias)).toOption - } - certificates.size must be_==(2) // the self-signed and the trusted - } finally { - ksPath.delete() - } - } - - } - -}