为什么我的服务器没有在Play 2.6.x中请求客户端证书? [英] Why isn't my server requesting a client cert in Play 2.6.x?

查看:113
本文介绍了为什么我的服务器没有在Play 2.6.x中请求客户端证书?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试通过遵循 play-tls-example来启用客户端身份验证.由于这只是一个实验,因此我正在生成自签名证书.

I'm trying to enable client authentication by following the play-tls-example. Since this is just an experiment, I'm generating self-signed certs.

我有以下SSL引擎提供程序:

I have the following SSL engine provider:

package https

import java.nio.file.{FileSystems, Files}
import java.security.KeyStore

import play.core.ApplicationProvider
import play.server.api._
import javax.net.ssl._
import play.api.Configuration

class CustomSSLEngineProvider(appProvider: ApplicationProvider) extends SSLEngineProvider {

  private val config: Configuration = appProvider.current.get.configuration

  private val certificateDirectory: String = config.get[String]("certificateDirectory")

  private def readTrustInputStream(): java.io.InputStream = {
    val keyPath = FileSystems.getDefault.getPath(certificateDirectory, "clientca.jks")
    Files.newInputStream(keyPath)
  }

  private def readPassword(): Array[Char] = {
    val passwordPath = FileSystems.getDefault.getPath(certificateDirectory, "password")
    Files.readAllLines(passwordPath).get(0).toCharArray
  }

  private def readTrustManagers(): Array[TrustManager] = {
    val password = readPassword()
    val trustInputStream = readTrustInputStream()
    try {
      val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
      keyStore.load(trustInputStream, password)
      val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
      tmf.init(keyStore)
      tmf.getTrustManagers
    } finally {
      trustInputStream.close()
    }
  }

  private def readKeyInputStream(): java.io.InputStream = {
    val keyPath = FileSystems.getDefault.getPath(certificateDirectory, "localhost.jks")
    Files.newInputStream(keyPath)
  }

  private def readKeyManagers(): Array[KeyManager] = {
    val password = readPassword()
    val keyInputStream = readKeyInputStream()
    try {
      val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
      keyStore.load(keyInputStream, password)
      val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
      kmf.init(keyStore, password)

      kmf.getKeyManagers
    } finally {
      keyInputStream.close()
    }
  }

  private def createSSLContext(applicationProvider: ApplicationProvider): SSLContext = {
    val keyManagers = readKeyManagers()
    val trustManagers = readTrustManagers()

    // Configure the SSL context to use TLS
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(keyManagers, trustManagers, null)
    sslContext
  }

  override def createSSLEngine(): SSLEngine = {

    val sslContext = createSSLContext(appProvider)

    // Start off with a clone of the default SSL parameters...
    val sslParameters = sslContext.getDefaultSSLParameters

    // Tells the server to ignore client's cipher suite preference.
    // http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#cipher_suite_preference
    sslParameters.setUseCipherSuitesOrder(true)

    // http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SSLParameters
    val needClientAuth = true
    sslParameters.setNeedClientAuth(needClientAuth)

    // Clone and modify the default SSL parameters.
    val engine = sslContext.createSSLEngine
    engine.setSSLParameters(sslParameters)

    println(s"Need client auth: ${sslParameters.getNeedClientAuth}")

    engine
  }
}

clientca.jks是根据以下脚本配置的:

and the clientca.jks was configured according to the following script:

#!/bin/bash

export PW=`cat password`

# Create a self signed certificate & private key to create a root certificate authority.
keytool -genkeypair -v \
  -alias clientca \
  -keystore client.jks \
  -dname "CN=clientca, OU=foo, O=bar, L=baz, ST=Ohio, C=US" \
  -keypass:env PW \
  -storepass:env PW \
  -keyalg EC \
  -keysize 256 \
  -ext KeyUsage:critical="keyCertSign" \
  -ext BasicConstraints:critical="ca:true" \
  -validity 365

# Create another key pair that will act as the client.  We want this signed by the client CA.
keytool -genkeypair -v \
  -alias client \
  -keystore client.jks \
  -dname "CN=client, OU=foo, O=bar, L=baz, ST=Ohio, C=US" \
  -keypass:env PW \
  -storepass:env PW \
  -keyalg EC \
  -keysize 256 \

# Create a certificate signing request from the client certificate.
keytool -certreq -v \
  -alias client \
  -keypass:env PW \
  -storepass:env PW \
  -keystore client.jks \
  -file client.csr

# Make clientCA create a certificate chain saying that client is signed by clientCA.
keytool -gencert -v \
  -alias clientca \
  -keypass:env PW \
  -storepass:env PW \
  -keystore client.jks \
  -infile client.csr \
  -outfile client.crt \
  -ext EKU="clientAuth" \
  -rfc

# Export the client-ca certificate from the keystore.  This goes to nginx under "ssl_client_certificate"
# and is presented in the CertificateRequest.
keytool -export -v \
  -alias clientca \
  -file clientca.crt \
  -storepass:env PW \
  -keystore client.jks \
  -rfc

# Import the signed client certificate back into client.jks.  This is important, as JSSE won't send a client
# certificate if it can't find one signed by the client-ca presented in the CertificateRequest.
keytool -import -v \
  -alias client \
  -file client.crt \
  -keystore client.jks \
  -storetype JKS \
  -storepass:env PW

# Export the client CA to pkcs12, so it's safe. 
keytool -importkeystore -v \
  -srcalias clientca \
  -srckeystore client.jks \
  -srcstorepass:env PW \
  -destkeystore client.p12 \
  -deststorepass:env PW \
  -deststoretype PKCS12

