1、OpenSSL BIO介绍
在项目中我们通常使用OpenSSL SSL / TLS来保护数据传输, OpenSSL作为一个基于密码学的安全开发包,提供了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议,并提供了丰富的应用程序供测试或其它目的使用。
BIO 是OpenSSL封装的抽象的I/O接口,在底层覆盖了许多类型I/O接口细节的一种应用接口,BIO可以连接在一起成为一个BIO链(单个的BIO就是一个环节的BIO链的特例),数据从第一个BIO读出或写入,然后经过一系列BIO变化到输出,使用BIO可以和SSL、非加密的网络以及文件IO进行透明的连接。
2、BIO类型
BIO分为source/sink 和filter两种类型,其中包括6种filter型和8种source/sink型。
3、BIO常用接口
- BIO_read:从BIO接口中读出len字节的数据到buf中。成功就返回真正读出的数据的长度。
- BIO_write:往BIO中写入长度为len的数据。成功返回真正写入的数据的长度。
- BIO_gets:该函数从BIO中读取一行长度最大为size的数据。通常情况下,该函数会以最大长度限制读取一行数据,但是也有例外,比如digest型的BIO,该函数会计算并返回整个digest信息。此外,有些BIO可能不支持这个函数。成功就返回真正读出的数据的长度。
- BIO_puts:往BIO中写入一个以NULL为结束符的字符串,成功就返回真正写入的数据的长度。
使用BIO_write()存储如TCP / UDP套接字接收加密的数据。一旦您写入输入BIO,您就可以使用SSL_read()获取未加密的数据,但只有在握手准备好之后。
使用BIO_read()以检查是否有输出BIO的任何数据。当处理握手或打电话时,输出BIO将由openSSL填充SSL_write()。当输出BIO中有数据时,使用BIO_read获取数据并将其发送到例如客户端。使用BIO_ctrl_pending()检查输出BIO中存储了多少字节。
4、OpenSSL Memory BIO的使用
Memory BIO封装对内存的操作,包括读写操作。要使用openSSL需要创建一个SSL_CTX对象,该对象是跟踪共享信息的上下文,例如要使用的证书,要使用的私钥等。有了SSL_CTX对象,就可SSL以为特定连接创建一个对象。您可以为要处理的每个连接创建许多SSL对象并共享一个SSL_CTX对象。
当您使用openSSL作为服务器时,您需要确保SSL对象知道它应该充当可以通过调用完成的服务器 SSL_set_accept_state(ssl)。当SSL用作服务器时,它将等待 ClientHello消息并确保将ServerHello,CertificateRequest等发送回客户端。当您希望SSL对象像客户端一样工作时,您需要通过调用来调用SSL_set_connect_state(ssl)并调用start来填充输出BIO SSL_do_handshake()。
只要握手尚未就绪,您必须确保 每次输入或输出BIO发生变化时都会调用该SSL_do_handshake()函数。例如,当服务器启动并且客户端连接时,您调用BIO_write()并使用您在套接字上获得的数据填充输入BIO,然后只要握手尚未就绪就调用SSL_do_handshake(ssl)。握手完成后,您可以通过使用您想要保护的应用程序数据调用SSL_read和SSL_write来开始填充BIO。
OpenSSL提供了SSL_is_init_finished()检查握手是否完成。SSL_is_init_finished()返回false,就应该处理握手流程,需要调用 SSL_do_handshake() 。OpenSSL在内部保持状态,但是,检查调用的返回值非常重要,SSL_do_handshake()因为它告诉您需要接下来调用的函数,尤其是在使用内存BIOS时。如手册中所述:“如果底层BIO是非阻塞的,当底层BIO无法满足SSL_do_handshake()继续握手的需求时,SSL_do_handshake()也会返回。在这种情况下,调用SSL_get_error()并返回值为SSL_do_handshake( )将产生SSL_ERROR_WANT_READ或SSL_ERROR_WANT_WRITE。
5、示例代码
/* SSL debug */
#define SSL_WHERE_INFO(ssl, w, flag, msg) { \
if(w & flag) { \
printf("+ %s: ", name); \
printf("%20.20s", msg); \
printf(" - %30.30s ", SSL_state_string_long(ssl)); \
printf(" - %5.10s ", SSL_state_string(ssl)); \
printf("\n"); \
} \
}
typedef void(*info_callback)();
typedef struct {
SSL_CTX* ctx; /* main ssl context */
SSL* ssl; /* the SSL* which represents a "connection" */
BIO* in_bio; /* we use memory read bios */
BIO* out_bio; /* we use memory write bios */
char name[512];
} krx;
void krx_begin(); /* initialize SSL */
void krx_end(); /* shutdown SSL */
int krx_ssl_ctx_init(krx* k, const char* keyname); /* initialize the SSL_CTX */
int krx_ssl_init(krx* k, int isserver, info_callback cb); /* init the SSL* (the "connection"). we use the `isserver` to tell SSL that it should either use the server or client protocol */
int krx_ssl_shutdown(krx* k); /* cleanup SSL allocated mem */
int krx_ssl_verify_peer(int ok, X509_STORE_CTX* ctx); /* we set the SSL_VERIFY_PEER option on the SSL_CTX, so that the server will request the client certificate. We can use the certificate to get/verify the fingerprint */
int krx_ssl_handle_traffic(krx* from, krx* to);
/* some debug info */
void krx_ssl_server_info_callback(const SSL* ssl, int where, int ret); /* purely for debug purposes; logs server info. */
void krx_ssl_client_info_callback(const SSL* ssl, int where, int ret); /* client info callback */
void krx_ssl_info_callback(const SSL* ssl, int where, int ret, const char* name); /* generic info callback */
int main() {
/* startup SSL */
krx_begin();
/* create client/server objects */
krx server;
krx client;
/* init server. */
if(krx_ssl_ctx_init(&server, "server") < 0) {
exit(EXIT_FAILURE);
}
if(krx_ssl_init(&server, 1, krx_ssl_server_info_callback) < 0) {
exit(EXIT_FAILURE);
}
printf("+ Initialized server.\n");
/* init client. */
if(krx_ssl_ctx_init(&client, "client") < 0) {
exit(EXIT_FAILURE);
}
if(krx_ssl_init(&client, 0, krx_ssl_client_info_callback) < 0) {
exit(EXIT_FAILURE);
}
printf("+ Initialized client.\n");
/* kickoff handshake; initiated by client (e.g. browser) */
SSL_do_handshake(client.ssl);
krx_ssl_handle_traffic(&client, &server);
krx_ssl_handle_traffic(&server, &client);
krx_ssl_handle_traffic(&client, &server);
krx_ssl_handle_traffic(&server, &client);
/* encrypt some data and send it to the client */
char buf[521] = { 0 } ;
sprintf(buf, "%s", "Hello world");
SSL_write(server.ssl, buf, sizeof(buf));
krx_ssl_handle_traffic(&server, &client);
krx_ssl_shutdown(&server);
krx_ssl_shutdown(&client);
krx_end();
return EXIT_SUCCESS;
}
void krx_begin() {
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
}
void krx_end() {
ERR_remove_state(0);
ENGINE_cleanup();
CONF_modules_unload(1);
ERR_free_strings();
EVP_cleanup();
sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
CRYPTO_cleanup_all_ex_data();
}
int krx_ssl_ctx_init(krx* k, const char* keyname) {
int r = 0;
/* create a new context using DTLS */
k->ctx = SSL_CTX_new(DTLSv1_method());
if(!k->ctx) {
printf("Error: cannot create SSL_CTX.\n");
ERR_print_errors_fp(stderr);
return -1;
}
/* set our supported ciphers */
r = SSL_CTX_set_cipher_list(k->ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
if(r != 1) {
printf("Error: cannot set the cipher list.\n");
ERR_print_errors_fp(stderr);
return -2;
}
/* the client doesn't have to send it's certificate */
SSL_CTX_set_verify(k->ctx, SSL_VERIFY_PEER, krx_ssl_verify_peer);
/* enable srtp */
r = SSL_CTX_set_tlsext_use_srtp(k->ctx, "SRTP_AES128_CM_SHA1_80");
if(r != 0) {
printf("Error: cannot setup srtp.\n");
ERR_print_errors_fp(stderr);
return -3;
}
/* load key and certificate */
char certfile[1024];
char keyfile[1024];
sprintf(certfile, "./%s-cert.pem", keyname);
sprintf(keyfile, "./%s-key.pem", keyname);
/* certificate file; contains also the public key */
r = SSL_CTX_use_certificate_file(k->ctx, certfile, SSL_FILETYPE_PEM);
if(r != 1) {
printf("Error: cannot load certificate file.\n");
ERR_print_errors_fp(stderr);
return -4;
}
/* load private key */
r = SSL_CTX_use_PrivateKey_file(k->ctx, keyfile, SSL_FILETYPE_PEM);
if(r != 1) {
printf("Error: cannot load private key file.\n");
ERR_print_errors_fp(stderr);
return -5;
}
/* check if the private key is valid */
r = SSL_CTX_check_private_key(k->ctx);
if(r != 1) {
printf("Error: checking the private key failed. \n");
ERR_print_errors_fp(stderr);
return -6;
}
sprintf(k->name, "+ %s", keyname);
return 0;
}
int krx_ssl_verify_peer(int ok, X509_STORE_CTX* ctx) {
return 1;
}
/* this sets up the SSL* */
int krx_ssl_init(krx* k, int isserver, info_callback cb) {
/* create SSL* */
k->ssl = SSL_new(k->ctx);
if(!k->ssl) {
printf("Error: cannot create new SSL*.\n");
return -1;
}
/* info callback */
SSL_set_info_callback(k->ssl, cb);
/* bios */
k->in_bio = BIO_new(BIO_s_mem());
if(k->in_bio == NULL) {
printf("Error: cannot allocate read bio.\n");
return -2;
}
BIO_set_mem_eof_return(k->in_bio, -1); /* see: https://www.openssl.org/docs/crypto/BIO_s_mem.html */
k->out_bio = BIO_new(BIO_s_mem());
if(k->out_bio == NULL) {
printf("Error: cannot allocate write bio.\n");
return -3;
}
BIO_set_mem_eof_return(k->out_bio, -1); /* see: https://www.openssl.org/docs/crypto/BIO_s_mem.html */
SSL_set_bio(k->ssl, k->in_bio, k->out_bio);
/* either use the server or client part of the protocol */
if(isserver == 1) {
SSL_set_accept_state(k->ssl);
}
else {
SSL_set_connect_state(k->ssl);
}
return 0;
}
void krx_ssl_server_info_callback(const SSL* ssl, int where, int ret) {
krx_ssl_info_callback(ssl, where, ret, "server");
}
void krx_ssl_client_info_callback(const SSL* ssl, int where, int ret) {
krx_ssl_info_callback(ssl, where, ret, "client");
}
void krx_ssl_info_callback(const SSL* ssl, int where, int ret, const char* name) {
if(ret == 0) {
printf("-- krx_ssl_info_callback: error occured.\n");
return;
}
SSL_WHERE_INFO(ssl, where, SSL_CB_LOOP, "LOOP");
SSL_WHERE_INFO(ssl, where, SSL_CB_HANDSHAKE_START, "HANDSHAKE START");
SSL_WHERE_INFO(ssl, where, SSL_CB_HANDSHAKE_DONE, "HANDSHAKE DONE");
}
int krx_ssl_handle_traffic(krx* from, krx* to) {
// Did SSL write something into the out buffer
char outbuf[4096];
int written = 0;
int read = 0;
int pending = BIO_ctrl_pending(from->out_bio);
if(pending > 0) {
read = BIO_read(from->out_bio, outbuf, sizeof(outbuf));
}
printf("%s Pending %d, and read: %d\n", from->name, pending, read);
if(read > 0) {
written = BIO_write(to->in_bio, outbuf, read);
}
if(written > 0) {
if(!SSL_is_init_finished(to->ssl)) {
SSL_do_handshake(to->ssl);
}
else {
read = SSL_read(to->ssl, outbuf, sizeof(outbuf));
printf("%s read: %s\n", to->name, outbuf);
}
}
return 0;
}
int krx_ssl_shutdown(krx* k) {
if(!k) {
return -1;
}
if(k->ctx) {
SSL_CTX_free(k->ctx);
k->ctx = NULL;
}
if(k->ssl) {
SSL_free(k->ssl);
k->ssl = NULL;
}
return 0;
}