您的位置:首页 > 运维架构

多线程环境下使用openssl

2014-02-12 17:25 549 查看
openssl 官网说了:

OpenSSL can safely be used in multi-threaded applications provided that at least two callback functions are set, locking_function and threadid_func.
还有这里说了,Openssl 是安全的,前提是必须注册两个回调函数。其中根据 Openssl 的版本不同,会有不同 版本的 threadid 回调函数。

1. Is OpenSSL thread-safe?

Yes (with limitations: an SSL connection may not concurrently be used by multiple threads). On Windows and many Unix systems, OpenSSL automatically uses the multi-threaded versions of the standard libraries. If your platform is not one of these, consult the INSTALL file.

Multi-threaded applications must provide two callback functions to OpenSSL by calling CRYPTO_set_locking_callback() and CRYPTO_set_id_callback(), for all versions of OpenSSL up to and including 0.9.8[abc...]. As of version 1.0.0, CRYPTO_set_id_callback() and associated APIs are deprecated by CRYPTO_THREADID_set_callback() and friends. This is described in the threads(3) manpage.


这里有几个例子程序,主要是在使用openssl之前,做一些初始化和设置callback函数,以及退出时的范初始化动作:

http://www.cs.odu.edu/~cs772/sourcecode/NSwO/compiled/common.c
http://ardoino.com/pub/crypto-security/sslc.c
http://curl.haxx.se/libcurl/c/opensslthreadlock.html
http://curl.haxx.se/libcurl/c/threaded-ssl.html

这里有老外推荐的3本书的链接:

Chapter 10 of the book The
Definitive Guide to Linux Network Programming includes a section Thread-safe Programming with OpenSSL (on pages 255-259). This section details
how OpenSSL and the pthreads library work. Specially, it tells how to setup the callback functions both in static allocation (where the number of threads are known a
priori) and dynamic allocation (where threads are created and destroyed on the fly).

Another good source is Section 4.1 of the book Network
Security with OpenSSL, titled Multithread Support. It provides static/dynamic allocation mechanisms in subsections 4.1.1 and 4.1.2, respectively.
Finally, there's the book Unix-Netzwerkprogrammierung
mit Threads, Sockets und SSL, which is by far the most comprehensive one on the subject. Unfortunately, the English translation of this German book is not available.
这里的例子程序中,有对locking callback function 参数的注释:

// Locking callback. The type, file and line arguments are

// ignored. The file and line may be used to identify the site of the

// call in the OpenSSL library for diagnostic purposes if required.

void ecos_locking_callback(int mode, int type, char *file, int line)

{

if (mode & CRYPTO_LOCK)

{

cyg_mutex_lock(&(lock_cs[type]));

}

else

{

cyg_mutex_unlock(&(lock_cs[type]));

}

}
locking callback function 更详细的例子如:

void ssl_locking_function(int mode, int type, const char * file, int line) {

if(CRYPTO_LOCK_FIPS == type

|| CRYPTO_LOCK_FIPS2 == type

|| CRYPTO_LOCK_ERR == type

|| CRYPTO_LOCK_EVP_PKEY == type)

{

printf("ssl_locking_function return directly: %d %d %s %d\n", mode, type, file, line);

return;

}

if(mode & CRYPTO_LOCK) {

if(mode & CRYPTO_WRITE) {

printf("ssl_locking_function write lock: %d %d %s %d\n", mode, type, file, line);

pthread_rwlock_wrlock(&(ssl_lock[type]));

}

else {

printf("ssl_locking_function read lock: %d %d %s %d\n", mode, type, file, line);

pthread_rwlock_rdlock(&(ssl_lock[type]));

}

}

else

{

printf("ssl_locking_function unlock: %d %d %s %d\n", mode, type, file, line);

pthread_rwlock_unlock(&(ssl_lock[type]));

}

}
上面的代码中,有些被划掉了的原因是这样的:
在有些性能至关重要的项目中,相比其他值,openssl 会更加频繁地用 CRYPTO_LOCK_FIPS、CRYPTO_LOCK_FIPS2、CRYPTO_LOCK_ERR、CRYPTO_LOCK_EVP_PKEY 中的某些值回调,当像上面的代码中那样直接 return 从而忽视这些值的回调后,性能陡然上升许多,但貌似存在 crash 的风险。如果针对这些值也枷锁的话,程序的性能会下降的很厉害。其中,CRYPTO_LOCK_ERR
是最频繁回调的,在不支持FIPS 的情况下,CRYPTO_LOCK_FIPS 和 CRYPTO_LOCK_FIPS2 是不会被回调的。总的来说,一般情况下,被划掉的代码是不需要的。

