【问题标题】:How to connect to secure web site (https:)?如何连接到安全网站 (https:)?
【发布时间】:2021-05-20 18:49:32
【问题描述】:

我正在尝试连接到安全 (https:) 网站并执行写入/读取操作。我试图找到一个使用 OpenSSL 的简单示例,但所有示例似乎都是针对早期版本的,并且文档差异很大。我尽我所能,制作了一些模组,并创建了一个简单的“Hello, World”SSL 程序(如下)。它主要工作,但在 SSL_write() 上失败。错误是 SSL_ERROR_SSL。

//  client.c - SSL client program
// Uses OpenSSL v1.1.1i for Win32

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include "openssl/ssl.h"    // openSSL includes
#include "client.h"

// LINK TO THESE LIBRARIES
#pragma comment( lib, "libssl.lib" )
#pragma comment( lib, "Ws2_32.lib" )

//@ Main program
int
main(
  int argc,     // count of args
  char **argv)      //ptr to array or arg strings
{
    #define HOSTNAME "www.fax.plus"     // secure host
    #define PORT     443                // https port
    int i,ok;
    WSADATA data;
    SSL_METHOD *method;
    SSL_CTX *ctx;
    SSL *ssl;
    SOCKET s;
    char request[1024+1];
    SSL_METHOD *TLSv1_2_client_method();    // not defined in includes!?

    i = WSAStartup( 0x002, &data );                     // start winsock
    if( i ) sslFatalExit( "on WSAStartup" );
    printf( "OK: WinSock Started.\n" );

    method = TLSv1_2_client_method();                   // get method
    ctx = SSL_CTX_new( method );                        // get ctx
    if( !ctx ) sslFatalExit( "on SSL_CTX_new" );
    printf( "OK: SSL CTX created\n" );

    ssl = SSL_new( ctx );                               // get ssl
    if( !ssl ) sslFatalExit( "on SSL_new" );
    printf( "OK: SSL ssl created.\n" );

    s = sslConnect( HOSTNAME, PORT );                   // connect to host
    if( s <= 0 ) sslFatalExit( "on sslConnect" );
    printf( "OK: Connected to %s:%u\n", HOSTNAME, PORT );

    ok = SSL_set_fd( ssl, s );                          // set ssl socket
    if( !ok ) sslFatalExit( "SSL_set_fd" );
    printf( "OK: SSL socket set.\n" );

    strncpy( request, "GET / HTTP/1.00\r\n\r\n", 1024 );
    i = SSL_write( ssl, request, strlen( request ) );   // write request to host
    if( i != 1 ) sslReportError( i, ssl );    // ALWAYS FAILS !

    // more...

    return( 0 );
}

//@ Connect to host:port
SOCKET
sslConnect(
  char *hostname,   // host name
  WORD port)        // port number
{
    int i,sd;
    struct hostent *host;
    struct sockaddr_in addr;

    host = gethostbyname( hostname );
    if( host == NULL ) {
      perror( hostname );
      return( 0 );
    }
    sd = socket(PF_INET, SOCK_STREAM, 0 );
    ZeroMemory( &addr, sizeof(addr ) );
    addr.sin_family = AF_INET;
    addr.sin_port = htons( port );
    addr.sin_addr.s_addr = *(long *)(host->h_addr);
    i = connect( sd, (struct sockaddr*)&addr, sizeof(addr));
    if( i != 0 ) {
      perror( hostname );
      return( 0 );
    }
    return( sd );
    //r Returns connected socket or 0 on error
}

//@ Report SSL error
void
sslReportError(
  int code,
  SSL *ssl)
{
    int sslerr;
    char *msg;
    sslerr = SSL_get_error( ssl, code );
    msg = sslErrorName( sslerr );
    printf( "ERROR: %s\n", msg );
    sslFatalExit( "on SSL_write" );

}

//@ Get string of ssl error code
char *
sslErrorName(
  int code) // code returned by SSL_get_error()
{
    switch( code ) {
      case SSL_ERROR_NONE:              return( "[0] SSL_ERROR_NONE" );
      case SSL_ERROR_SSL:               return( "[1] SSL_ERROR_SSL" );
      case SSL_ERROR_WANT_READ:         return( "[2] SSL_ERROR_WANT_READ" );
      case SSL_ERROR_WANT_WRITE:        return( "[4] SSL_ERROR_WANT_WRITE" );
      case SSL_ERROR_WANT_X509_LOOKUP:  return( "[4] SSL_ERROR_WANT_X509_LOOKUP" );
      case SSL_ERROR_SYSCALL:           return( "[5] SSL_ERROR_SYSCALL" );
      case SSL_ERROR_ZERO_RETURN:       return( "[6] SSL_ERROR_ZERO_RETURN" );
      case SSL_ERROR_WANT_CONNECT:      return( "[7] SSL_ERROR_WANT_CONNECT" );
      case SSL_ERROR_WANT_ACCEPT:       return( "[8] SSL_ERROR_WANT_ACCEPT" );
      //case SSL_ERROR_WANT_ASYNC:      return( "[9] SSL_ERROR_WANT_ASYNC" );
      //case SSL_ERROR_WANT_ASYNC_JOB:  return( "[10] SSL_ERROR_WANT_ASYNC_JOB" );
      //case SSL_ERROR_WANT_CLIENT_HELLO_CB:    return( "[11] SSL_ERROR_WANT_CLIENT_HELLO_CB" );
      default:              return( "ERROR_UNKNOWN" );
      //r Returns string of error code
    }
}

//@ Print msg, pause and exit
void
sslFatalExit(
  char *msg)    // msg to print
{
    printf( "FATAL ERROR: %s\n", msg );
    printf( "Press any key to exit...\n" );
    getch();
    exit( 1 );
}

程序输出:

OK: WinSock Started.
OK: SSL CTX created
OK: SSL ssl created.
OK: Connected to www.fax.plus:443
OK: SSL socket set.
ERROR: [1] SSL_ERROR_SSL
FATAL ERROR: on SSL_write
Press any key to exit...

我的尝试和奇怪的地方

  • 许多示例都调用了诸如“SSL_add_all_algorithms()”之类的东西,但这在 libssl.lib 中没有得到解决
  • 一些称为 SSL_init_ssl。这已解决,但导致 'SSL_CTX_new()' 失败。一些文档表示这是不必要的。
  • ''TLSv1_2_client_method()' 已解决,但未包含在 SSL 包含文件中,因此我自己声明了。

有人对我的问题有任何见解或替代方法吗?

【问题讨论】:

  • 检查在libssl之前添加lcrypto是否解决链接问题,同时检查链接是动态的还是静态的
  • 试过但结果相同。库是静态的。
  • 你用过哪些库?
  • 我制作了一个运行良好的windows客户端程序,它需要与静态库和程序目录中的dll链接。
  • 你在某处调用 SSL_connect 吗?

标签: ssl https openssl


【解决方案1】:

这里有一些开箱即用的客户端代码。

#define _WINSOCKAPI_
#include <iostream>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <mstcpip.h>
#include <openssl/ssl.h>

#pragma comment (lib, "Ws2_32")
#pragma comment (lib, "libssl")
#pragma comment (lib, "libcrypto")

int main() {
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("Failed to initialize wsa");
    }

    printf("Started client\n");

    SSL_CTX* ctx;
    int server;
    SSL* ssl;
    char buf[1024];
    int bytes;

    const SSL_METHOD* method;
    method = TLS_client_method();
    ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        printf("Failed to create ssl context\n");
    }

    sockaddr_in addr;
    server = socket(PF_INET, SOCK_STREAM, 0);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(4433);
    in_addr inaddr;
    inet_pton(AF_INET, "127.0.0.1", &inaddr);
    addr.sin_addr = inaddr;
    if (connect(server, (sockaddr*)&addr, sizeof(addr)) != 0)
    {
        printf("Failed to connect to server\n");
    }

    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, server);

    if (SSL_connect(ssl) == -1) {
        printf("Failed to connect with ssl\n");
    }
    else {
        printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
        const char* ClientRequest = "g/hello@hello.com/\0";
        SSL_write(ssl, ClientRequest, strlen(ClientRequest));
        bytes = SSL_read(ssl, buf, 1023);
        buf[bytes] = '\0';
        printf("Received: %s\n", buf);
        SSL_free(ssl);
    }

    closesocket(server);
    SSL_CTX_free(ctx);

    WSACleanup();
    return 0;
}

您需要调用两个连接(一个用于基本套接字,一个用于 SSL)。我认为握手发生在 2 次通话中。基本连接会进行正常的套接字未加密握手,然后您需要调用 SSL_connect() 以使用非对称加密等方式进行加密握手。

编辑

