【问题标题】:curl cannot handshake https server created with Nodejs v14.16.0 tls.createSecureContextcurl 无法与使用 Nodejs v14.16.0 tls.createSecureContext 创建的 https 服务器握手
【发布时间】:2021-07-04 04:04:52
【问题描述】:

我已经设置了一个 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 -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

【问题讨论】:

  • 您设置了requestCert:true, rejectUnauthorizhed:true,它告诉服务器拒绝任何未使用有效证书和密钥进行身份验证的客户端。您的 curl 命令没有进行身份验证(根本没有验证),因此它被拒绝了——正如你所说的那样
  • 即使这些选项设置为 false,我也有同样的握手问题
  • curl --trace-ascii blahblah.log 将转储 lot 的调试消息,包括。 TLS握手的细节。
  • 我看到它使用 tls v1.2 但遇到错误。我将使用 curl 跟踪来编辑问题,因为它太长了,我无法将它发布到 cmets 中

标签: node.js ssl curl


【解决方案1】:

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

const server = https.createServer(
    {...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' }, 
   (req, res) => stuff )

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

【讨论】:

  • 谢谢,我使用 tls.createSecureContext 的全部理由是这样我就可以在 letencrypt 更新时热交换我的密钥,而无需重新启动服务器。我可以使用 https.createServer 创建具有相同密钥(和缓冲区)的 https 服务器。如果我通过 https.createServer 创建服务器然后调用server.setSecureContext(tls.createSecureContext(...)); @dave_thompson_085,我可以热交换我的letsencrypt 密钥吗?
  • 我将把它标记为答案,因为它指出了错误的核心原因。 nodejs.org/docs/latest-v14.x/api/… > server.setSecureContext(options)# 添加于:v11.0.0 options 包含 tls.createSecureContext() 选项参数(例如密钥、证书、ca 等)的任何可能属性的对象。 server.setSecureContext() 方法替换现有服务器的安全上下文。与服务器的现有连接不会中断。
猜你喜欢
  • 2013-01-14
  • 1970-01-01
  • 2015-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-07
  • 2014-03-29
相关资源
最近更新 更多