基本上,locking回调函数中的几个参数的含义如下:

参数含义
mode指明是lock/unlock,或者更详细些,read lock 或 write lock
type是初始化时动态分配的lock_cs数组的下标,即指明当前用的是第几个lock
file用于程序诊断,指明当前的lock调用出自openssl的哪个文件
line用于程序诊断,指明当前的lock调用出自openssl的哪个文件的哪一行
下面是一个比较完整的例子(出处):

==================

// 要注册的两个回调函数就长这样的.

// Pointer to array of locks.

void ecos_locking_callback(int mode, int type, char *file, int line);

unsigned long ecos_thread_id_callback(void);

// ecos_locking_callback 函数中要用到的 lock 数组申明
// 这里没有的 cyg_mutex_t 的类型定义,应该是原作者自定义的东西,Linux 下一般就是 pthread_mutex_t 之类的

// Pointer to array of locks.
static cyg_mutex_t *lock_cs;

// 多线程保护的初始化

// This function allocates and initializes the lock array

// and registers the callbacks. This should be called

// after the OpenSSL library has been initialized and

// before any new threads are created.

void thread_setup(void)

{

int i;

// 动态创建 lock 数组

// Allocate lock array according to OpenSSL's requirements

lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(cyg_mutex_t));

// lock 数组初始化

// Initialize the locks

for (i=0; i<CRYPTO_num_locks(); i++)

{

cyg_mutex_init(&(lock_cs[i]));

}

// 注册两个回调函数

// Register callbacks

CRYPTO_set_id_callback((unsigned long (*)())ecos_thread_id_callback);

CRYPTO_set_locking_callback((void (*)())ecos_locking_callback);

}

// 多线程保护的反初始化

// This function deallocates the lock array and deregisters the

// callbacks. It should be called after all threads have

// terminated.

void thread_cleanup(void)

{

int i;

// 清空 locking 回调函数

// Deregister locking callback. No real need to

// deregister id callback.

CRYPTO_set_locking_callback(NULL);

// 销毁初始化时分配的 lock 数组

// Destroy the locks

for (i=0; i<CRYPTO_num_locks(); i++)

{

cyg_mutex_destroy(&(lock_cs[i]));

}

// 销毁初始化时分配的 lock 数组

// Release the lock array.

OPENSSL_free(lock_cs);
lock_cs = NULL;

}

// locking 回调函数,由openssl库回调,向 openssl 库 提供 lock/unlock,或更详细些 read lock 或 write lock 的功能

// Locking callback. The type, file and line arguments are

// ignored. The file and line may be used to identify the site of the

// call in the OpenSSL library for diagnostic purposes if required.

void ecos_locking_callback(int mode, int type, char *file, int line)

{

if (mode & CRYPTO_LOCK)

{

cyg_mutex_lock(&(lock_cs[type]));

}

else

{

cyg_mutex_unlock(&(lock_cs[type]));

}

}

// thraed id 回调函数,由openssl回调,向 openssl 库提供当前线程号

// Thread id callback.

unsigned long ecos_thread_id_callback(void)

{

return (unsigned long)cyg_thread_get_id(cyg_thread_self());

}

【注意】
openssl 官网提到下面的有关 openssl 版本的注意事项

