用OpenSSL编写SSL,TLS程序

http://zhoulifa.bokee.com/6134045.html

http://blog.sina.com.cn/s/blog_86ca13bb0100vaph.html

http://blog.chinaunix.net/uid-26575352-id-3048856.html

一、简介:

SSL(SecureSocket Layer)是netscape公司提出的主要用于web的安全通信标准,分为2.0版和3.0版.TLS(TransportLayer Security)是IETF的TLS工作组在SSL3.0基础之上提出的安全通信标准,目前版本是1.0,即RFC2246.SSL/TLS提供的安全机制可以保证应用层数据在互联网络传输不 被监听,伪造和窜改.

openssl(www.openssl.org)是sslv2,sslv3,tlsv1的一份完整实现,内部包含了大量加密算法程序.其命令行提供了丰富的加密,验证,证书生成等功能,甚至可以用其建立一个完整的CA.与其同时,它也提供了一套完整的库函数,可用开发用SSL/TLS的通信程序.Apache的https两种版本mod_ssl和apachessl均基于它实现的.openssl继承于ssleay,并做了一定的扩展,当前的版本是0.9.5a.

openssl的缺点是文档太少,连一份完整的函数说明都没有,man page也至今没做完整:-(,如果想用它编程序,除了熟悉已有的文档(包括ssleay,mod_ssl,apachessl的文档)外,可以到它的maillist上找相关的帖子,许多问题可以在以前的文章中找到答案.

基于OpenSSL的程序都要遵循以下几个步骤:

(1 ) OpenSSL初始化

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

int SSL_library_init(void);

(2 ) 选择会话协议

在利用OpenSSL开始SSL会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0、SSLv2、SSLv3、SSLv2/v3。

需要注意的是,客户端和服务器必须使用相互兼容的协议,否则SSL会话将无法正常进行。

(3 ) 创建会话环境

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

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

SSL_CTX *SSL_CTX_new(SSL_METHOD * method);

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

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

int SSL_CTX_set_verify(SSL_CTX *ctx,intmode,int(*verify_callback),int(X509_STORE_CTX *));

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

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

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

SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,inttype);

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

SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,inttype);

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

int SSL_CTX_check_private_key(SSL_CTX *ctx);

(4) 建立SSL套接字

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

SSL *SSl_new(SSL_CTX *ctx);    //申请一个SSL套接字

int SSL_set_fd(SSL *ssl,int fd);    //绑定读写套接字

int SSL_set_rfd(SSL *ssl,int fd);    //绑定只读套接字

int SSL_set_wfd(SSL *ssl,int fd);    //绑定只写套接字

(5) 完成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);

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

(6) 进行数据传输

当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);

(7 ) 结束SSL通信

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

int SSL_shutdown(SSL *ssl);    //关闭SSL套接字

void SSl_free(SSL *ssl);   
//释放SSL套接字

 
void SSL_CTX_free(SSL_CTX *ctx);     //释放SSL会话环境


客户端与服务端编程框架:

程序分为两部分,客户端和服务器端,我们的目的是利用SSL/TLS的特性保证通信双方能够互相验证对方身份(真实性),并保证数据的完整性, 私密性.


1.客户端程序的框架为:

/*生成一个SSL结构*/
meth = SSLv23_client_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);

/*下面是正常的socket过程*/
fd = socket();
connect();

/*把建立好的socket和SSL结构联系起来*/
SSL_set_fd(ssl,fd);

/*SSL的握手过程*/
SSL_connect(ssl);

/*接下来用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_write(ssl,”Hello world”,strlen(“Hello World!”));

2.服务端程序的框架为:
/*生成一个SSL结构*/
meth = SSLv23_server_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);

/*下面是正常的socket过程*/
fd = socket();
bind();
listen();
accept();

/*把建立好的socket和SSL结构联系起来*/
SSL_set_fd(ssl,fd);

/*SSL的握手过程*/
SSL_connect(ssl);

/*接下来用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_read (ssl, buf, sizeof(buf));

根据RFC2246(TLS1.0)整个TLS(SSL)的流程如下:

Client                                                Server

ClientHello           ——–>
                                                ServerHello
                                                 Certificate*
                                          ServerKeyExchange*
                                            CertificateRequest*
                           <——–      ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished               ——–>
                                             [ChangeCipherSpec]
                            <——–             Finished
Application Data   <——->     Application Data

对程序来说,openssl将整个握手过程用一对函数体现,即客户端的SSL_connect和服务端的SSL_accept.而后的应用层数据交换则用SSL_read和 SSL_write来完成.

二、证书文件生成

除将程序编译成功外,还需生成必要的证书和私钥文件使双方能够成功验证对方,步骤如下:

1.首先要生成服务器端的私钥(key文件):
openssl genrsa -des3 -out server.key 1024
运行时会提示输入密码,此密码用于加密key文件(参数des3便是指加密算法,当然也可以选用其他你认为安全的算法.),以后每当需读取此文 件(通过openssl提供的命令或API)都需输入口令.如果觉得不方便,也可以去除这个口令,但一定要采取其他的保护措施!
去除key文件口令的命令:
openssl rsa -in server.key -out server.key

2.openssl req -new -key server.key -out server.csr
生成Certificate Signing Request(CSR),生成的csr文件交给CA签名后形成服务端自己的证书.屏幕上将有提示,依照其指示一步一步输入要 求的个人信息即可.

3.对客户端也作同样的命令生成key及csr文件:
openssl genrsa -des3 -out client.key 1024
openssl req -new -key client.key -out client.csr

4.CSR文件必须有CA的签名才可形成证书.可将此文件发送到verisign等地方由它验证,要交一大笔钱,何不自己做CA呢.
首先生成CA的key文件:
openssl -des3 -out ca.key 1024
在生成CA自签名的证书:
openssl req -new -x509 -key ca.key -out ca.crt
如果想让此证书有个期限,如一年,则加上”-days 365”.
(“如果非要为这个证书加上一个期限,我情愿是..一万年”)

5.用生成的CA的证书为刚才生成的server.csr,client.csr文件签名:
可以用openssl中CA系列命令,但不是很好用(也不是多难,唉,一言难尽),一篇文章中推荐用mod_ssl中的sign.sh脚本,试了一下,确实方便了不 少,如果ca.csr存在的话,只需:
./sigh.sh server.csr
./sign.sh client.csr
相应的证书便生成了(后缀.crt).

现在我们所需的全部文件便生成了.

其实openssl中还附带了一个叫CA.pl的文件(在安装目录中的misc子目录下),可用其生成以上的文件,使用也比较方便,但此处就不作介绍了.

三、需要了解的一些函数:

1.int    SSL_CTX_set_cipher_list(SSL_CTX *,const char *str);

SSL_CTX_set_cipher_list() sets the list of available ciphers for ctx using the control string str. The format of the string is described in ciphers. The list of ciphers is inherited by all ssl objects created from ctx.
根据SSL/TLS规范,在ClientHello中,客户端会提交一份自己能够支持的加密方法的列表,由服务端选择一种方法后在ServerHello中通知客户端, 从而完成加密算法的协商.

如果服务端只设置了一种加密套件,那么客户端要么接受要么返回错误。加密套件的选择是由服务端做出的

可用的算法为:
EDH-RSA-DES-CBC3-SHA
EDH-DSS-DES-CBC3-SHA
DES-CBC3-SHA
DHE-DSS-RC4-SHA
IDEA-CBC-SHA
RC4-SHA
RC4-MD5
EXP1024-DHE-DSS-RC4-SHA
EXP1024-RC4-SHA
EXP1024-DHE-DSS-DES-CBC-SHA
EXP1024-DES-CBC-SHA
EXP1024-RC2-CBC-MD5
EXP1024-RC4-MD5
EDH-RSA-DES-CBC-SHA
EDH-DSS-DES-CBC-SHA
DES-CBC-SHA
EXP-EDH-RSA-DES-CBC-SHA
EXP-EDH-DSS-DES-CBC-SHA
EXP-DES-CBC-SHA
EXP-RC2-CBC-MD5
EXP-RC4-MD5


下面的openssl命令可以列出支持的所有算法,并按强度排序:

# openssl ciphers -v ‘ALL:!ADH:@STRENGTH’
DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
DHE-DSS-AES256-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(256)  Mac=SHA1
AES256-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA1
EDH-RSA-DES-CBC3-SHA    SSLv3 Kx=DH       Au=RSA  Enc=3DES(168) Mac=SHA1
EDH-DSS-DES-CBC3-SHA    SSLv3 Kx=DH       Au=DSS  Enc=3DES(168) Mac=SHA1
DES-CBC3-SHA            SSLv3 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=SHA1
DES-CBC3-MD5            SSLv2 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=MD5
DHE-RSA-AES128-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-DSS-AES128-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(128)  Mac=SHA1
AES128-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-DSS-RC4-SHA         SSLv3 Kx=DH       Au=DSS  Enc=RC4(128)  Mac=SHA1
RC4-SHA                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=SHA1
RC4-MD5                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=MD5
RC2-CBC-MD5             SSLv2 Kx=RSA      Au=RSA  Enc=RC2(128)  Mac=MD5
RC4-MD5                 SSLv2 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=MD5
RC4-64-MD5              SSLv2 Kx=RSA      Au=RSA  Enc=RC4(64)   Mac=MD5
EXP1024-DHE-DSS-RC4-SHA SSLv3 Kx=DH(1024) Au=DSS  Enc=RC4(56)   Mac=SHA1 export
EXP1024-RC4-SHA         SSLv3 Kx=RSA(1024) Au=RSA  Enc=RC4(56)   Mac=SHA1 export
EXP1024-DHE-DSS-DES-CBC-SHA SSLv3 Kx=DH(1024) Au=DSS  Enc=DES(56)   Mac=SHA1 export
EXP1024-DES-CBC-SHA     SSLv3 Kx=RSA(1024) Au=RSA  Enc=DES(56)   Mac=SHA1 export
EXP1024-RC2-CBC-MD5     SSLv3 Kx=RSA(1024) Au=RSA  Enc=RC2(56)   Mac=MD5  export
EXP1024-RC4-MD5         SSLv3 Kx=RSA(1024) Au=RSA  Enc=RC4(56)   Mac=MD5  export
EDH-RSA-DES-CBC-SHA     SSLv3 Kx=DH       Au=RSA  Enc=DES(56)   Mac=SHA1
EDH-DSS-DES-CBC-SHA     SSLv3 Kx=DH       Au=DSS  Enc=DES(56)   Mac=SHA1
DES-CBC-SHA             SSLv3 Kx=RSA      Au=RSA  Enc=DES(56)   Mac=SHA1
DES-CBC-MD5             SSLv2 Kx=RSA      Au=RSA  Enc=DES(56)   Mac=MD5
EXP-EDH-RSA-DES-CBC-SHA SSLv3 Kx=DH(512)  Au=RSA  Enc=DES(40)   Mac=SHA1 export
EXP-EDH-DSS-DES-CBC-SHA SSLv3 Kx=DH(512)  Au=DSS  Enc=DES(40)   Mac=SHA1 export
EXP-DES-CBC-SHA         SSLv3 Kx=RSA(512) Au=RSA  Enc=DES(40)   Mac=SHA1 export
EXP-RC2-CBC-MD5         SSLv3 Kx=RSA(512) Au=RSA  Enc=RC2(40)   Mac=MD5  export
EXP-RC4-MD5             SSLv3 Kx=RSA(512) Au=RSA  Enc=RC4(40)   Mac=MD5  export
EXP-RC2-CBC-MD5         SSLv2 Kx=RSA(512) Au=RSA  Enc=RC2(40)   Mac=MD5  export
EXP-RC4-MD5             SSLv2 Kx=RSA(512) Au=RSA  Enc=RC4(40)   Mac=MD5  export


这些算法按一定优先级排列,如果不作任何指定,将选用DES-CBC3-SHA.用SSL_CTX_set_cipher_list可以指定自己希望用的算法(实际上只是 提高其优先级,是否能使用还要看对方是否支持).

我们在程序中选用了RC4做加密,MD5做消息摘要(先进行MD5运算,后进行RC4加密).即
SSL_CTX_set_cipher_list(ctx,”RC4-MD5”);

在消息传输过程中采用对称加密(比公钥加密在速度上有极大的提高),其所用秘钥(shared secret)在握手过程中中协商(每次对话过程均不同, 在一次对话中都有可能有几次改变),并通过公钥加密的手段由客户端提交服务端.


SSL_set_cipher_list

int SSL_set_cipher_list(SSL *ssl, const char *str);

SSL_set_cipher_list() sets the list of ciphers only for ssl.

RETURN VALUES

SSL_CTX_set_cipher_list() and SSL_set_cipher_list() return 1 if any cipher could be selected and 0 on complete failure.


2.void SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int (*callback)(int, X509_STORE_CTX *));
缺省mode是SSL_VERIFY_NONE,如果想要验证对方的话,便要将此项变成SSL_VERIFY_PEER.SSL/TLS中缺省只验证server,如果没有设置 SSL_VERIFY_PEER的话,客户端连证书都不会发过来.

3.int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,const char *CApath);
要验证对方的话,当然装要有CA的证书了,此函数用来便是加载CA的证书文件的.

4.int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
加载自己的证书文件.

5.int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
加载自己的私钥,以用于签名.

6.int SSL_CTX_check_private_key(SSL_CTX *ctx);
调用了以上两个函数后,自己检验一下证书与私钥是否配对.
7.void RAND_seed(const void *buf,int num);
在win32的环境中client程序运行时出错(SSL_connect返回-1)的一个主要机制便是与UNIX平台下的随机数生成机制不同(握手的时候用的到).具体描述可见mod_ssl的FAQ.解决办法就是调用此函数,其中buf应该为一随机的字符串,作为”seed”.
还可以采用一下两个函数:
void RAND_screen(void);
int RAND_event(UINT, WPARAM, LPARAM);
其中RAND_screen()以屏幕内容作为”seed”产生随机数,RAND_event可以捕获windows中的事件(event),以此为基础产生随机数.如果一直有 用户干预的话,用这种办法产生的随机数能够”更加随机”,但如果机器一直没人理(如总停在登录画面),则每次都将产生同样的数字.

这几个函数都只在WIN32环境下编译时有用,各种UNIX下就不必调了.
大量其他的相关函数原型,见crypto\rand\rand.h.

8.OpenSSL_add_ssl_algorithms()或SSLeay_add_ssl_algorithms()
其实都是调用int SSL_library_init(void)
进行一些必要的初始化工作,用openssl编写SSL/TLS程序的话第一句便应是它.

9.void    SSL_load_error_strings(void );
如果想打印出一些方便阅读的调试信息的话,便要在一开始调用此函数.

10.void ERR_print_errors_fp(FILE *fp);
如果调用了SSL_load_error_strings()后,便可以随时用ERR_print_errors_fp()来打印错误信息了.

11.X509 *SSL_get_peer_certificate(SSL *s);
握手完成后,便可以用此函数从SSL结构中提取出对方的证书(此时证书得到且已经验证过了)整理成X509结构.

12.X509_NAME *X509_get_subject_name(X509 *a);
得到证书所有者的名字,参数可用通过SSL_get_peer_certificate()得到的X509对象.

13.X509_NAME *X509_get_issuer_name(X509 *a)
得到证书签署者(往往是CA)的名字,参数可用通过SSL_get_peer_certificate()得到的X509对象.

14.char *X509_NAME_oneline(X509_NAME *a,char *buf,int size);
将以上两个函数得到的对象变成字符型,以便打印出来.

15.SSL_METHOD的构造函数,包括
SSL_METHOD *TLSv1_server_method(void);    /* TLSv1.0 */
SSL_METHOD *TLSv1_client_method(void);    /* TLSv1.0 */

SSL_METHOD *SSLv2_server_method(void);    /* SSLv2 */
SSL_METHOD *SSLv2_client_method(void);    /* SSLv2 */

SSL_METHOD *SSLv3_server_method(void);    /* SSLv3 */
SSL_METHOD *SSLv3_client_method(void);    /* SSLv3 */

SSL_METHOD *SSLv23_server_method(void);    /* SSLv3 but can rollback to v2 */
SSL_METHOD *SSLv23_client_method(void);    /* SSLv3 but can rollback to v2 */
在程序中究竟采用哪一种协议(TLSv1/SSLv2/SSLv3),就看调哪一组构造函数了.



四:程序源代码:


服务器端:


    
    
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>
  11. #include <openssl/ssl.h>
  12. #include <openssl/err.h>
  13. #define MAXBUF 1024
  14. /************关于本文档********************************************
  15. *filename: ssl-server.c
  16. *purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是服务器端例子
  17. *wrote by: zhoulifa([email protected]) 周立发(http://zhoulifa.bokee.com)
  18. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  19. *date time:2007-02-02 19:40
  20. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  21. * 但请遵循GPL
  22. *Thanks to:Google
  23. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  24. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  25. *********************************************************************/
  26. int main(int argc, char **argv)
  27. {
  28. int sockfd, new_fd;
  29. socklen_t len;
  30. struct sockaddr_in my_addr, their_addr;
  31. unsigned int myport, lisnum;
  32. char buf[MAXBUF + 1];
  33. SSL_CTX *ctx;
  34. if (argv[ 1])
  35. myport = atoi(argv[ 1]);
  36. else
  37. myport = 7838;
  38. if (argv[ 2])
  39. lisnum = atoi(argv[ 2]);
  40. else
  41. lisnum = 2;
  42. /* SSL 库初始化 */
  43. SSL_library_init();
  44. /* 载入所有 SSL 算法 */
  45. OpenSSL_add_all_algorithms();
  46. /* 载入所有 SSL 错误消息 */
  47. SSL_load_error_strings();
  48. /* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
  49. ctx = SSL_CTX_new(SSLv23_server_method());
  50. /* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
  51. if (ctx == NULL) {
  52. ERR_print_errors_fp( stdout);
  53. exit( 1);
  54. }
  55. /* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
  56. if (SSL_CTX_use_certificate_file(ctx, argv[ 4], SSL_FILETYPE_PEM) <= 0) {
  57. ERR_print_errors_fp( stdout);
  58. exit( 1);
  59. }
  60. /* 载入用户私钥 */
  61. if (SSL_CTX_use_PrivateKey_file(ctx, argv[ 5], SSL_FILETYPE_PEM) <= 0) {
  62. ERR_print_errors_fp( stdout);
  63. exit( 1);
  64. }
  65. /* 检查用户私钥是否正确 */
  66. if (!SSL_CTX_check_private_key(ctx)) {
  67. ERR_print_errors_fp( stdout);
  68. exit( 1);
  69. }
  70. /* 开启一个 socket 监听 */
  71. if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  72. perror( "socket");
  73. exit( 1);
  74. } else
  75. printf( "socket created\n");
  76. bzero(&my_addr, sizeof(my_addr));
  77. my_addr.sin_family = PF_INET;
  78. my_addr.sin_port = htons(myport);
  79. if (argv[ 3])
  80. my_addr.sin_addr.s_addr = inet_addr(argv[ 3]);
  81. else
  82. my_addr.sin_addr.s_addr = INADDR_ANY;
  83. if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  84. == -1) {
  85. perror( "bind");
  86. exit( 1);
  87. } else
  88. printf( "binded\n");
  89. if (listen(sockfd, lisnum) == -1) {
  90. perror( "listen");
  91. exit( 1);
  92. } else
  93. printf( "begin listen\n");
  94. while ( 1) {
  95. SSL *ssl;
  96. len = sizeof(struct sockaddr);
  97. /* 等待客户端连上来 */
  98. if ((new_fd =
  99. accept(sockfd, (struct sockaddr *) &their_addr,
  100. &len)) == -1) {
  101. perror( "accept");
  102. exit(errno);
  103. } else
  104. printf( "server: got connection from %s, port %d, socket %d\n",
  105. inet_ntoa(their_addr.sin_addr),
  106. ntohs(their_addr.sin_port), new_fd);
  107. /* 基于 ctx 产生一个新的 SSL */
  108. ssl = SSL_new(ctx);
  109. /* 将连接用户的 socket 加入到 SSL */
  110. SSL_set_fd(ssl, new_fd);
  111. /* 建立 SSL 连接 */
  112. if (SSL_accept(ssl) == -1) {
  113. perror( "accept");
  114. close(new_fd);
  115. break;
  116. }
  117. /* 开始处理每个新连接上的数据收发 */
  118. bzero(buf, MAXBUF + 1);
  119. strcpy(buf, "server->client");
  120. /* 发消息给客户端 */
  121. len = SSL_write(ssl, buf, strlen(buf));
  122. if (len <= 0) {
  123. printf
  124. ( "消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  125. buf, errno, strerror(errno));
  126. goto finish;
  127. } else
  128. printf( "消息'%s'发送成功,共发送了%d个字节!\n",
  129. buf, len);
  130. bzero(buf, MAXBUF + 1);
  131. /* 接收客户端的消息 */
  132. len = SSL_read(ssl, buf, MAXBUF);
  133. if (len > 0)
  134. printf( "接收消息成功:'%s',共%d个字节的数据\n",
  135. buf, len);
  136. else
  137. printf
  138. ( "消息接收失败!错误代码是%d,错误信息是'%s'\n",
  139. errno, strerror(errno));
  140. /* 处理每个新连接上的数据收发结束 */
  141. finish:
  142. /* 关闭 SSL 连接 */
  143. SSL_shutdown(ssl);
  144. /* 释放 SSL */
  145. SSL_free(ssl);
  146. /* 关闭 socket */
  147. close(new_fd);
  148. }
  149. /* 关闭监听的 socket */
  150. close(sockfd);
  151. /* 释放 CTX */
  152. SSL_CTX_free(ctx);
  153. return 0;
  154. }

客户端:


    
    
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>
  10. #include <openssl/ssl.h>
  11. #include <openssl/err.h>
  12. #define MAXBUF 1024
  13. void ShowCerts(SSL * ssl)
  14. {
  15. X509 cert;
  16. char *line;
  17. cert = SSL_get_peer_certificate(ssl);
  18. if (cert != NULL) {
  19. printf( "数字证书信息:\n");
  20. line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
  21. printf( "证书: %s\n", line);
  22. free(line);
  23. line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
  24. printf( "颁发者: %s\n", line);
  25. free(line);
  26. X509_free(cert);
  27. } else
  28. printf( "无证书信息!\n");
  29. }
  30. /***关于本文档**********************************
  31. filename: ssl-client.c
  32. *purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是客户端例子
  33. *wrote by: zhoulifa([email protected]) 周立发(http://zhoulifa.bokee.com)
  34. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  35. *date time:2007-02-02 20:10
  36. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  37. 但请遵循GPL
  38. Thanks to:Google
  39. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  40. 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  41. ***********************************************************/
  42. int main(int argc, char *argv)
  43. {
  44. int sockfd, len;
  45. struct sockaddr_in dest;
  46. char buffer[MAXBUF + 1];
  47. SSL_CTX *ctx;
  48. SSL *ssl;
  49. if (argc != 3) {
  50. printf
  51. ( "参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  52. argv[ 0], argv[ 0]);
  53. exit( 0);
  54. }
  55. / SSL 库初始化,参看 ssl-server.c 代码 /
  56. SSL_library_init();
  57. OpenSSL_add_all_algorithms();
  58. SSL_load_error_strings();
  59. ctx = SSL_CTX_new(SSLv23_client_method());
  60. if (ctx == NULL) {
  61. ERR_print_errors_fp( stdout);
  62. exit( 1);
  63. }
  64. / 创建一个 socket 用于 tcp 通信 /
  65. if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  66. perror( "Socket");
  67. exit(errno);
  68. }
  69. printf( "socket created\n");
  70. / 初始化服务器端(对方)的地址和端口信息 /
  71. bzero(&dest, sizeof(dest));
  72. dest.sin_family = AF_INET;
  73. dest.sin_port = htons(atoi(argv[ 2]));
  74. if (inet_aton(argv[ 1], (struct in_addr ) &dest.sin_addr.s_addr) == 0) {
  75. perror(argv[ 1]);
  76. exit(errno);
  77. }
  78. printf( "address created\n");
  79. /* 连接服务器 /
  80. if (connect(sockfd, (struct sockaddr ) &dest, sizeof(dest)) != 0) {
  81. perror( "Connect ");
  82. exit(errno);
  83. }
  84. printf( "server connected\n");
  85. /* 基于 ctx 产生一个新的 SSL /
  86. ssl = SSL_new(ctx);
  87. SSL_set_fd(ssl, sockfd);
  88. / 建立 SSL 连接 /
  89. if (SSL_connect(ssl) == -1)
  90. ERR_print_errors_fp( stderr);
  91. else {
  92. printf( "Connected with %s encryption\n", SSL_get_cipher(ssl));
  93. ShowCerts(ssl);
  94. }
  95. / 接收对方发过来的消息,最多接收 MAXBUF 个字节 /
  96. bzero(buffer, MAXBUF + 1);
  97. / 接收服务器来的消息 /
  98. len = SSL_read(ssl, buffer, MAXBUF);
  99. if (len > 0)
  100. printf( "接收消息成功:'%s',共%d个字节的数据\n",
  101. buffer, len);
  102. else {
  103. printf
  104. ( "消息接收失败!错误代码是%d,错误信息是'%s'\n",
  105. errno, strerror(errno));
  106. goto finish;
  107. }
  108. bzero(buffer, MAXBUF + 1);
  109. strcpy(buffer, "from client->server");
  110. / 发消息给服务器 /
  111. len = SSL_write(ssl, buffer, strlen(buffer));
  112. if (len < 0)
  113. printf
  114. ( "消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  115. buffer, errno, strerror(errno));
  116. else
  117. printf( "消息'%s'发送成功,共发送了%d个字节!\n",
  118. buffer, len);
  119. finish:
  120. / 关闭连接 */
  121. SSL_shutdown(ssl);
  122. SSL_free(ssl);
  123. close(sockfd);
  124. SSL_CTX_free(ctx);
  125. return 0;
  126. }

编译程序用下列命令:
gcc -Wall ssl-client.c -o client -L /usr/local/openssl/lib/ -Wl,-R /usr/local/openssl/lib/ -lssl -lcrypto
gcc -Wall ssl-server.c -o server -L /usr/local/openssl/lib/ -Wl,-R /usr/local/openssl/lib/ -lssl -lcrypto
运行程序用如下命令:
./server 7838 1 127.0.0.1 cacert.pem privkey.pem
./client 127.0.0.1 7838

用下面这两个命令产生上述cacert.pem和privkey.pem文件:
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

http://zhoulifa.bokee.com/6134045.html

http://blog.sina.com.cn/s/blog_86ca13bb0100vaph.html

http://blog.chinaunix.net/uid-26575352-id-3048856.html

一、简介:

