Chinaunix首页 | 论坛 | 博客
  • 博客访问: 407629
  • 博文数量: 101
  • 博客积分: 2207
  • 博客等级: 大尉
  • 技术积分: 2508
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-19 20:45
文章分类

全部博文(101)

文章存档

2013年(15)

2012年(86)

我的朋友

分类: C/C++

2012-07-25 11:24:22

最近由于在做苹果的一个Push,需要用到SSL的库,决定选择网上比较热炒的Openssl,在此之前,也听说了这个库比较难处理,因为资料并不是很完整,所以开发会有困难,不过本着挑战一下,既然有人说好,那么就实际检验一下吧。在查了很多资料后,写了一个openssl的上层封装类,给大家拍砖。
首先到openssl去下载一个最新的openssl。地址是
这里要说明一下,这时候最新的是1.0.0版本的。不过因为1.0.0支持VS2008,而本人的开发环境是VS2005,所以我使用了较为早一些的版本,0.9.8b。
linux下的安装很简单,按照install文件里面一步步来就可以了。
windows下比较复杂,需要下载一个工具。
安装步骤如下:
1.下载ActivePerl
2.打开Vs2005命令行提示工具。
3.进入你的openssl解压目录,并之行perl Configure VC-WIN32
4.运行ms\do_ms进行编译
5.运行vcvars32.bat
6.在openssl目录下运行“nmake -f ms\ntdll.mak ”
如果一切顺利,会在你的openssl解压目录下多了一个out*dll的目录(如果是32位系统,会是out32dll,不过在我的64位win7下生成的也是out32dll,不过根据官方文档说可以生成64位的)
行了,openssl已经可以使用了。
建立一个新的工程,你可以把你的库目录指定到out32dll,把的附加包含目录指定到include目录下,不过我个人建议把include目录考到你的工程路径下,然后建立一个lib目录,把OpenSSL下生成的libeay32.lib和ssleay32.lib考进去就行了,建立一个bin目录,把工程的工作路径指定在这里,并把libeay32.dll和ssleay32.dll拷贝进去,这样你的程序就可以随时随地带着走了。不需要到处编译OpenSSL。
呵呵,好了,所有的材料都齐备了。
看看怎么写一个支持SSL通讯的客户端程序吧。网上的一些OpenSSL写的比较乱,其实OpenSSL和CUrl一样,会用的话,非常好使,而且代码简单,完全不用带着恐惧的心情。(一开始总结网上那些OpenSSL可借鉴的时候,看着长篇累牍的代码确实头晕,但是后来一点点的分析,发现大部分写的都是没用的。)
其实OpenSSL并不是只能连接SSL连接,其实也可以连接非SSL的网址。并获得网站返回数据。
要用OpenSSL,需要像CUrl一样,初始化一些动作。

点击(此处)折叠或打开

  1. OpenSSL_add_all_algorithms();
  2. ERR_load_BIO_strings();
  3. SSL_load_error_strings();
  4. SSL_library_init();
这是固定的代码,加载一些必要的库和函数,没啥好解释的。
SSL通讯需要几个关键对象,在这里解释一下。
SSL*      m_pSSL;
SSL的对象指针,主要用于记录一些SSL中用到的和对象,以及最关键的SSL_CTX。
SSL_CTX*  m_pSSLCtx;
SSL_CTX实际上就是一个SSL的上下文。这里可以记录和设置你的SSL一些状态,比如你采用的SSL协议格式(SSL2,SSL3或者TLS)
BIO*      m_pSockBIO;
BIO我的理解是就是一个标准的Socket对象封装,之所以要用它把socket包装起来,因为加解密算法在里面完成,并不暴露在外面,极大的减轻了开发人员的开发量。
先从简单的来,先看看OpenSSL如何建立一个非安全的套接字。