CRYPTO_set_locking_callback() is available in all versions of SSLeay and OpenSSL. CRYPTO_num_locks() was added in OpenSSL 0.9.4. All functions dealing with dynamic locks were added in OpenSSL
0.9.5b-dev. CRYPTO_THREADID and associated functions were introduced in OpenSSL 1.0.0 to replace (actually, deprecate) the previousCRYPTO_set_id_callback(), CRYPTO_get_id_callback(),
and CRYPTO_thread_id() functions which assumed thread IDs to always be represented by 'unsigned long'.

即,从 openssl 1.0.0 开始,CRYPTO_set_id_callback() 就被废除了,CRYPTO_THREADID_set_callback()
取而代之。因此,注册 threadid 的 callback 函数的时候,要注意判断当前使用的 openssl 的版本。鉴于
openssl 下载页面中出现的最早的 1.0.0 版本,即

3954601 Apr 1 11:11:42 2009openssl-1.0.0-beta1.tar.gz

中的 openssl/crypto.h 中的 OPENSSL_VERSION_NUMBER 定义为 0x10000001L

#define OPENSSL_VERSION_NUMBER 0x10000001L

于是,判断 openssl 版本的做法如下:
(注意,这里是不建议用 SSLeay() >= OPENSSL_VERSION_NUMBER_1_0_0 的方式的,因为定义回调函数的地方是函数申明,肯定不行,并且下面3处也不能假设任何版本的 openssl
中都定义了 CRYPTO_THREADI 类型。呵呵,幸好 C 语言的宏还支持数值大小比较)

1)定义两个版本的 threadid 回调函数
#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
void ssl_threadid_function(CRYPTO_THREADID * id);
#else
unsigned long ssl_threadid_function_deprecated();
#endif


2)注册回调
#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
CRYPTO_THREADID_set_callback(ssl_threadid_function);
#else
CRYPTO_set_id_callback(ssl_threadid_function_deprecated);
#endif


3)反注册回调

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_0
CRYPTO_THREADID_set_callback(NULL);
#else
CRYPTO_set_id_callback(NULL);
#endif


Stackoverflow 的这篇帖子 的回复中,有提到一直build openssl 时就配置“支持多线程”的方式。
实际上说的就是,openssl 的代码中判断了 OPENSSL_THREADS 宏,当该宏定义了,也就是在build Openssl 之前,做了
./config threads 操作,就支持多线程,否则不支持多线程。

根据这里说的

1. Is OpenSSL thread-safe?

Yes (with limitations: an SSL connection may not concurrently be used by multiple threads). On Windows and many Unix systems, OpenSSL automatically uses the multi-threaded versions of the standard libraries. If your platform is not one of these, consult the INSTALL file.

Multi-threaded applications must provide two callback functions to OpenSSL by calling CRYPTO_set_locking_callback() and CRYPTO_set_id_callback(), for all versions of OpenSSL up to and including 0.9.8[abc...]. As of version 1.0.0, CRYPTO_set_id_callback() and associated APIs are deprecated by CRYPTO_THREADID_set_callback() and friends. This is described in the threads(3) manpage.
个人理解是,依然是“Openssl 的所谓的多线程安全”最多只是“在调用者提供了两个回调函数的前提下的”多线程安全。如果在build Openssl 的时候,强制加上了"no-threads"选项,即先 ./config no-threads,那么这样build 出来的 Openssl,连“在调用者提供了两个回调函数的前提下的”多线程安全都保证不了!

总而言之,至少要注册那两个回调函数,并注意区分不同的 Openssl 版本。

参考:

http://www.openssl.org/docs/crypto/threads.html

http://stackoverflow.com/questions/3919420/tutorial-on-using-openssl-with-pthreads

http://ardoino.com/2008/02/openssl-thread-safe-secure-connections/

按照官网手册中说的,使用openssl,要先通过 CRYPTO_set_locking_callback() 和 CRYPTO_set_id_callback() 注册两个 callback 函数,只有这样,才能确保 openssl 的 API 是 thread-safe 的。

对此,ardoino 的博客中有详细的说明和例子。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: