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

Client program to validate server certificate returned by SSL_get_peer_certificate?(客户端程序验证由 SSL_get_peer_certificate 返回的服务器证书?)

本文介绍了客户端程序验证由 SSL_get_peer_certificate 返回的服务器证书?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a SSL/TLS client program using OpenSSL in C++ programming language. I am looking for methods to validate server certificate (X509) returned by SSL_get_peer_certificate function call. Also, I have my own CA certificate loaded using SSL_CTX_load_verify_locations function. The CA certified the server certificate.

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

I am able to make SSL session to my server. Now, i want to validate server certificate received during SSL handshake using my own CA. I couldn't find a way to do it in C or 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
HOST: www.siliconbolt.com

";
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), '', 8);

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

return fd;
}

推荐答案

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

Proper certificate verification is a complex thing and OpenSSL is only slightly helpful there. If you expect full code for each step I would consider the question as too broad. Therefore I'll focus on the essential parts needed in verification and mainly point out other resources for the details of the implementation of the specific parts.

验证服务器证书的第一步是构建可信根 CA 证书的信任链.如果您设置了受信任的根(即在代码中调用 SSL_CTX_load_verify_locations)并使用 SSL_CTX_set_verify 到 SSL_VERIFY_PEER.此内置验证还包括检查叶证书和链证书是否已经有效且尚未过期.

The first step for validating a server certificate is building the trust chain to a trusted root CA certificate. This is implicitly done by openssl inside the TLS handshake if you've set a trusted root (i.e. call of SSL_CTX_load_verify_locations in your code) and also set the verification mode with SSL_CTX_set_verify to SSL_VERIFY_PEER. This built-in validation also includes checks if the leaf and chain certificates are already valid and not yet expired.

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

The next step is to validate if the subject of the certificate matches the expected one. For server certificates this usually means that the target hostname is somehow included in the common name or the subject alternative names of the certificate. The actual requirements depend on the application layer protocol, i.e. HTTP, SMTP, LDAP ... all have slightly different rules especially if wildcards are involved. Since OpenSSL 1.0.2 a a X509_check_host function is available and can be used to check against the rules in most cases. With earlier versions of OpenSSL you are on your own to implement such a function. Note that you explicitly need to validate the hostname, i.e. OpenSSL will not do this for you and omitting this step will make man in the middle attacks against your application easy.

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

At this point that you know that the certificate is directly or indirectly issued by a trusted root CA and that the certificate matches the expected hostname. You now need to check if the certificate is revoked. One way is to use a certificate revocation list (CRL) which you got somewhere, another is to use the the Online Certificate Status Protocol (OCSP).

有关 CRL,请参阅 OpenSSL 是否自动处理 CRL(证书撤销Lists) now? 了解如何使用您已经下载的 CRL.但是 OpenSSL 首先不会帮助您下载 CRL.相反,您需要从叶证书中提取 CRL 分发点,自己下载 CRL,然后才能使用它.

For CRL see Does OpenSSL automatically handle CRLs (Certificate Revocation Lists) now? which hows how to use a CRL you've downloaded already. But OpenSSL will not help you with downloading the CRL in the first place. Instead you would need to extract the CRL distribution point from the leaf certificate, download the CRL yourself and then you can use it.

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

As for OCSP OpenSSL provides the necessary API to build OCSP requests and validate OCSP responses. You are still on your own to figure out where to sent this OCSP request to. This needs to be done again by parsing the leaf certificate and extracting the OCSP URL from the Authority Information Access field. And while there is API for the rest it is not easy to use and was mostly or fully undocumented at least before OpenSSL version 1.1.0. I don't know of a good and easy to understand example where OCSP validation is implemented using this API but you might have a look at the openssl ocsp command and its implementation.

如果您编写了一个客户端应用程序并且无论如何只想接受一个证书,您可以省略针对 PKI 但只检查证书是否完全符合预期,即证书固定.请参阅OWASP:证书和公钥固定,了解更多信息和示例代码.

In case you write a client application and want to accept only a single certificate anyway you can omit all the complex steps of validating against a PKI but only check if the certificate is exactly the expected one, i.e. certificate pinning. See OWASP: Certificate and Public Key Pinning for more information about this and for sample code.

这篇关于客户端程序验证由 SSL_get_peer_certificate 返回的服务器证书?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:客户端程序验证由 SSL_get_peer_certificate 返回的服务器证书?