SSL(SecureSocket Layer)是netscape公司提出的主要用于web的安全通信标准,分为2.0版和3.0版.TLS(TransportLayer Security)是IETF的TLS工作组在SSL3.0基础之上提出的安全通信标准,目前版本是1.0,即RFC2246.SSL/TLS提供的安全机制可以保证应用层数据在互联网络传输不 被监听,伪造和窜改.

openssl(www.openssl.org)是sslv2,sslv3,tlsv1的一份完整实现,内部包含了大量加密算法程序.其命令行提供了丰富的加密,验证,证书生成等功能,甚至可以用其建立一个完整的CA.与其同时,它也提供了一套完整的库函数,可用开发用SSL/TLS的通信程序.Apache的https两种版本mod_ssl和apachessl均基于它实现的.openssl继承于ssleay,并做了一定的扩展,当前的版本是0.9.5a.

openssl的缺点是文档太少,连一份完整的函数说明都没有,man page也至今没做完整:-(,如果想用它编程序,除了熟悉已有的文档(包括ssleay,mod_ssl,apachessl的文档)外,可以到它的maillist上找相关的帖子,许多问题可以在以前的文章中找到答案.

基于OpenSSL的程序都要遵循以下几个步骤:

(1 ) OpenSSL初始化

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

int SSL_library_init(void);

(2 ) 选择会话协议

在利用OpenSSL开始SSL会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0、SSLv2、SSLv3、SSLv2/v3。

需要注意的是,客户端和服务器必须使用相互兼容的协议,否则SSL会话将无法正常进行。

(3 ) 创建会话环境

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

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

SSL_CTX *SSL_CTX_new(SSL_METHOD * method);

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

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

int SSL_CTX_set_verify(SSL_CTX *ctx,intmode,int(*verify_callback),int(X509_STORE_CTX *));

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

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

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

SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,inttype);

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

SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,inttype);

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

int SSL_CTX_check_private_key(SSL_CTX *ctx);

(4) 建立SSL套接字

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

SSL *SSl_new(SSL_CTX *ctx);    //申请一个SSL套接字

int SSL_set_fd(SSL *ssl,int fd);    //绑定读写套接字

int SSL_set_rfd(SSL *ssl,int fd);    //绑定只读套接字

int SSL_set_wfd(SSL *ssl,int fd);    //绑定只写套接字

(5) 完成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);

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

(6) 进行数据传输

当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);

(7 ) 结束SSL通信

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

int SSL_shutdown(SSL *ssl);    //关闭SSL套接字

void SSl_free(SSL *ssl);   
//释放SSL套接字

 
void SSL_CTX_free(SSL_CTX *ctx);     //释放SSL会话环境


客户端与服务端编程框架:

程序分为两部分,客户端和服务器端,我们的目的是利用SSL/TLS的特性保证通信双方能够互相验证对方身份(真实性),并保证数据的完整性, 私密性.


1.客户端程序的框架为:

/*生成一个SSL结构*/
meth = SSLv23_client_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);

/*下面是正常的socket过程*/
fd = socket();
connect();

/*把建立好的socket和SSL结构联系起来*/
SSL_set_fd(ssl,fd);

/*SSL的握手过程*/
SSL_connect(ssl);

/*接下来用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_write(ssl,”Hello world”,strlen(“Hello World!”));

2.服务端程序的框架为:
/*生成一个SSL结构*/
meth = SSLv23_server_method();
ctx = SSL_CTX_new (meth);
ssl = SSL_new(ctx);

/*下面是正常的socket过程*/
fd = socket();
bind();
listen();
accept();

/*把建立好的socket和SSL结构联系起来*/
SSL_set_fd(ssl,fd);

/*SSL的握手过程*/
SSL_connect(ssl);