点击(此处)折叠或打开

  1. //创建一个非安全连接
  2. bool CSSLConnect::ConnectNonSSL(const char* pUrl, int nPort, const char* pCmd)
  3. {
  4.  char szURL[SSL_BUFF_200] = {'\0'};
  5.  char szData[SSL_BUFF_1024] = {'\0'};
  6.  sprint_safe(szURL, SSL_BUFF_200, "%s:%d", pUrl, nPort);
  7.  Close();
  8.  m_pSockBIO = BIO_new_connect(szURL);
  9.  if(m_pSockBIO == NULL)
  10.  {
  11.   ERR_error_string(ERR_get_error(), (char* )m_szError);
  12.   printf("[CSSLConnect::ConnectNonSSL] BIO_new_connect ERROR: %s.\n", m_szError);
  13.   return false;
  14.  }
  15.  if(BIO_do_connect(m_pSockBIO) <= 0)
  16.  {
  17.   ERR_error_string(ERR_get_error(), (char* )m_szError);
  18.   printf("[CSSLConnect::ConnectNonSSL] BIO_do_connect ERROR: %s.\n", m_szError);
  19.   return false;
  20.  }
  21.  //链接建立后,发送命令,可以是http命令,如果是http命令,最好解析第一个包获得数据长度。
  22.  int nPos = 0;
  23.  int nCmdLen = (int)strlen(pCmd);
  24.  while(true)
  25.  {
  26.   if(nPos == nCmdLen || nCmdLen == 0)
  27.   {
  28.    break;
  29.   }
  30.   int nLen = BIO_write(m_pSockBIO, &pCmd[nPos], nCmdLen);
  31.   if(nLen <= 0)
  32.   {
  33.    break;
  34.   }
  35.   else
  36.   {
  37.    nPos += nLen;
  38.    nCmdLen -= nPos;
  39.   }
  40.  }
  41.  m_strBuff = "";
  42.  while(true)
  43.  {
  44.   int nLen = BIO_read(m_pSockBIO, szData, SSL_BUFF_1024);
  45.   if(nLen <= 0)
  46.   {
  47.    break;
  48.   }
  49.   else
  50.   {
  51.    m_strBuff += szData;
  52.   }
  53.  }
  54.  return true;
  55. }
pUrl nPort是对应的你的URL和Port,比如你的URL是"",端口是80。pCmd是你发送的命令,比如给它一个标准的http头。
"GET / HTTP/1.1\r\nHost: : Close\r\n\r\n"
在这里,m_pSockBIO完全就是一个socket的用法。
你可以发送数据,并获得返回,对应的BIO_write()就是send,BIO_read对应的是Recv。
这里可以有一个小技巧,你可以在recv的时候,回调一个指定的函数指针,呵呵,想到这里,如果你看过CUrl的话,你就会恍然大悟。其实CURL完全可以用openssl做一个底层,用法也就是上述我说的方法。当然在这里,我简化了,只持续的接受数据。
好了,来点正题吧。
如果我要连接一个https网站怎么办,这里要区分一下,有的htpps需要一个自己的证书,有的需要一个默认的根证书。
先说简单的,比如我访问google的https怎么处理?
来看看以下代码:

点击(此处)折叠或打开

  1. //创建一个安全连接,默认的安全证书
  2. bool CSSLConnect::ConnectSSL(const char* pUrl, int nPort, const char* pCmd)
  3. {
  4. char szURL[SSL_BUFF_200] = {'\0'};

  5. sprint_safe(szURL, SSL_BUFF_200, "%s:%d", pUrl, nPort);

  6. Close();

  7. m_pSSLCtx = SSL_CTX_new(SSLv23_client_method());

  8. m_pSockBIO = BIO_new_ssl_connect(m_pSSLCtx);

  9. BIO_get_ssl(m_pSockBIO, &m_pSSL);

  10. SSL_set_mode(m_pSSL, SSL_MODE_AUTO_RETRY);

  11. BIO_set_conn_hostname(m_pSockBIO, szURL);

  12. if(BIO_do_connect(m_pSockBIO) <= 0)
  13. {
  14. ERR_error_string(ERR_get_error(), (char* )m_szError);
  15. printf("[CSSLConnect::Connect] SSL_CTX_use_certificate ERROR: %s.\n", m_szError);
  16. return false;
  17. }

  18. int nPos = 0;
  19. int nCmdLen = (int)strlen(pCmd);

  20. while(true)
  21. {
  22. if(nPos == nCmdLen || nCmdLen == 0)
  23. {
  24. break;
  25. }

  26. int nLen = BIO_write(m_pSockBIO, &pCmd[nPos], nCmdLen);
  27. if(nLen <= 0)
  28. {
  29. break;
  30. }
  31. else
  32. {
  33. nPos += nLen;
  34. nCmdLen -= nPos;
  35. }
  36. }

  37. char szData[SSL_BUFF_1024] = {'\0'};
  38. m_strBuff = "";
  39. while(true)
  40. {
  41. int nLen = BIO_read(m_pSockBIO, szData, SSL_BUFF_1024);
  42. if(nLen <= 0)
  43. {
  44. break;
  45. }
  46. else
  47. {
  48. m_strBuff += szData;
  49. }
  50. }

  51. return true;
  52. }
