0%

SSL 编程

openssl编程框架及相关示例代码

openssl

OpenSSL 是一个开放源代码的 SSL 协议的产品实现,它采用 C 语言作为开发语言,具备了跨系统的性能。调用 OpenSSL 的函数就可以实现一个 SSL 加密的安全数据传输通道,从而保护客户端和服务器之间数据的安全。

头文件

#include <openssl/ssl.h>
#include <openssl/err.h>

常用函数

初始化

在使用 OpenSSL 之前,必须进行相应的协议初始化工作,这可以通过下面的函数实现:

int SSL_library_int(void);

选择会话协议

在利用 OpenSSL 开始 SSL 会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0SSLv2SSLv3SSLv2/v3
需要注意的是,客户端和服务器必须使用相互兼容的协议,否则 SSL 会话将无法正常进行。

创建会话环境

在 OpenSSL 中创建的 SSL 会话环境称为 CTX,使用不同的协议会话,其环境也不一样的。

申请 SSL 会话环境的 OpenSSL 函数是:

SSL_CTX *SSL_CTX_new(SSL_METHOD * method);

当 SSL 会话环境申请成功后,还要根据实际的需要设置 CTX 的属性,通常的设置是指定 SSL 握手阶段证书的验证方式和加载自己的证书。

指定证书验证方式的函数是:

int SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int(*verify_callback),int(X509_STORE_CTX *));
  • SSL_VERIFY_NONE 表示不验证
  • SSL_VERIFY_PEER 用于客户端时要求服务器必须提供证书,用于服务器时服务器会发出证书请求消息要求客户端提供证书,但是客户端也可以不提供
  • SSL_VERIGY_FAIL_IF_NO_PEER_CERT只适用于服务器且必须提供证书。他必须与 SSL_VERIFY_PEER 一起使用

为 SSL 会话环境加载 CA 证书的函数是:

SSL_CTX_load_verify_location(SSL_CTX *ctx,const char *Cafile,const char *Capath);

从指定文件中加载 CA 证书

STACK_OF(X509_NAME) *SSL_load_client_CA_file(const char *file);

发送 CAs 到客户端,客户端必须提供一个有 CAs 签名的 CA 用于服务端验证客户端

 void SSL_CTX_set_client_CA_list(SSL_CTX *ctx, STACK_OF(X509_NAME) *list);
 void SSL_set_client_CA_list(SSL *s, STACK_OF(X509_NAME) *list);
 int SSL_CTX_add_client_CA(SSL_CTX *ctx, X509 *cacert);
 int SSL_add_client_CA(SSL *ssl, X509 *cacert);

The SSL_CTX_set_client_CA_list function is only needed by server applications that verify the identity of remote client applications when SSL sessions are started.

SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file())用于双向认证时,服务端验证客户端

为 SSL 会话加载用户证书的函数是:

SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,int type);

为 SSL 会话加载用户私钥的函数是:

SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,int type);

在将证书和私钥加载到 SSL 会话环境之后,就可以调用下面的函数来验证私钥和证书是否相符:

int SSL_CTX_check_private_key(SSL_CTX *ctx);

建立 SSL 套接字

SSL 套接字是建立在普通的 TCP 套接字基础之上,在建立 SSL 套接字时可以使用下面的一些函数:

// 申请一个 SSL 套接字
SSL *SSl_new(SSL_CTX *ctx);
// 绑定读写套接字
int SSL_set_fd(SSL *ssl,int fd);
// 绑定只读套接字
int SSL_set_rfd(SSL *ssl,int fd);
// 绑定只写套接字
int SSL_set_wfd(SSL *ssl,int fd);

完成 SSL 握手

在成功创建 SSL 套接字后,客户端应使用函数SSL_connect替代传统的函数connect来完成握手过程:

int SSL_connect(SSL *ssl);

而对服务器来讲,则应使用函数SSL_ accept替代传统的函数accept来完成握手过程:

int SSL_accept(SSL *ssl);

握手过程完成之后,通常需要询问通信双方的证书信息,以便进行相应的验证,这可以借助于下面的函数来实现:

X509 *SSL_get_peer_certificate(SSL *ssl);

该函数可以从 SSL 套接字中提取对方的证书信息,这些信息已经被 SSL 验证过了。

X509_NAME *X509_get_subject_name(X509 *a);

该函数得到证书所用者的名字。

进行数据传输

当 SSL 握手完成之后,就可以进行安全的数据传输了,在数据传输阶段,需要使用SSL_readSSL_write来替代传统的readwrite函数,来完成对套接字的读写操作:

int SSL_read(SSL *ssl,void *buf,int num);
int SSL_write(SSL *ssl,const void *buf,int num);

结束 SSL 通信

当客户端和服务器之间的数据通信完成之后,调用下面的函数来释放已经申请的 SSL 资源:

// 关闭 SSL 套接字
int SSL_shutdown(SSL *ssl);
// 释放 SSL 套接字
void SSl_free(SSL *ssl);
// 释放 SSL 会话环境
void SSL_CTX_free(SSL_CTX *ctx);

编程框架

server

meth = SSLv23_client_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);
fd = socket();
connect();
SSL_set_fd(ssl,fd);
SSL_connect(ssl);
SSL_write(ssl,"Hello world",strlen("Hello World!"));

client

meth = SSLv23_server_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);
fd = socket();
bind();
listen();
accept();
SSL_set_fd(ssl,fd);
SSL_connect(ssl);
SSL_read (ssl, buf, sizeof(buf));