/*接下来用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_read (ssl, buf, sizeof(buf));

根据RFC2246(TLS1.0)整个TLS(SSL)的流程如下:

Client                                                Server

ClientHello           ——–>
                                                ServerHello
                                                 Certificate*
                                          ServerKeyExchange*
                                            CertificateRequest*
                           <——–      ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished               ——–>
                                             [ChangeCipherSpec]
                            <——–             Finished
Application Data   <——->     Application Data

对程序来说,openssl将整个握手过程用一对函数体现,即客户端的SSL_connect和服务端的SSL_accept.而后的应用层数据交换则用SSL_read和 SSL_write来完成.

二、证书文件生成

除将程序编译成功外,还需生成必要的证书和私钥文件使双方能够成功验证对方,步骤如下:

1.首先要生成服务器端的私钥(key文件):
openssl genrsa -des3 -out server.key 1024
运行时会提示输入密码,此密码用于加密key文件(参数des3便是指加密算法,当然也可以选用其他你认为安全的算法.),以后每当需读取此文 件(通过openssl提供的命令或API)都需输入口令.如果觉得不方便,也可以去除这个口令,但一定要采取其他的保护措施!
去除key文件口令的命令:
openssl rsa -in server.key -out server.key

2.openssl req -new -key server.key -out server.csr
生成Certificate Signing Request(CSR),生成的csr文件交给CA签名后形成服务端自己的证书.屏幕上将有提示,依照其指示一步一步输入要 求的个人信息即可.

3.对客户端也作同样的命令生成key及csr文件:
openssl genrsa -des3 -out client.key 1024
openssl req -new -key client.key -out client.csr

4.CSR文件必须有CA的签名才可形成证书.可将此文件发送到verisign等地方由它验证,要交一大笔钱,何不自己做CA呢.
首先生成CA的key文件:
openssl -des3 -out ca.key 1024
在生成CA自签名的证书:
openssl req -new -x509 -key ca.key -out ca.crt
如果想让此证书有个期限,如一年,则加上”-days 365”.
(“如果非要为这个证书加上一个期限,我情愿是..一万年”)

5.用生成的CA的证书为刚才生成的server.csr,client.csr文件签名:
可以用openssl中CA系列命令,但不是很好用(也不是多难,唉,一言难尽),一篇文章中推荐用mod_ssl中的sign.sh脚本,试了一下,确实方便了不 少,如果ca.csr存在的话,只需:
./sigh.sh server.csr
./sign.sh client.csr
相应的证书便生成了(后缀.crt).

现在我们所需的全部文件便生成了.

其实openssl中还附带了一个叫CA.pl的文件(在安装目录中的misc子目录下),可用其生成以上的文件,使用也比较方便,但此处就不作介绍了.

三、需要了解的一些函数:

1.int    SSL_CTX_set_cipher_list(SSL_CTX *,const char *str);

SSL_CTX_set_cipher_list() sets the list of available ciphers for ctx using the control string str. The format of the string is described in ciphers. The list of ciphers is inherited by all ssl objects created from ctx.
根据SSL/TLS规范,在ClientHello中,客户端会提交一份自己能够支持的加密方法的列表,由服务端选择一种方法后在ServerHello中通知客户端, 从而完成加密算法的协商.

如果服务端只设置了一种加密套件,那么客户端要么接受要么返回错误。加密套件的选择是由服务端做出的

可用的算法为:
EDH-RSA-DES-CBC3-SHA
EDH-DSS-DES-CBC3-SHA
DES-CBC3-SHA
DHE-DSS-RC4-SHA
IDEA-CBC-SHA
RC4-SHA
RC4-MD5
EXP1024-DHE-DSS-RC4-SHA
EXP1024-RC4-SHA
EXP1024-DHE-DSS-DES-CBC-SHA
EXP1024-DES-CBC-SHA
EXP1024-RC2-CBC-MD5
EXP1024-RC4-MD5
EDH-RSA-DES-CBC-SHA
EDH-DSS-DES-CBC-SHA
DES-CBC-SHA
EXP-EDH-RSA-DES-CBC-SHA
EXP-EDH-DSS-DES-CBC-SHA
EXP-DES-CBC-SHA
EXP-RC2-CBC-MD5
EXP-RC4-MD5


下面的openssl命令可以列出支持的所有算法,并按强度排序:

# openssl ciphers -v ‘ALL:!ADH:@STRENGTH’
DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
DHE-DSS-AES256-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(256)  Mac=SHA1
AES256-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA1
EDH-RSA-DES-CBC3-SHA    SSLv3 Kx=DH       Au=RSA  Enc=3DES(168) Mac=SHA1
EDH-DSS-DES-CBC3-SHA    SSLv3 Kx=DH       Au=DSS  Enc=3DES(168) Mac=SHA1
DES-CBC3-SHA            SSLv3 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=SHA1
DES-CBC3-MD5            SSLv2 Kx=RSA      Au=RSA  Enc=3DES(168) Mac=MD5
DHE-RSA-AES128-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-DSS-AES128-SHA      SSLv3 Kx=DH       Au=DSS  Enc=AES(128)  Mac=SHA1
AES128-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-DSS-RC4-SHA         SSLv3 Kx=DH       Au=DSS  Enc=RC4(128)  Mac=SHA1
RC4-SHA                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=SHA1
RC4-MD5                 SSLv3 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=MD5
RC2-CBC-MD5             SSLv2 Kx=RSA      Au=RSA  Enc=RC2(128)  Mac=MD5
RC4-MD5                 SSLv2 Kx=RSA      Au=RSA  Enc=RC4(128)  Mac=MD5
RC4-64-MD5              SSLv2 Kx=RSA      Au=RSA  Enc=RC4(64)   Mac=MD5
EXP1024-DHE-DSS-RC4-SHA SSLv3 Kx=DH(1024) Au=DSS  Enc=RC4(56)   Mac=SHA1 export
EXP1024-RC4-SHA         SSLv3 Kx=RSA(1024) Au=RSA  Enc=RC4(56)   Mac=SHA1 export
EXP1024-DHE-DSS-DES-CBC-SHA SSLv3 Kx=DH(1024) Au=DSS  Enc=DES(56)   Mac=SHA1 export
EXP1024-DES-CBC-SHA     SSLv3 Kx=RSA(1024) Au=RSA  Enc=DES(56)   Mac=SHA1 export
EXP1024-RC2-CBC-MD5     SSLv3 Kx=RSA(1024) Au=RSA  Enc=RC2(56)   Mac=MD5  export
EXP1024-RC4-MD5         SSLv3 Kx=RSA(1024) Au=RSA  Enc=RC4(56)   Mac=MD5  export
EDH-RSA-DES-CBC-SHA     SSLv3 Kx=DH       Au=RSA  Enc=DES(56)   Mac=SHA1
EDH-DSS-DES-CBC-SHA     SSLv3 Kx=DH       Au=DSS  Enc=DES(56)   Mac=SHA1
DES-CBC-SHA             SSLv3 Kx=RSA      Au=RSA  Enc=DES(56)   Mac=SHA1
DES-CBC-MD5             SSLv2 Kx=RSA      Au=RSA  Enc=DES(56)   Mac=MD5
EXP-EDH-RSA-DES-CBC-SHA SSLv3 Kx=DH(512)  Au=RSA  Enc=DES(40)   Mac=SHA1 export
EXP-EDH-DSS-DES-CBC-SHA SSLv3 Kx=DH(512)  Au=DSS  Enc=DES(40)   Mac=SHA1 export
EXP-DES-CBC-SHA         SSLv3 Kx=RSA(512) Au=RSA  Enc=DES(40)   Mac=SHA1 export
EXP-RC2-CBC-MD5         SSLv3 Kx=RSA(512) Au=RSA  Enc=RC2(40)   Mac=MD5  export
EXP-RC4-MD5             SSLv3 Kx=RSA(512) Au=RSA  Enc=RC4(40)   Mac=MD5  export
EXP-RC2-CBC-MD5         SSLv2 Kx=RSA(512) Au=RSA  Enc=RC2(40)   Mac=MD5  export
EXP-RC4-MD5             SSLv2 Kx=RSA(512) Au=RSA  Enc=RC4(40)   Mac=MD5  export


这些算法按一定优先级排列,如果不作任何指定,将选用DES-CBC3-SHA.用SSL_CTX_set_cipher_list可以指定自己希望用的算法(实际上只是 提高其优先级,是否能使用还要看对方是否支持).

我们在程序中选用了RC4做加密,MD5做消息摘要(先进行MD5运算,后进行RC4加密).即
SSL_CTX_set_cipher_list(ctx,”RC4-MD5”);

在消息传输过程中采用对称加密(比公钥加密在速度上有极大的提高),其所用秘钥(shared secret)在握手过程中中协商(每次对话过程均不同, 在一次对话中都有可能有几次改变),并通过公钥加密的手段由客户端提交服务端.


SSL_set_cipher_list

int SSL_set_cipher_list(SSL *ssl, const char *str);

SSL_set_cipher_list() sets the list of ciphers only for ssl.

RETURN VALUES

SSL_CTX_set_cipher_list() and SSL_set_cipher_list() return 1 if any cipher could be selected and 0 on complete failure.


2.void SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int (*callback)(int, X509_STORE_CTX *));
缺省mode是SSL_VERIFY_NONE,如果想要验证对方的话,便要将此项变成SSL_VERIFY_PEER.SSL/TLS中缺省只验证server,如果没有设置 SSL_VERIFY_PEER的话,客户端连证书都不会发过来.

3.int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,const char *CApath);
要验证对方的话,当然装要有CA的证书了,此函数用来便是加载CA的证书文件的.

4.int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
加载自己的证书文件.

5.int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
加载自己的私钥,以用于签名.

6.int SSL_CTX_check_private_key(SSL_CTX *ctx);
调用了以上两个函数后,自己检验一下证书与私钥是否配对.
7.void RAND_seed(const void *buf,int num);
在win32的环境中client程序运行时出错(SSL_connect返回-1)的一个主要机制便是与UNIX平台下的随机数生成机制不同(握手的时候用的到).具体描述可见mod_ssl的FAQ.解决办法就是调用此函数,其中buf应该为一随机的字符串,作为”seed”.
还可以采用一下两个函数:
void RAND_screen(void);
int RAND_event(UINT, WPARAM, LPARAM);
其中RAND_screen()以屏幕内容作为”seed”产生随机数,RAND_event可以捕获windows中的事件(event),以此为基础产生随机数.如果一直有 用户干预的话,用这种办法产生的随机数能够”更加随机”,但如果机器一直没人理(如总停在登录画面),则每次都将产生同样的数字.

这几个函数都只在WIN32环境下编译时有用,各种UNIX下就不必调了.
大量其他的相关函数原型,见crypto\rand\rand.h.

8.OpenSSL_add_ssl_algorithms()或SSLeay_add_ssl_algorithms()
其实都是调用int SSL_library_init(void)
进行一些必要的初始化工作,用openssl编写SSL/TLS程序的话第一句便应是它.

9.void    SSL_load_error_strings(void );
如果想打印出一些方便阅读的调试信息的话,便要在一开始调用此函数.

10.void ERR_print_errors_fp(FILE *fp);
如果调用了SSL_load_error_strings()后,便可以随时用ERR_print_errors_fp()来打印错误信息了.

11.X509 *SSL_get_peer_certificate(SSL *s);
握手完成后,便可以用此函数从SSL结构中提取出对方的证书(此时证书得到且已经验证过了)整理成X509结构.

12.X509_NAME *X509_get_subject_name(X509 *a);
得到证书所有者的名字,参数可用通过SSL_get_peer_certificate()得到的X509对象.

13.X509_NAME *X509_get_issuer_name(X509 *a)
得到证书签署者(往往是CA)的名字,参数可用通过SSL_get_peer_certificate()得到的X509对象.

14.char *X509_NAME_oneline(X509_NAME *a,char *buf,int size);
将以上两个函数得到的对象变成字符型,以便打印出来.

15.SSL_METHOD的构造函数,包括
SSL_METHOD *TLSv1_server_method(void);    /* TLSv1.0 */
SSL_METHOD *TLSv1_client_method(void);    /* TLSv1.0 */

