客户端程序验证由 SSL_get_peer_certificate 返回的服务器证书?

abh*_*ora 2 c++ ssl openssl pki x509

我有一个在 C++ 编程语言中使用 OpenSSL 的 SSL/TLS 客户端程序。我正在寻找验证函数调用X509返回的服务器证书 ( ) 的SSL_get_peer_certificate方法。另外,我使用SSL_CTX_load_verify_locations函数加载了自己的 CA 证书。CA 认证了服务器证书。

我能够与我的服务器建立 SSL 会话。现在,我想使用我自己的 CA 验证在 SSL 握手期间收到的服务器证书。我找不到在 C 或 C++ 中做到这一点的方法。

#include <iostream>
#include <string.h>

#include <unistd.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <openssl/bio.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/x509_vfy.h>


#define DEFAULT_PORT_NUMBER                     443

int create_socket(char *, uint16_t port_num);

int openSSL_client_init()
{
    OpenSSL_add_all_algorithms();
    ERR_load_BIO_strings();
    ERR_load_crypto_strings();
    SSL_load_error_strings();

    if (SSL_library_init() < 0)
        return -1;
    return 0;
}

int openSSL_create_client_ctx(SSL_CTX **ctx)
{
    const SSL_METHOD *method = SSLv23_client_method();

    if ((*ctx = SSL_CTX_new(method)) == NULL)
        return -1;


    //SSL_CTX_set_options(*ctx, SSL_OP_NO_SSLv2);

    return 0;
}

int main(int argc, char *argv[])
{
BIO *outbio = NULL;

X509 *cert;
X509_NAME *certname = NULL;

SSL_CTX *ctx;
SSL *ssl;
int server = 0;
int ret, i;

if (openSSL_client_init()) {
    std :: cerr << "Could not initialize the OpenSSL library !" << std     :: endl;
    return -1;
}

outbio  = BIO_new_fp(stdout, BIO_NOCLOSE);

if (openSSL_create_client_ctx(&ctx)) {
    std :: cerr << "Unable to create a new SSL context structure." << std :: endl;
    return -1;
}


std :: cout << "Adding Certifcate" << std :: endl;
if (SSL_CTX_load_verify_locations(ctx, "ca-cert.pem", NULL) <= 0) {
    std :: cerr << "Unable to Load certificate" << std :: endl;
    return -1;
}


ssl = SSL_new(ctx);
server = create_socket(argv[1], atoi(argv[2]));

if (server < 0) {
    std :: cerr << "Error: Can't create TCP session" << std :: endl;
    return -1;
}
std :: cout << "Successfully made the TCP connection to: " << argv[1] << " port: " << atoi(argv[2]) << std :: endl;

SSL_set_fd(ssl, server);

if (SSL_connect(ssl) != 1) {
    std :: cerr << "Error: Could not build a SSL session to: " << argv[1] << std :: endl;
    return -1;
}

std :: cout << "Successfully enabled SSL/TLS session to: " << argv[1] << std :: endl;
//SSL_SESSION *ss = SSL_get_session(ssl);

cert = SSL_get_peer_certificate(ssl);
if (cert == NULL) {
    std :: cerr << "Error: Could not get a certificate from: " <<  argv[1] << std :: endl;
    return -1;
}

certname = X509_NAME_new();
certname = X509_get_subject_name(cert);

std :: cout << "Displaying the certificate subject data:" << std :: endl;
X509_NAME_print_ex(outbio, certname, 0, 0);
std :: cout << std :: endl;


char msg[100000] = "GET / HTTP/1.1\r\nHOST: www.siliconbolt.com\r\n\r\n";
SSL_write(ssl, msg, strlen(msg));
SSL_read(ssl, msg, 100000);
std :: cout << "Message is " << msg << std :: endl;


SSL_free(ssl);
close(server);
X509_free(cert);
SSL_CTX_free(ctx);
std :: cout << "Finished SSL/TLS connection with server" << std :: endl;
return 0;
}


int create_socket(char *ip_cstr, uint16_t port_num)
{
int fd;
struct sockaddr_in dest_addr;

fd = socket(AF_INET, SOCK_STREAM, 0);

dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(port_num);
dest_addr.sin_addr.s_addr = inet_addr(ip_cstr);

memset(&(dest_addr.sin_zero), '\0', 8);

if (connect(fd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr)) == -1)
    return -1;

return fd;
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*ich 8

正确的证书验证是一件复杂的事情,而 OpenSSL 在这方面的帮助很小。如果您期望每个步骤的完整代码,我会认为这个问题太宽泛了。因此,我将重点介绍验证所需的基本部分,并主要指出其他资源以了解具体部分的实现细节。


验证服务器证书的第一步是将信任链构建到受信任的根 CA 证书。这是隐含的OpenSSL TLS握手内部完成,如果你(即调用设置受信任的根SSL_CTX_load_verify_locations在你的代码),并与设置验证模式SSL_CTX_set_verifySSL_VERIFY_PEER。此内置验证还包括检查叶证书和链证书是否已经有效且尚未过期。

下一步是验证证书的主题是否与预期的主题匹配。对于服务器证书,这通常意味着目标主机名以某种方式包含在证书的通用名称或主题替代名称中。实际要求取决于应用层协议,即 HTTP、SMTP、LDAP...所有规则都略有不同,尤其是在涉及通配符时。自 OpenSSL 1.0.2 aa X509_check_host函数可用,在大多数情况下可用于检查规则。使用早期版本的 OpenSSL,您可以自己实现这样的功能。请注意,您明确需要验证主机名,即 OpenSSL 不会为您执行此操作,并且省略此步骤将使针对您的应用程序的中间人攻击变得容易。

此时,您知道该证书是由受信任的根 CA 直接或间接颁发的,并且该证书与预期的主机名匹配。您现在需要检查证书是否被吊销。一种方法是使用您在某处获得的证书吊销列表 ( CRL ),另一种方法是使用在线证书状态协议 ( OCSP )。

对于 CRL,请参阅OpenSSL 现在是否自动处理 CRL(证书吊销列表)?这是如何使用您已经下载的 CRL。但是 OpenSSL 首先不会帮助您下载 CRL。相反,您需要从叶证书中提取 CRL 分发点,自己下载 CRL,然后才能使用它。

至于 OCSP,OpenSSL 提供了必要的 API 来构建 OCSP 请求和验证 OCSP 响应。您仍然需要自己确定将此 OCSP 请求发送到何处。这需要通过解析叶证书并从授权信息访问字段中提取 OCSP URL 来再次完成。虽然还有其他 API,但它并不容易使用,并且至少在 OpenSSL 1.1.0 版之前大部分或完全没有记录。我不知道使用此 API 实现 OCSP 验证的良好且易于理解的示例,但您可能会查看openssl ocsp命令及其实现


如果您编写了一个客户端应用程序并且无论如何只想接受一个证书,您可以省略针对PKI进行验证的所有复杂步骤,但只检查证书是否完全符合预期,即证书锁定。有关此内容和示例代码的更多信息,请参阅OWASP:证书和公钥固定