我用C ++写了一些软件,现在我正试图获取GDAX /products列表(目前主要是作为测试)。

更新:我想补充一下,该连接实际上是到cloudflare的,而不是直接到GDAX的。因此,可能是cloudflare而不是直接与GDAX服务器有关的问题。

BIO_do_connect()函数每次返回-1。它并没有给我带来太多好处。我在日志中写了以下内容。所以主要的信息是错误发生在s23_clnt.c的第794行...


  OpenSSL:[336031996/20 | 119 | 252]:[]:[]:[]:[s23_clnt.c]:[794]:[(无详细信息)]


我可以说这意味着TCP连接本身发生了,但是不知何故它无法获得可接受的安全连接。在机器只使用一些旧的加密方法之前,我已经看到过类似的行为。但是我检查了nmap,连接肯定支持TLS 1.2。我运行以下命令并得到:

nmap --script ssl-enum-ciphers api-public.sandbox.gdax.com


我得到以下输出,证明端口443是开放的,并且具有所有必要的必要加密方案。

Starting Nmap 7.01 ( https://nmap.org ) at 2018-03-24 21:57 PDT
Nmap scan report for api-public.sandbox.gdax.com (104.28.30.142)
Host is up (0.016s latency).
Other addresses for api-public.sandbox.gdax.com (not scanned): 104.28.31.142
Not shown: 996 filtered ports
PORT     STATE SERVICE
80/tcp   open  http
443/tcp  open  https
| ssl-enum-ciphers:
|   TLSv1.0:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|     compressors:
|       NULL
|     cipher preference: server
|   TLSv1.1:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|_  least strength: C
8080/tcp open  http-proxy
8443/tcp open  https-alt
| ssl-enum-ciphers:
|   TLSv1.0:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
|     compressors:
|       NULL
|     cipher preference: server
|   TLSv1.1:
|     ciphers:
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|   TLSv1.2:
|     ciphers:
|       TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (secp256r1) - A
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
|       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (secp256r1) - A
|       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp256r1) - A
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|_  least strength: C

Nmap done: 1 IP address (1 host up) scanned in 8.07 seconds


现在,我针对正常的REST API地址(api.gdax.com)和我自己的网站(www.m2osw.com)测试了我的代码,加密部分工作正常。我真的看不到我会做错什么,除非它的SSL设置很奇怪,否则沙盒URL(api-public.sandbox.gdax.com)会失败。

请注意,当我尝试连接到端口80(我知道这是错误的)时,它会按预期工作。也就是说,我得到了一个301,其位置与协议HTTPS的地址相同。

有人在连接到沙箱时遇到了问题吗?

有所有被调用的函数。此时,完整的实现可在github中的1111行附近的libsnapwebsites中找到(bio_client构造函数)。

// called once on initialization
SSL_library_init();
ERR_load_crypto_strings();
ERR_load_SSL_strings();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
crypto_thread_setup();

// call each time we connect
SSL_CTX * ssl_ctx = SSL_CTX_new(SSLv23_client_method();
SSL_CTX_set_verify_depth(ssl_ctx, 4);
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_COMPRESSION);

// just in case I tried with "ALL", but no difference
//SSL_CTX_set_cipher_list(ssl_ctx, "ALL");
SSL_CTX_set_cipher_list(ssl_ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");

SSL_CTX_load_verify_locations(ssl_ctx, NULL, "/etc/ssl/certs");
BIO * bio = BIO_new_ssl_connect(ssl_ctx);
SSL * ssl(nullptr);
BIO_get_ssl(bio, &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
BIO_set_conn_hostname(bio, const_cast<char *>(addr.c_str()));
BIO_set_conn_int_port(bio, &port);

int const cr(BIO_do_connect(bio));
// here cr == -1 when I use api-public.sandbox.gdax.com


再次,如果我使用cr > 0,则此代码可以查找和api.gdax.com,所以我现在真的很茫然!而且我知道TCP连接本身会发生,因为它进入了那部分发生之后的s23_clnt.c。

最佳答案

好的,我花了整整一天的时间(大约半天),将我的代码与libcurl的代码(也使用OpenSSL的SSL_CTXSSL结构)进行了比较。该代码看起来几乎相同...除了libcurl版本包括以下内容:

[...]
switch(data->set.ssl.version) {
case CURL_SSLVERSION_DEFAULT:
  [...]
  use_sni(TRUE);
  break;

[...]
if((0 == Curl_inet_pton(AF_INET, conn->host.name, &addr)) &&
   (0 == Curl_inet_pton(AF_INET6, conn->host.name, &addr)) &&
   sni &&
   !SSL_set_tlsext_host_name(connssl->handle, conn->host.name))
  infof(data, "WARNING: failed to configure server name indication (SNI) "
        "TLS extension\n");
[...]


如我们所见,它们有一个名为SNI的东西,如果为true,则设置TLS扩展名为Hostname。如果SSL HELLO消息中未包含该主机名参数,则GDAX服务器(或者很可能是cloudflare服务器)立即拒绝连接。

因此,最终,我将强制使用SNI(服务器名称标识),这样一来,它就可能在更多服务器上运行。 libcurl允许不包含它,但看起来您应该始终拥有它。至少不应该伤害。

BIO_get_ssl(bio, &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
SSL_set_tlsext_host_name(ssl, const_cast<char *>(addr.c_str()));
BIO_set_conn_hostname(bio, const_cast<char *>(addr.c_str()));


请注意,必须为SSL_set_tlsext_host_name()函数指定正确的主机名,而不是IPv4或IPv6地址。

09-19 23:32