SSL_METHOD *SSLv2_server_method(void);    /* SSLv2 */
SSL_METHOD *SSLv2_client_method(void);    /* SSLv2 */

SSL_METHOD *SSLv3_server_method(void);    /* SSLv3 */
SSL_METHOD *SSLv3_client_method(void);    /* SSLv3 */

SSL_METHOD *SSLv23_server_method(void);    /* SSLv3 but can rollback to v2 */
SSL_METHOD *SSLv23_client_method(void);    /* SSLv3 but can rollback to v2 */
在程序中究竟采用哪一种协议(TLSv1/SSLv2/SSLv3),就看调哪一组构造函数了.



四:程序源代码:


服务器端:


  
  
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/wait.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>
  11. #include <openssl/ssl.h>
  12. #include <openssl/err.h>
  13. #define MAXBUF 1024
  14. /************关于本文档********************************************
  15. *filename: ssl-server.c
  16. *purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是服务器端例子
  17. *wrote by: zhoulifa([email protected]) 周立发(http://zhoulifa.bokee.com)
  18. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  19. *date time:2007-02-02 19:40
  20. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  21. * 但请遵循GPL
  22. *Thanks to:Google
  23. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  24. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  25. *********************************************************************/
  26. int main(int argc, char **argv)
  27. {
  28. int sockfd, new_fd;
  29. socklen_t len;
  30. struct sockaddr_in my_addr, their_addr;
  31. unsigned int myport, lisnum;
  32. char buf[MAXBUF + 1];
  33. SSL_CTX *ctx;
  34. if (argv[ 1])
  35. myport = atoi(argv[ 1]);
  36. else
  37. myport = 7838;
  38. if (argv[ 2])
  39. lisnum = atoi(argv[ 2]);
  40. else
  41. lisnum = 2;
  42. /* SSL 库初始化 */
  43. SSL_library_init();
  44. /* 载入所有 SSL 算法 */
  45. OpenSSL_add_all_algorithms();
  46. /* 载入所有 SSL 错误消息 */
  47. SSL_load_error_strings();
  48. /* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
  49. ctx = SSL_CTX_new(SSLv23_server_method());
  50. /* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
  51. if (ctx == NULL) {
  52. ERR_print_errors_fp( stdout);
  53. exit( 1);
  54. }
  55. /* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
  56. if (SSL_CTX_use_certificate_file(ctx, argv[ 4], SSL_FILETYPE_PEM) <= 0) {
  57. ERR_print_errors_fp( stdout);
  58. exit( 1);
  59. }
  60. /* 载入用户私钥 */
  61. if (SSL_CTX_use_PrivateKey_file(ctx, argv[ 5], SSL_FILETYPE_PEM) <= 0) {
  62. ERR_print_errors_fp( stdout);
  63. exit( 1);
  64. }
  65. /* 检查用户私钥是否正确 */
  66. if (!SSL_CTX_check_private_key(ctx)) {
  67. ERR_print_errors_fp( stdout);
  68. exit( 1);
  69. }
  70. /* 开启一个 socket 监听 */
  71. if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
  72. perror( "socket");
  73. exit( 1);
  74. } else
  75. printf( "socket created\n");
  76. bzero(&my_addr, sizeof(my_addr));
  77. my_addr.sin_family = PF_INET;
  78. my_addr.sin_port = htons(myport);
  79. if (argv[ 3])
  80. my_addr.sin_addr.s_addr = inet_addr(argv[ 3]);
  81. else
  82. my_addr.sin_addr.s_addr = INADDR_ANY;
  83. if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
  84. == -1) {
  85. perror( "bind");
  86. exit( 1);
  87. } else
  88. printf( "binded\n");
  89. if (listen(sockfd, lisnum) == -1) {
  90. perror( "listen");
  91. exit( 1);
  92. } else
  93. printf( "begin listen\n");
  94. while ( 1) {
  95. SSL *ssl;
  96. len = sizeof(struct sockaddr);
  97. /* 等待客户端连上来 */
  98. if ((new_fd =
  99. accept(sockfd, (struct sockaddr *) &their_addr,
  100. &len)) == -1) {
  101. perror( "accept");
  102. exit(errno);
  103. } else
  104. printf( "server: got connection from %s, port %d, socket %d\n",
  105. inet_ntoa(their_addr.sin_addr),
  106. ntohs(their_addr.sin_port), new_fd);
  107. /* 基于 ctx 产生一个新的 SSL */
  108. ssl = SSL_new(ctx);
  109. /* 将连接用户的 socket 加入到 SSL */
  110. SSL_set_fd(ssl, new_fd);
  111. /* 建立 SSL 连接 */
  112. if (SSL_accept(ssl) == -1) {
  113. perror( "accept");
  114. close(new_fd);
  115. break;
  116. }
  117. /* 开始处理每个新连接上的数据收发 */
  118. bzero(buf, MAXBUF + 1);
  119. strcpy(buf, "server->client");
  120. /* 发消息给客户端 */
  121. len = SSL_write(ssl, buf, strlen(buf));
  122. if (len <= 0) {
  123. printf
  124. ( "消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  125. buf, errno, strerror(errno));
  126. goto finish;
  127. } else
  128. printf( "消息'%s'发送成功,共发送了%d个字节!\n",
  129. buf, len);
  130. bzero(buf, MAXBUF + 1);
  131. /* 接收客户端的消息 */
  132. len = SSL_read(ssl, buf, MAXBUF);
  133. if (len > 0)
  134. printf( "接收消息成功:'%s',共%d个字节的数据\n",
  135. buf, len);
  136. else
  137. printf
  138. ( "消息接收失败!错误代码是%d,错误信息是'%s'\n",
  139. errno, strerror(errno));
  140. /* 处理每个新连接上的数据收发结束 */
  141. finish:
  142. /* 关闭 SSL 连接 */
  143. SSL_shutdown(ssl);
  144. /* 释放 SSL */
  145. SSL_free(ssl);
  146. /* 关闭 socket */
  147. close(new_fd);
  148. }
  149. /* 关闭监听的 socket */
  150. close(sockfd);
  151. /* 释放 CTX */
  152. SSL_CTX_free(ctx);
  153. return 0;
  154. }