代码就这么多。是不是很简单?
你完全可以对比上面的代码,你会发现其实只是多了几句话而已。
m_pSSLCtx = SSL_CTX_new(SSLv23_client_method());
这句话是生成一个SSL上下文,这里支持SSLv2_client_method(),SSLv3_client_method(),TLSv1_client_method()。这里应对的是SSL的协议标准,SSL2,SSL3亦或TLS1。具体可以根据自己的需要进行替换。
m_pSockBIO = BIO_new_ssl_connect(m_pSSLCtx);
生成一个BIO*对象,并绑定SSLCtx的上下文。
BIO_get_ssl(m_pSockBIO, &m_pSSL);
根据这个BIO对象返回一个SSL对象。
SSL_set_mode(m_pSSL, SSL_MODE_AUTO_RETRY);
设置SSL对象需要自动重试请求。
if(BIO_do_connect(m_pSockBIO) <= 0)
这个代码是,根据已有的SSL,向远程SSL提交请求。进行一次握手,如果对方服务器拒绝,会在这里返回错误。抓住这里的错误,你可以进行分析。
此后的代码,和非安全套接字一样。
BIO_write会在内部将你给出的字符加密,这里不用你担心。
BIO_read会在内部解密服务器的数据返回,这里也不用担心,这里得到的都是解密后的字符。
看看,简单吧。
让我们测试一下,这时候拿一个google的测试一下。
测试地址是 端口是443(一般SSL都是这个端口)发送命令么。。就是这个吧"GET / HTTP/1.1\x0D\x0AHost: : Close\x0D\x0A\x0D\x0A"
数据回来了吧。
呵呵,接下来,说一下最复杂的证书加载。
有些网站需要加载证书,证书类型不同,比如P12,pem,DER等等。有的证书还需要key。
写到这里,先写一个处理证书读取的类,在OpenSSL中,只要你把不同的证书加载成一种中间格式(X509)就可以了。
先写一个函数吧。

点击(此处)折叠或打开

  1. X509* CSSLConnect::LoadBaseCret(BIO* pKeyBuff, int nType, const char* pPassword)
  2. {
  3. X509* pX509 = NULL;
  4. EVP_PKEY* pPKey = NULL;
  5. switch(nType)
  6. {
  7. case DER:
  8. {
  9. //如果是DER证书
  10. pX509 = d2i_X509_bio(pKeyBuff, NULL);
  11. break;
  12. }
  13. case PEM:
  14. {
  15. pX509 = PEM_read_bio_X509(pKeyBuff, NULL, NULL, NULL);
  16. break;
  17. }
  18. case P12:
  19. {
  20. PKCS12 *pP12 = d2i_PKCS12_bio(pKeyBuff, NULL);
  21. if(1 != PKCS12_parse(pP12, pPassword, &pPKey, &pX509, NULL))
  22. {
  23. ERR_error_string(ERR_get_error(), (char* )m_szError);
  24. printf("[CSSLConnect::LoadBaseCret] ERROR: %s.\n", m_szError);
  25. }

  26. PKCS12_free(pP12);
  27. break;
  28. }
  29. }
  30. return pX509;
  31. }
这里只写出常用的几种证书的加载,其他的你可以往里添加。
把证书转换成X509格式,然后加载到相应的SSLCtx就大功告成了。
 
//创建一个安全的链接,需要证书
bool CSSLConnect::ConnectSSL(const char* pUrl, int nPort, int nCreType, const char* pCerFile, const char* pPassword, const char* pCmd)
{
 char szURL[SSL_BUFF_200]   = {'\0'};
 sprint_safe(szURL, SSL_BUFF_200, "%s:%d", pUrl, nPort);
 
 Close();
 //初始化SSLCtx
 //m_pSSLCtx = SSL_CTX_new(SSLv23_client_method());
 m_pSSLCtx = SSL_CTX_new(TLSv1_client_method());
 if(m_pSSLCtx == NULL)
 {
  printf("SSL CTX new Fail.\n");
  return false;
 }
 SSL_CTX_set_options(m_pSSLCtx, SSL_OP_ALL);
 //解析证书文件
 BIO* pKeyBuff = BIO_new_file(pCerFile, "r");
 if(NULL == pKeyBuff)
 {
  printf("[CSSLConnect::Connect]Load key file Fail.\n");
  return false;
 }
 X509*     pX509 = NULL;
 EVP_PKEY* pPKey = NULL;
 pX509 = LoadBaseCret(pKeyBuff, nCreType, pPassword);
 if(NULL == pX509)
 {
  printf("[CSSLConnect::Connect]LoadBaseCret error.\n");
  BIO_free_all(pKeyBuff);
  return false;
 }
 //加载证书
 //if(1 != SSL_CTX_use_certificate(m_pSSLCtx, pX509))
 if(1 != SSL_CTX_add_client_CA(m_pSSLCtx, pX509))
 {
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] SSL_CTX_use_certificate ERROR: %s.\n", m_szError);
  return false;
 }
 /*
 //测试代码
 if(! SSL_CTX_load_verify_locations(m_pSSLCtx, "dev.pem", NULL))
 {
  //没有正确的获得pem
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] pSockBIO ERROR: %s.\n", m_szError);
  return false;
 }
 
 if(1 != SSL_CTX_use_certificate_file(m_pSSLCtx, "dev.pem", SSL_FILETYPE_PEM))
 {
  //没有正确的获得pem
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] pSockBIO ERROR: %s.\n", m_szError);
  return false;
 }
 */

 //检查证书是否有效
 m_pSockBIO = BIO_new_ssl_connect(m_pSSLCtx);
 if(m_pSockBIO == NULL)
 {
  //没有正确的获得BIO
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] pSockBIO ERROR: %s.\n", m_szError);
  return false;
 }
 BIO_get_ssl(m_pSockBIO, &m_pSSL);
 if(m_pSSL == NULL)
 {
  //没有正确的获得BIO
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] m_pSSL ERROR: %s.\n", m_szError);
  return false;
 }
 if(SSL_get_verify_result(m_pSSL) != X509_V_OK)
 {
  //X509证书无效
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] SSL_get_verify_result ERROR: %s.\n", m_szError);
  return false;
 }
 SSL_set_mode(m_pSSL, SSL_MODE_AUTO_RETRY);
 
 BIO_set_conn_hostname(m_pSockBIO, szURL);
 int nRet = BIO_do_connect(m_pSockBIO);
 if(nRet <= 0)
 {
  //没有正确连接
  ERR_error_string(ERR_get_error(), (char* )m_szError);
  printf("[CSSLConnect::Connect] pSockBIO Connect ERROR: %s.\n", m_szError);
  return false;
 }
 int nPos    = 0;
 int nCmdLen = (int)strlen(pCmd);
 while(true)
 {
  if(nPos == nCmdLen || nCmdLen == 0)
  {
   break;
  }
  int nLen = BIO_write(m_pSockBIO, &pCmd[nPos], nCmdLen);
  if(nLen <= 0)
  {
   break;
  }
  else
  {
   nPos    += nLen;
   nCmdLen -= nPos;
  }
 }
 char szData[SSL_BUFF_1024] = {'\0'};
 m_strBuff = "";
 while(true)
 {
  int nLen = BIO_read(m_pSockBIO, szData, SSL_BUFF_1024);
  if(nLen <= 0)
  {
   break;
  }
  else
  {
   m_strBuff += szData;
  }
 } 
 BIO_free_all(pKeyBuff);
 X509_free(pX509);
 return true;

这个函数就是加载有证书的SSL连接代码了。
其他部分的代码很简单,关键在这里
if(1 != SSL_CTX_add_client_CA(m_pSSLCtx, pX509))
加载你的证书到m_pSSLCtx就行了。
呵呵,不复杂吧。
好了,代码完成了,拿一个有证书的链接测试一下。这里我拿苹果的Push服务器测试的。
如果你有苹果开发者账号,你可以参考一下。
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html

呵呵,有兴趣的哈,给自己的IPhone Push一条属于自己的消息吧。
在这里抛砖引玉了。
把工程代码都贴上来,大家可以测试。不对的地方多多指正。

阅读(3904) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~