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

时间:2016-10-15 19:50:46

标签: c++ ssl openssl x509 pki

我有一个使用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;
}

1 个答案:

答案 0 :(得分:4)

正确的证书验证是一件复杂的事情,OpenSSL在那里只是稍微有用。如果您希望每个步骤都有完整的代码,我会认为这个问题过于宽泛。因此,我将重点关注验证所需的基本部分,并主要指出其他资源,以了解具体部分的实施细节。

验证服务器证书的第一步是将信任链构建为受信任的根CA证书。如果您已设置受信任的根(即在代码中调用SSL_CTX_load_verify_locations)并将验证模式设置为SSL_CTX_set_verify至{{1},则TLS握手内的openssl会隐式执行此操作}。此内置验证还包括检查叶证书和链证书是否已有效且尚未过期。

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

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

对于CRL,请参阅Does OpenSSL automatically handle CRLs (Certificate Revocation Lists) now?,了解如何使用已经下载过的CRL。但OpenSSL不会帮助您首先下载CRL。相反,您需要从叶证书中提取CRL分发点,自己下载CRL然后才能使用它。

对于OCSP,OpenSSL提供了构建OCSP请求和验证OCSP响应所需的API。您仍然可以自行确定将此OCSP请求发送到的位置。这需要通过解析叶证书并从授权信息访问字段中提取OCSP URL来再次完成。虽然有其余的API,但它不容易使用,并且至少在OpenSSL 1.1.0之前大部分或完全没有记录。我不知道一个好的,易于理解的示例,其中使用此API实现了OCSP验证,但您可以查看SSL_VERIFY_PEER命令和its implementation

如果您编写客户端应用程序并且只想接受单个证书,则可以省略针对PKI进行验证的所有复杂步骤,但只检查证书是否与预期证书完全相同,即证书固定。有关此内容和示例代码的详细信息,请参阅OWASP: Certificate and Public Key Pinning