OpenSSL编程的基本步骤
===================================== 启用加密 ======================================
客户端必备过程:
0. 变量定义
BIO *conn; /*底层socket连接*/
SSL *ssl; /*SSL连接*/
SSL_CTX *ctx; /*SSL会话、上下文*/
1. 初始化
SSL_library_init() 或者 OpenSSL_add_ssl_algorithms()
/*SSL_library_init() registers the available SSL/TLS ciphers and digests.*/
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_method( ));
/*
SSL_CTX对象将是产生SSL连接对象的工厂,是上下文的联系。 这个ssl的上下文使我们可以在建立
连接之前设置连接配置参数,例如协议版本,证书信息和验证要求。
SSL_CTX_new() creates a new SSL_CTX object as framework to establish TLS/SSL or DTLS enabled
connections. An SSL_CTX object is reference counted.
*/
SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR);
/*
set default locations for trusted CA certificates
将受信任的证书正确加载到SSL_CTX对象中,OpenSSL就具有一个内置功能来自动验证对等方的证书链
*/
SSL_CTX_set_default_verify_paths(ctx);
/*
可选
指定应使用加载CA证书的默认位置
*/
SSL_CTX_use_certificate_chain_file(ctx, CERTFILE);
/*
加载信任的证书链,SSL_use_certificate_chain_file()也可以
SSL_CTX_use_certificate_chain_file() loads a certificate chain from file into ctx. The certificates
must be in PEM format and must be sorted starting with the subject's certificate (actual client or
server certificate), followed by intermediate CA certificates if applicable, and ending at the
highest level (root) CA. SSL_use_certificate_chain_file() is similar except it loads the certificate
chain into ssl.
*/
SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM);
/*
加载与证书中嵌入的公共密钥相对应私钥文件
SSL_CTX_use_PrivateKey_file() adds the first private key found in file to ctx. The formatting type
of the private key must be specified from the known types SSL_FILETYPE_PEM, SSL_FILETYPE_ASN1
*/
/*
SSL_CTX_set_default_passwd_cb()
sets the default password callback called when loading/storing a PEM certificate with encryption.
*/
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback/*下面举例了*/);
/*
控制握手期间如何处理证书和请求:SSL_VERIFY_PEER
*/
SSL_CTX_set_verify_depth(ctx, 4);
/*
证书验证深度
*/
SSL_CTX_set_options(ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
SSL_CTX_set_cipher_list(ctx, CIPHER_LIST); /* #define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" */
/*
设置可用的密码套件
*/
2. 连接建立
conn = BIO_new_connect(SERVER ":" PORT);
/*
创建一个新的BIO连接
*/
BIO_do_connect(conn);
/*
BIO_do_connect()尝试连接提供的BIO。
*/
ssl = SSL_new(ctx);
/*
创建SSL类型对象,处理TLS/SSL连接。ssl继承底层ctx中的信息:connection method, options,
verification settings, timeout settings。An SSL structure is reference counted
*/
SSL_set_bio(ssl, conn, conn);
/*
connect the SSL object with a BIO
*/
SSL_connect(ssl);
/*
initiate the TLS/SSL handshake with an TLS/SSL server。
辅助函数BIO_set_tcp_ndelay()可用于打开或关闭TCP_NODELAY选项。
*/
post_connection_check(ssl, SERVER); /*一些验证,后面举例了*/
SSL_write(ssl, buf + nwritten, sizeof(buf) - nwritten);
/* 数据读写
If necessary, a write function will negotiate a TLS/SSL session, if not already explicitly performed by
SSL_connect(3) or SSL_accept(3). If the peer requests a re-negotiation, it will be performed
transparently during the write function operation. The behaviour of the write functions depends on the
underlying BIO.
有阻塞和非阻塞两种情况。non-blocking blocking
*/
SSL_shutdown(ssl);
/*
SSL_shutdown()关闭活动的TLS / SSL连接。它将close_notify关闭警报发送给对等方。
SSL_shutdown()仅关闭写入方向。调用SSL_shutdown()后无法调用SSL_write()。读取方向被对等方关闭。
*/
SSL_clear(ssl);
/*
重置SSL对象以允许另一个连接。如果会话仍处于打开状态,则将其视为错误会话,并将按照RFC2246
的要求将其从会话缓存中删除。
*/
3. 资源释放
SSL_free(ssl);
SSL_CTX_free(ctx);
/*BIO_free(conn); 无需使用*/
--------------------------------------------------------------------
服务端必备过程:
0. 变量定义
BIO *acc, *client;
SSL *ssl;
SSL_CTX *ctx;
1. 初始化
SSL_library_init() 或者 OpenSSL_add_ssl_algorithms()
/*SSL_library_init() registers the available SSL/TLS ciphers and digests.*/
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_method());
SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR)
SSL_CTX_set_default_verify_paths(ctx)
SSL_CTX_use_certificate_chain_file(ctx, CERTFILE);
SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);
SSL_CTX_set_verify_depth(ctx, 4);
SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_SINGLE_DH_USE);
SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_callback);
SSL_CTX_set_cipher_list(ctx, CIPHER_LIST);
2. 连接建立
acc = BIO_new_accept(PORT);
BIO_do_accept(acc);
BIO_do_accept(acc);
/*BIO_do_accept() serves two functions. When it is first called, after the accept BIO has been setup, it
will attempt to create the accept socket and bind an address to it. Second and subsequent calls to
BIO_do_accept() will await an incoming connection, or request a retry in non blocking mode.*/
client = BIO_pop(acc);
ssl = SSL_new(ctx);
SSL_set_bio(ssl, client, client);
SSL_set_accept_state(ssl);
SSL_accept(ssl);
post_connection_check(ssl, CLIENT);
SSL_read(ssl, buf + nread, sizeof(buf) - nread);
(SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) ? 1 : 0;
/*
判断是否收到了对端的shutdown,客户端也应该添加这条
*/
SSL_shutdown(ssl);
SSL_clear(ssl);
ERR_remove_state(0);
/*
void ERR_remove_state(unsigned long tid);
frees the error queue associated with the specified thread, identified by tid
*/
3. 资源释放
SSL_free(ssl);
SSL_CTX_free(ctx);
=================================二、 其它特性=================================
1. 保存session
SSL_set_session(ssl, saved session);
SSL_get1_session(ssl);
保存session到磁盘
int new_session_cb(SSL *ctx, SSL_SESSION *session);
void remove_session_cb(SSL_CTX *ctx, SSL_SESSION *session);
SSL_SESSION *get_session_cb(SSL *ctx, unsigned char *id, int len, int *ref);
2. 同步/异步消息读取
3. 会话重新协商(更新对话秘钥)
SSL_renegotiate(ssl);
=================================三、一些证书验证的函数、回调函数=================================
int verify_callback(int ok, X509_STORE_CTX *store)
{
char data[256];
if (!ok)
{
X509 *cert = X509_STORE_CTX_get_current_cert(store);
int depth = X509_STORE_CTX_get_error_depth(store);
int err = X509_STORE_CTX_get_error(store);
fprintf(stderr, "-Error with certificate at depth: %i\n", depth);
X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
fprintf(stderr, " issuer = %s\n", data);
X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
fprintf(stderr, " subject = %s\n", data);
fprintf(stderr, " err %i:%s\n", err, X509_verify_cert_error_string(err));
}
return ok;
}
long post_connection_check(SSL *ssl, char *host)
{
X509 *cert;
X509_NAME *subj;
char data[256];
int extcount;
int ok = 0;
/* Checking the return from SSL_get_peer_certificate here is not strictly
* necessary. With our example programs, it is not possible for it to return
* NULL. However, it is good form to check the return since it can return NULL
* if the examples are modified to enable anonymous ciphers or for the server
* to not require a client certificate.
*/
if (!(cert = SSL_get_peer_certificate(ssl)) || !host)
goto err_occured;
if ((extcount = X509_get_ext_count(cert)) > 0)
{
int i;
for (i = 0; i < extcount; i++)
{
char *extstr;
X509_EXTENSION *ext;
ext = X509_get_ext(cert, i);
extstr = (char*) OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
if (!strcmp(extstr, "subjectAltName"))
{
int j;
unsigned char *data;
STACK_OF(CONF_VALUE) *val;
CONF_VALUE *nval;
X509V3_EXT_METHOD *meth;
void *ext_str = NULL;
if (!(meth = X509V3_EXT_get(ext)))
break;
data = ext->value->data;
#if (OPENSSL_VERSION_NUMBER > 0x00907000L)
if (meth->it)
ext_str = ASN1_item_d2i(NULL, &data, ext->value->length,
ASN1_ITEM_ptr(meth->it));
else
ext_str = meth->d2i(NULL, &data, ext->value->length);
#else
ext_str = meth->d2i(NULL, &data, ext->value->length);
#endif
val = meth->i2v(meth, ext_str, NULL);
for (j = 0; j < sk_CONF_VALUE_num(val); j++)
{
nval = sk_CONF_VALUE_value(val, j);
if (!strcmp(nval->name, "DNS") && !strcmp(nval->value, host))
{
ok = 1;
break;
}
}
}
if (ok)
break;
}
}
if (!ok && (subj = X509_get_subject_name(cert)) &&
X509_NAME_get_text_by_NID(subj, NID_commonName, data, 256) > 0)
{
data[255] = 0;
if (strcasecmp(data, host) != 0)
goto err_occured;
}
X509_free(cert);
return SSL_get_verify_result(ssl);
err_occured:
if (cert)
X509_free(cert);
return X509_V_ERR_APPLICATION_VERIFICATION;
}
void init_dhparams(void)
{
BIO *bio;
bio = BIO_new_file("dh512.pem", "r");
if (!bio)
int_error("Error opening file dh512.pem");
dh512 = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
if (!dh512)
int_error("Error reading DH parameters from dh512.pem");
BIO_free(bio);
bio = BIO_new_file("dh1024.pem", "r");
if (!bio)
int_error("Error opening file dh1024.pem");
dh1024 = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
if (!dh1024)
int_error("Error reading DH parameters from dh1024.pem");
BIO_free(bio);
}
DH *tmp_dh_callback(SSL *ssl, int is_export, int keylength)
{
DH *ret;
if (!dh512 || !dh1024)
init_dhparams( );
switch (keylength)
{
case 512:
ret = dh512;
break;
case 1024:
default: /* generating DH params is too costly to do on the fly */
ret = dh1024;
break;
}
return ret;
}
#define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"