您可能应该构建自己的库,因为这样更安全且更可定制。我一直在使用 OpenSSL 开发人员 (How to build OpenSSL on Windows with Visual Studio 2017?) 的 stackoverflow 答案进行一些测试。构建您自己的 OpenSSL 库非常简单。基本上,

  • 下载 Perl Stawberry (https://strawberryperl.com/)
  • 下载 NASM (https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/)
  • 在您的机器上为您的 NASM 和 Perl 安装创建路径环境变量
  • 下载 OpenSSL 源代码(在 Windows 的 git 命令提示符中输入“git clone git://git.openssl.org/openssl.git”或直接从 Github 下载源代码)
  • 打开 Visual Studio 开发人员提示(在 Visual Studio 中单击工具 > 命令行 > 开发人员命令提示)
  • 将目录更改为 OpenSSL 源代码
  • 为动态编译版本键入“perl Configure VC-WIN32”(它将创建动态库,因此除了与静态库链接外,您还需要将它们放在可执行文件的目录中)或“perl Configure VC-WIN32 no- shared" 用于静态编译(这样您将只有静态库,因此您的可执行文件目录中不需要任何 dll)
  • 最后,输入“nmake”。这将开始编译 OpenSSL。最后,您将在 OpenSSL 源代码根目录中拥有库(和动态编译的 dll)。

OpenSSL 动态版本的工作代码如下:

#define _WINSOCKAPI_
#include <iostream>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <openssl/ssl.h>


#pragma comment (lib, "Ws2_32")
#pragma comment (lib, "libssl")

int main() {
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        std::cout << "Failed to initialize wsa" << std::endl;
    }

    printf("Started client\n");

    SSL_CTX* ctx;
    int server;
    SSL* ssl;
    char buf[1024];
    int bytes;

    const SSL_METHOD* method;
    method = TLS_client_method();
    ctx = SSL_CTX_new(method);
    if (ctx == NULL) {
        printf("Failed to create ssl context\n");
    }

    sockaddr_in addr;
    server = socket(PF_INET, SOCK_STREAM, 0);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(443);
    in_addr inaddr;
    inet_pton(AF_INET, "172.217.13.110", &inaddr);
    addr.sin_addr = inaddr;
    if (connect(server, (sockaddr*)&addr, sizeof(addr)) != 0)
    {
        printf("Failed to connect to server\n");
    }

    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, server);

    if (SSL_connect(ssl) == -1) {
        printf("Failed to connect with ssl\n");
    }
    else {
        printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
        const char* ClientRequest = "GET / HTTP / 1.00\r\n\r\n\0";
        SSL_write(ssl, ClientRequest, strlen(ClientRequest));
        bytes = SSL_read(ssl, buf, 1023);
        buf[bytes] = '\0';
        printf("Received: %s\n", buf);
        SSL_free(ssl);
    }

    closesocket(server);
    SSL_CTX_free(ctx);

    WSACleanup();
    return 0;
}

不需要与 libcrypto.lib 链接,但您需要将 libssl-3.dll 和 libcrypto-3.dll 放在可执行文件的目录中。使用此代码并按照上面的编译指南,我得到以下输出(IP 地址是 google.com):

Started client
Connected with TLS_AES_256_GCM_SHA384 encryption
Received: HTTP/1.0 400 Bad Request
Content-Type: text/html; charset=UTF-8
Referrer-Policy: no-referrer
Content-Length: 1555
Date: Tue, 23 Feb 2021 06:59:28 GMT

<!DOCTYPE html>
<html lang=en>
  <meta charset=utf-8>
  <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
  <title>Error 400 (Bad Request)!!1</title>
  <style>
    *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:

您看到它连接良好,发送您的请求,显然 google.com 回答了一个错误的请求。

静态编译的工作代码是相同的,只是你需要在顶部添加这两行(与 libcrypto 和 crypt32 链接)。

#pragma comment (lib, "libcrypto")
#pragma comment (lib, "crypt32")

除了这两个附加链接之外,使用静态库和相同的代码,我得到相同的输出。

【讨论】:

  • 感谢简单明了的示例。我仍然遇到问题:找不到 inet_pton 或 TLS_client_method()。我正在研究这些……
  • inet_pton 在 ws2tcpip.h 中。虽然 TLS_client_method() 可能在更高版本的 OpenSSL 中。如openssl.org/docs/man1.1.0/man3/TLS_client_method.html 所述,TLS_method()、TLS_server_method()、TLS_client_method()、DTLS_method()、DTLS_server_method() 和 DTLS_client_method() 是版本灵活的方法。所有其他方法仅支持一种特定的协议版本。使用版本灵活的方法而不是版本特定的方法。
  • 你使用什么库?你自己编译的吗。您可能正在使用 1.0 版 OpenSSL 的旧预编译库。
  • 还是没有运气。所有通过 SSL_connect 的调用都成功,但 SSL_connect 返回 -1。我正在使用从 Shining Light 下载的库版本 v1.1.1i。顺便说一句,我尝试了 DTLS 方法,没有任何区别。此外,“TLS_client_method”不在 SSL 包含文件中,但已解决:我必须在我的应用程序中将其声明为 extern 以消除警告
  • 这些库不起作用。试试这里的最后一个链接wiki.openssl.org/index.php/Binaries,即kb.firedaemon.com/support/solutions/articles/4000121705。这个对我有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-03
  • 2018-03-21
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
  • 2014-02-26
  • 1970-01-01
相关资源
最近更新 更多