我正在尝试使用happstack-server-tls创建一个 Web 服务器,该服务器将使用由私有 CA 签名的证书。不幸的是,TLS 握手似乎只有在我给服务器一个自签名证书时才能成功。Wireshark 显示,当我的服务器使用由我的私有 CA 签名的证书时,它不会向客户端发送 Server Hello 消息,而是发送 Fatal (2) Alert 消息报告握手失败 (40)。
这不是 Web 浏览器拒绝服务器证书的情况;Wireshark 表明,TLS 握手甚至从未到达服务器向浏览器提供证书的地步。这也不是未能就加密算法达成一致的情况,因为服务器使用自签名证书运行没有问题,该证书使用与我想要使用的算法相同的算法。证书本身似乎是有效的,因为它openssl s_server
在命令行上按预期工作,并且 happstack-server-tls 似乎使用 OpenSSL 进行 TLS 操作,所以我认为证书的生成方式不是问题。
我需要做什么才能让 happstack-server-tls 使用非自签名证书?
下面是一个最小的 Haskell 程序,它使用 happstack-server-tls 在端口 8443 上运行 Web 服务器来演示该问题。它最多需要三个参数:包含服务器私钥的文件、包含服务器证书的文件和(可选)包含 CA 证书的文件。
module Main ( main ) where
import Happstack.Server.Response (ok)
import Happstack.Server.SimpleHTTPS (TLSConf (..), nullTLSConf, simpleHTTPS)
import System.Environment (getArgs)
main :: IO ()
main = do args <- getArgs
case args of
[keyFile, certFile] -> runServer keyFile certFile Nothing
[keyFile, certFile, caFile] -> runServer keyFile certFile (Just caFile)
runServer :: FilePath -> FilePath -> Maybe FilePath -> IO ()
runServer keyFile certFile caFile = simpleHTTPS conf $ ok ":-)"
where conf = nullTLSConf { tlsPort = 8443
, tlsCert = certFile
, tlsKey = keyFile
, tlsCA = caFile
}
上述程序使用通过以下脚本生成的自签名证书工作:
#! /bin/sh
openssl ecparam -name secp384r1 -genkey -out selfsigned.key
openssl req -new -x509 -days 365 -key selfsigned.key -out selfsigned.crt
并运行以下命令:
runghc Main.hs selfsigned.key selfsigned.crt
它的工作原理是 Web 浏览器可以成功连接到服务器(尽管正如预期的那样抱怨自签名证书)。
但是,使用以下脚本生成的非自签名证书不起作用,该脚本首先创建一个新的私有 CA,然后使用该 CA 为服务器生成证书:
#! /bin/sh
rm -rf ca
mkdir ca
mkdir ca/newcerts
mkdir ca/private
touch ca/index.txt
echo "100001" >ca/serial
openssl ecparam -name secp384r1 -genkey -out ca/private/cakey.pem
openssl req -new -x509 -days 365 -key ca/private/cakey.pem -out ca/cacert.pem
openssl ecparam -name secp384r1 -genkey -out onelevel-server.key
openssl req -new -days 365 -key onelevel-server.key -out onelevel-server.req
openssl ca -config onelevel.openssl.cnf -in onelevel-server.req -out onelevel-server.crt
当我运行以下命令时:
runghc Main.hs onelevel-server.key onelevel-server.crt ca/cacert.pem
Web 浏览器无法连接,因为(如 Wireshark 所示)初始 TLS 握手失败。
但是,Web 浏览器在用作使用相同证书的服务器时可以建立 TLS 连接:openssl
openssl s_server -accept 8443 -key onelevel-server.key -cert onelevel-server.crt -CAfile ca/cacert.pem
为了完成示例,这里是onelevel.openssl.cnf
前面引用的配置文件,它提供了私有 CA 的配置:
[ ca ]
default_ca = CA_onelevel
[ CA_onelevel ]
dir = ./ca
certs = $dir/certs
crl_dir = $dir/crl
database = $dir/index.txt
new_certs_dir = $dir/newcerts
certificate = $dir/cacert.pem
serial = $dir/serial
crlnumber = $dir/crlnumber
crl = $dir/crl.pem
private_key = $dir/private/cakey.pem
x509_extensions = onelevel_cert
name_opt = ca_default
cert_opt = ca_default
default_days = 365
default_crl_days = 30
default_md = default
preserve = no
policy = policy_match
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = match
commonName = supplied
emailAddress = optional
[ onelevel_cert ]
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer