curl 无法握手使用 Nodejs v14.16.0 tls.createSecureContext 创建的 https 服务器

时间:2021-04-08 09:43:14

标签: node.js ssl curl

我已经设置了一个 HTTPS 服务器(nodejs v14.16.0)和来自letsEncrypt 的证书(在使用 https.createServer 的应用程序的当前版本中工作)。不幸的是,curl 无法成功连接到我的 HTTPS 服务器。我收到以下错误

routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

这是我的服务器的最低可重现版本

const https = require('https')
const tls = require('tls');
const fs = require('fs');
const constants = require('constants');

tls.DEFAULT_ECDH_CURVE = "auto"
require('dotenv').config()

const certOpts = {
    key: `${process.env.KEY_PATH}/privkey.pem`,
    cert: `${process.env.KEY_PATH}/cert.pem`,
    ca: `${process.env.KEY_PATH}/chain.pem`,
};

  /**
   * 
   * @param {Record<string, string>} filePathMap
   * @returns {Record<string, Buffer>} 
   */
  function getBuffersFromFilePathMap(filePathMap) {
    const bufferMap = {}

    for (const path in filePathMap) {
      if (Object.hasOwnProperty.call(filePathMap, path)) {
        bufferMap[path] = fs.readFileSync(filePathMap[path]);
      }
    }

    return bufferMap;
  }

const buffers = getBuffersFromFilePathMap(certOpts);


function createContext() {
    return tls.createSecureContext({
        ...buffers,
        secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_TLSv1 | constants.SSL_OP_NO_TLSv1_1,
        maxVersion:'TLSv1.2'
    });
}

const server = https.createServer({ secureContext: createContext()  }, (req, res) => {
    // eslint-disable-next-line no-console
    console.log('connect');
    res.writeHead(200);
    res.write('Hello World!\n');
    res.end('Goodbye World!\n');
  });

server.listen(9999, () => console.log('Server up: ', server.address()));

这是curl --version

的输出
curl 7.61.1 (x86_64-redhat-linux-gnu) libcurl/7.61.1 OpenSSL/1.0.2k zlib/1.2.8 libidn2/2.3.0 libpsl/0.6.2 (+libicu/50.1.2) libssh2/1.4.2 nghttp2/1.31.1
Release-Date: 2018-09-05
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy PSL 

据我所知,ssl3 可能不受支持。

curl 输出

$ curl -k https://localhost:9999
curl: (35) error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

卷曲痕迹

== Info: Rebuilt URL to: https://localhost:9999/
== Info:   Trying 127.0.0.1...
== Info: TCP_NODELAY set
== Info: Connected to localhost (127.0.0.1) port 9999 (#0)
== Info: ALPN, offering h2
== Info: ALPN, offering http/1.1
== Info: Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
== Info: successfully set certificate verify locations:
== Info:   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
== Info: TLSv1.2 (OUT), TLS header, Certificate Status (22):
=> Send SSL data, 5 bytes (0x5)
0000: .....
== Info: TLSv1.2 (OUT), TLS handshake, Client hello (1):
=> Send SSL data, 512 bytes (0x200)
0000: .......*.c....}.5.H>^..e\;)}.zEW.....d....0.,.(.$.............k.
0040: j.i.h.9.8.7.6.........2...*.&.......=.5.../.+.'.#.............g.
0080: @.?.>.3.2.1.0.........E.D.C.B.1.-.).%.......<./...A.............
00c0: ............3.........localhost......................... .......
0100: ..............................3t.........h2.http/1.1............
0140: ................................................................
0180: ................................................................
01c0: ................................................................
== Info: TLSv1.2 (IN), TLS header, Unknown (21):
<= Recv SSL data, 5 bytes (0x5)
0000: .....
== Info: TLSv1.2 (IN), TLS alert, handshake failure (552):
<= Recv SSL data, 2 bytes (0x2)
0000: .(
== Info: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

1 个答案:

答案 0 :(得分:0)

好的,尽管 tls.createSecureContext 的文档说结果“可用作多个 tls API(例如 tls.createServer)的参数”,但实际上并非如此。它接受server.addContext(对于虚拟主机或更确切地说是 SNI 值处理程序)tls.connect(对于客户端)tls.createSecurePair(已弃用)和 {{ 1}}(低级),但 new TLSSocket 只采用相同的选项,createServer 不是实际的 createSecureContext。由于您没有以可用的形式提供所需的密钥和证书,并且默认情况下 OpenSSL 禁用匿名密码套件(大多数客户端无论如何都不提供),因此所有握手都会因 no_shared_cipher 失败。试试:

SecureContext

顺便说一句,当您收到 request 而不是连接时,会调用 req,res=> 函数;一个合适的客户端只会在需要发出请求时才连接,但可能在不发出请求的情况下进行连接,相反,在一个连接上执行多个请求也可以。