Example

SSL有两种模式,单向和双向

  1. 1-way “Standard” SSL Authentication
  2. 2-way “Mutual” SSL Authentication

参考代码

server

/* Initialize OpenSSL */
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();

/* Get a default context */
if (!(ctx = SSL_CTX_new(SSLv23_server_method()))) {
    fprintf(stderr, "SSL_CTX_new failed\n");
    return NULL;
}

/* Set the CA file location for the server */
if (SSL_CTX_load_verify_locations(ctx, ca_pem, NULL) != 1) {
    fprintf(stderr, "Could not set the CA file location\n");
    goto fail;
}

/* Load the client's CA file location as well */
SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(ca_pem));

/* Set the server's certificate signed by the CA */
if (SSL_CTX_use_certificate_file(ctx, cert_pem, SSL_FILETYPE_PEM) != 1) {
    fprintf(stderr, "Could not set the server's certificate\n");
    goto fail;
}

/* Set the server's key for the above certificate */
if (SSL_CTX_use_PrivateKey_file(ctx, key_pem, SSL_FILETYPE_PEM) != 1) {
    fprintf(stderr, "Could not set the server's key\n");
    goto fail;
}

/* We've loaded both certificate and the key, check if they match */
if (SSL_CTX_check_private_key(ctx) != 1) {
    fprintf(stderr, "Server's certificate and the key don't match\n");
    goto fail;
}

/* We won't handle incomplete read/writes due to renegotiation */
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);

/* Specify that we need to verify the client as well */
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);

/* We accept only certificates signed only by the CA himself */
SSL_CTX_set_verify_depth(ctx, 1);

client

/* ---------------------------------------------------------- *
 * These function calls initialize openssl for correct work.  *
 * ---------------------------------------------------------- */
OpenSSL_add_all_algorithms();
ERR_load_BIO_strings();
ERR_load_crypto_strings();
SSL_load_error_strings();

/* ---------------------------------------------------------- *
 * Create the Input/Output BIO's.                             *
 * ---------------------------------------------------------- */
certbio = BIO_new(BIO_s_file());
outbio  = BIO_new_fp(stdout, BIO_NOCLOSE);

/* ---------------------------------------------------------- *
 * initialize SSL library and register algorithms             *
 * ---------------------------------------------------------- */
if(SSL_library_init() < 0)
  BIO_printf(outbio, "Could not initialize the OpenSSL library !\n");

/* ---------------------------------------------------------- *
 * Set SSLv2 client hello, also announce SSLv3 and TLSv1      *
 * ---------------------------------------------------------- */
method = SSLv23_client_method();

/* ---------------------------------------------------------- *
 * Try to create a new SSL context                            *
 * ---------------------------------------------------------- */
if ( (ctx = SSL_CTX_new(method)) == NULL)
  BIO_printf(outbio, "Unable to create a new SSL context structure.\n");

/* ---------------------------------------------------------- *
 * Disabling SSLv2 will leave v3 and TSLv1 for negotiation    *
 * ---------------------------------------------------------- */
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);

/* ---------------------------------------------------------- *
 * Create new SSL connection state object                     *
 * ---------------------------------------------------------- */
ssl = SSL_new(ctx);

/* ---------------------------------------------------------- *
 * Make the underlying TCP socket connection                  *
 * ---------------------------------------------------------- */
server = create_socket(dest_url, outbio);
if(server != 0)
  BIO_printf(outbio, "Successfully made the TCP connection to: %s.\n", dest_url);

/* ---------------------------------------------------------- *
 * Attach the SSL session to the socket descriptor            *
 * ---------------------------------------------------------- */
SSL_set_fd(ssl, server);

/* ---------------------------------------------------------- *
 * Try to SSL-connect here, returns 1 for success             *
 * ---------------------------------------------------------- */
if ( SSL_connect(ssl) != 1 )
  BIO_printf(outbio, "Error: Could not build a SSL session to: %s.\n", dest_url);
else
  BIO_printf(outbio, "Successfully enabled SSL/TLS session to: %s.\n", dest_url);

/* ---------------------------------------------------------- *
 * Get the remote certificate into the X509 structure         *
 * ---------------------------------------------------------- */
cert = SSL_get_peer_certificate(ssl);
if (cert == NULL)
  BIO_printf(outbio, "Error: Could not get a certificate from: %s.\n", dest_url);
else
  BIO_printf(outbio, "Retrieved the server's certificate from: %s.\n", dest_url);

/* ---------------------------------------------------------- *
 * extract various certificate information                    *
 * -----------------------------------------------------------*/
certname = X509_NAME_new();
certname = X509_get_subject_name(cert);

/* ---------------------------------------------------------- *
 * display the cert subject here                              *
 * -----------------------------------------------------------*/
BIO_printf(outbio, "Displaying the certificate subject data:\n");
X509_NAME_print_ex(outbio, certname, 0, 0);
BIO_printf(outbio, "\n");

/* ---------------------------------------------------------- *
 * Free the structures we don't need anymore                  *
 * -----------------------------------------------------------*/
SSL_free(ssl);
close(server);
X509_free(cert);
SSL_CTX_free(ctx);
BIO_printf(outbio, "Finished SSL/TLS connection with server: %s.\n", dest_url);

Ref

  1. SSL_CTX_set_client_CA_list
  2. Server application with SSL code
  3. SSL_CTX_set_client_CA_list