# Import the client CA's public certificate into a JKS store for Play Server to read (we don't use
# the PKCS12 because it's got the CA private key and we don't want that.
keytool -import -v \
  -alias clientca \
  -file clientca.crt \
  -keystore clientca.jks \
  -storepass:env PW << EOF
yes
EOF

# List out the contents of client.jks just to confirm it.
keytool -list -v \
  -keystore client.jks \
  -storepass:env PW

和我的localhost.jks由以下脚本生成:

and my localhost.jks is generated by the following script:

#!/bin/bash

export PW=`cat password`

# Create a server certificate, tied to localhost
keytool -genkeypair -v \
  -alias localhost \
  -dname "CN=localhost, OU=foo, O=bar, L=baz, ST=Ohio, C=US" \
  -keystore localhost.jks \
  -keypass:env PW \
  -storepass:env PW \
  -keyalg EC \
  -keysize 256 \
  -validity 385

# Create a certificate signing request for localhost
keytool -certreq -v \
  -alias localhost \
  -keypass:env PW \
  -storepass:env PW \
  -keystore localhost.jks \
  -file localhost.csr

# Tell ca to sign the localhost certificate. 
# Technically, digitalSignature for DHE or ECDHE, keyEncipherment for RSA 
keytool -gencert -v \
  -alias ca \
  -keypass:env PW \
  -storepass:env PW \
  -keystore ca.jks \
  -infile localhost.csr \
  -outfile localhost.crt \
  -ext KeyUsage:critical="digitalSignature,keyEncipherment" \
  -ext EKU="serverAuth" \
  -ext SAN="DNS:localhost" \
  -rfc

# Tell localhost.jks it can trust ca as a signer.
keytool -import -v \
  -alias ca \
  -file ca.crt \
  -keystore localhost.jks \
  -storetype JKS \
  -storepass:env PW << EOF
yes
EOF

# Import the signed certificate back into localhost.jks 
keytool -import -v \
  -alias localhost \
  -file localhost.crt \
  -keystore localhost.jks \
  -storetype JKS \
  -storepass:env PW

以及当我使用以下相关配置在生产环境中运行时:

and when I run in production with the following relevant config:

http.port=disabled 
https.port = 9443 
play.server.https.engineProvider=https.CustomSSLEngineProvider 
certificateDirectory=/path/to/certs/ 
play.server.https.keyStore.path=/path/to/certs/localhost.jks 
play.server.https.keyStore.password=`cat /path/to/certs/password` # actual string
jdk.tls.rejectClientInitiatedRenegotiation=true

当我发出不安全的curl时,我发现服务器没有请求客户端证书:curl -k https://localhost:9443:

I find that my server doesn't request the client cert when I issue an insecure curl: curl -k https://localhost:9443:

13:35:13.173 [application-akka.actor.default-dispatcher-4] INFO akka.event.slf4j.Slf4jLogger - Slf4jLogger started
13:35:13.182 [application-akka.actor.default-dispatcher-4] DEBUG akka.event.EventStream - logger log1-Slf4jLogger started
13:35:13.185 [application-akka.actor.default-dispatcher-4] DEBUG akka.event.EventStream - Default Loggers started
13:35:13.213 [main] DEBUG play.api.libs.concurrent.ActorSystemProvider - Starting application default Akka system: application
13:35:13.368 [main] DEBUG controllers.AssetsConfiguration - Using the following cache configuration for assets:
     enableCaching = true
     enableCacheControl = true
     defaultCacheControl = public, max-age=3600
     aggressiveCacheControl = public, max-age=31536000, immutable
     configuredCacheControl:


13:35:13.414 [main] INFO play.api.Play - Application started (Prod)
13:35:13.678 [application-akka.actor.default-dispatcher-2] DEBUG com.typesafe.sslconfig.akka.AkkaSSLConfig - Initializing AkkaSSLConfig extension...
13:35:13.680 [application-akka.actor.default-dispatcher-2] DEBUG com.typesafe.sslconfig.akka.AkkaSSLConfig - buildHostnameVerifier: created hostname verifier: com.typesafe.sslconfig.ssl.DefaultHostnameVerifier@4acf72b6
13:35:14.046 [application-akka.actor.default-dispatcher-3] DEBUG akka.io.TcpListener - Successfully bound to /0:0:0:0:0:0:0:0:9443
13:35:14.054 [main] INFO play.core.server.AkkaHttpServer - Listening for HTTPS on /0:0:0:0:0:0:0:0:9443
13:35:20.420 [application-akka.actor.default-dispatcher-3] DEBUG akka.io.TcpListener - New connection accepted
Need client auth: true
13:35:20.597 [application-akka.actor.default-dispatcher-3] DEBUG akka.stream.impl.io.TLSActor - closing output

尽管我的服务器需要客户端身份验证,但从不提示客户端提供证书,并且可以成功启动不安全的连接.为什么会这样?

Despite my server requiring client auth, the client is never prompted for a cert and successfully initiates an insecure connection. Why is this happening?

推荐答案

有一个未解决的问题与此相关.

要使其工作,需要在项目中启用sbt PlayAkkaHttp2Support,以尝试进行客户端身份验证和

To make this work, the sbt PlayAkkaHttp2Support will need to be enabled in the project attempting to do client auth and java agent will need to be updated to circumvent a JDK 161 issue.

这篇关于为什么我的服务器没有在Play 2.6.x中请求客户端证书?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