openssl
编程框架及相关示例代码
openssl
OpenSSL 是一个开放源代码的 SSL 协议的产品实现,它采用 C 语言作为开发语言,具备了跨系统的性能。调用 OpenSSL 的函数就可以实现一个 SSL 加密的安全数据传输通道,从而保护客户端和服务器之间数据的安全。
头文件
#include <openssl/ssl.h>
#include <openssl/err.h>
常用函数
初始化
在使用 OpenSSL 之前,必须进行相应的协议初始化工作,这可以通过下面的函数实现:
int SSL_library_int(void);
选择会话协议
在利用 OpenSSL 开始 SSL 会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0
、SSLv2
、SSLv3
、SSLv2/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_read
和SSL_write
来替代传统的read
和write
函数,来完成对套接字的读写操作:
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-way “Standard” SSL Authentication
- 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);