客户端:


  
  
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <sys/socket.h>
  5. #include <resolv.h>
  6. #include <stdlib.h>
  7. #include <netinet/in.h>
  8. #include <arpa/inet.h>
  9. #include <unistd.h>
  10. #include <openssl/ssl.h>
  11. #include <openssl/err.h>
  12. #define MAXBUF 1024
  13. void ShowCerts(SSL * ssl)
  14. {
  15. X509 cert;
  16. char *line;
  17. cert = SSL_get_peer_certificate(ssl);
  18. if (cert != NULL) {
  19. printf( "数字证书信息:\n");
  20. line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
  21. printf( "证书: %s\n", line);
  22. free(line);
  23. line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
  24. printf( "颁发者: %s\n", line);
  25. free(line);
  26. X509_free(cert);
  27. } else
  28. printf( "无证书信息!\n");
  29. }
  30. /***关于本文档**********************************
  31. filename: ssl-client.c
  32. *purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是客户端例子
  33. *wrote by: zhoulifa([email protected]) 周立发(http://zhoulifa.bokee.com)
  34. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  35. *date time:2007-02-02 20:10
  36. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  37. 但请遵循GPL
  38. Thanks to:Google
  39. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  40. 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  41. ***********************************************************/
  42. int main(int argc, char *argv)
  43. {
  44. int sockfd, len;
  45. struct sockaddr_in dest;
  46. char buffer[MAXBUF + 1];
  47. SSL_CTX *ctx;
  48. SSL *ssl;
  49. if (argc != 3) {
  50. printf
  51. ( "参数格式错误!正确用法如下:\n\t\t%s IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
  52. argv[ 0], argv[ 0]);
  53. exit( 0);
  54. }
  55. / SSL 库初始化,参看 ssl-server.c 代码 /
  56. SSL_library_init();
  57. OpenSSL_add_all_algorithms();
  58. SSL_load_error_strings();
  59. ctx = SSL_CTX_new(SSLv23_client_method());
  60. if (ctx == NULL) {
  61. ERR_print_errors_fp( stdout);
  62. exit( 1);
  63. }
  64. / 创建一个 socket 用于 tcp 通信 /
  65. if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  66. perror( "Socket");
  67. exit(errno);
  68. }
  69. printf( "socket created\n");
  70. / 初始化服务器端(对方)的地址和端口信息 /
  71. bzero(&dest, sizeof(dest));
  72. dest.sin_family = AF_INET;
  73. dest.sin_port = htons(atoi(argv[ 2]));
  74. if (inet_aton(argv[ 1], (struct in_addr ) &dest.sin_addr.s_addr) == 0) {
  75. perror(argv[ 1]);
  76. exit(errno);
  77. }
  78. printf( "address created\n");
  79. /* 连接服务器 /
  80. if (connect(sockfd, (struct sockaddr ) &dest, sizeof(dest)) != 0) {
  81. perror( "Connect ");
  82. exit(errno);
  83. }
  84. printf( "server connected\n");
  85. /* 基于 ctx 产生一个新的 SSL /
  86. ssl = SSL_new(ctx);
  87. SSL_set_fd(ssl, sockfd);
  88. / 建立 SSL 连接 /
  89. if (SSL_connect(ssl) == -1)
  90. ERR_print_errors_fp( stderr);
  91. else {
  92. printf( "Connected with %s encryption\n", SSL_get_cipher(ssl));
  93. ShowCerts(ssl);
  94. }
  95. / 接收对方发过来的消息,最多接收 MAXBUF 个字节 /
  96. bzero(buffer, MAXBUF + 1);
  97. / 接收服务器来的消息 /
  98. len = SSL_read(ssl, buffer, MAXBUF);
  99. if (len > 0)
  100. printf( "接收消息成功:'%s',共%d个字节的数据\n",
  101. buffer, len);
  102. else {
  103. printf
  104. ( "消息接收失败!错误代码是%d,错误信息是'%s'\n",
  105. errno, strerror(errno));
  106. goto finish;
  107. }
  108. bzero(buffer, MAXBUF + 1);
  109. strcpy(buffer, "from client->server");
  110. / 发消息给服务器 /
  111. len = SSL_write(ssl, buffer, strlen(buffer));
  112. if (len < 0)
  113. printf
  114. ( "消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
  115. buffer, errno, strerror(errno));
  116. else
  117. printf( "消息'%s'发送成功,共发送了%d个字节!\n",
  118. buffer, len);
  119. finish:
  120. / 关闭连接 */
  121. SSL_shutdown(ssl);
  122. SSL_free(ssl);
  123. close(sockfd);
  124. SSL_CTX_free(ctx);
  125. return 0;
  126. }

编译程序用下列命令:
gcc -Wall ssl-client.c -o client -L /usr/local/openssl/lib/ -Wl,-R /usr/local/openssl/lib/ -lssl -lcrypto
gcc -Wall ssl-server.c -o server -L /usr/local/openssl/lib/ -Wl,-R /usr/local/openssl/lib/ -lssl -lcrypto
运行程序用如下命令:
./server 7838 1 127.0.0.1 cacert.pem privkey.pem
./client 127.0.0.1 7838

用下面这两个命令产生上述cacert.pem和privkey.pem文件:
openssl genrsa -out privkey.pem 2048
openssl req -new -x509 -key privkey.pem -out cacert.pem -days 1095

猜你喜欢

转载自blog.csdn.net/cg129054036/article/details/82388955