SSL/TLS编程

Posted 火雨_Nick

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SSL/TLS编程相关的知识,希望对你有一定的参考价值。

                                  SSL/TLS编程

  公钥基础设施(PKI作为当今密码学应用最伟大的工程,已经深入到网络世界的各个方面。无论是什么品牌的浏览器,什么类型的Web 服务器,无论是远程调用的代码认证还是电子邮件加密和签名PKI 已经嵌入到几乎全部的网络软件中,成为这些软件不可缺少的安全支撑。
 公钥基础设施,即 PKI Public Key Infrastructure),是指用公开密钥技术来实施和提供安全服务的具有普适性的安全基础设施。任何以公钥技术为基础的安全基础设施都是PKI

   PKI 并不是单一的物理对象或软件过程,而是一种使发布、管理和利用公开密钥都比较容易的系统,其中不仅提供可以使用的安全工具,而且提供系统安全解决方案;PKI 由许多相互联系的组件共同协作来提供一整套服务,这些服务使得用户能够简单方便的使用公钥密码解决系统的安全问题。

   PKI可理解为证书管理的工具,包括创建、存储、分发、撤销公钥证书(PKC)的所有硬件、软件、人、政策法规和操作规程;PKI结合了技术和管理两个方面的因素;PKI 为应用提供可信的证书,因而PKI 可以被认为是信任管理设施

 PKI是在开放的网络上(如 Internet)提供和支持安全电子交易的所有的产品、服务、工具、政策法规、操作规程、协定和人的集合;PKI 不仅提供可信的证书,还包括建立在密码学基础之上的安全服务,如实体鉴别服务、消息保密性服务、消息完整性服务和不可否认性服务等。这些服务需要通过相关的协议实现,如消息保密性服务可以通过SSLTLS 等保密通信协议实现。总而言之,可以认为PKI 是用公钥技术实施的,支持公钥管理,提供真实性、保密性、完整性、可追究性等安全服务,具有普适性的安全基础设施。

   在 X.509标准中,为了能够使得 PKI 有别于权限管理基础设施(Privilege Management Infrastructure, PMI,将PKI 定义为支持公开密钥管理并能够支持认证、加密、完整性和可追究性服务的基础设施。即:仅仅使用公钥技术还不能叫做PKI,还应该提供公钥管理。PMI仅仅使用公钥技术,但并不管理公钥。根据X.509的定义, PMI+PKI 仍旧可以叫做 PKI,而 PMI完全可以看作PKI的一部分。

1.目的

   (1) 掌握OpenSSL SSL程序开发的原理以及常用的API;

   (2) 基于OpenSSL API,实现一个简单的SSL C/S通信程序。

2.步骤

   (1)  client .cpp:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <winsock.h>
#include <windows.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define CHK_NULL(x)if ((x)==NULL) exit (1)
#define CHK_ERR(err,s)if (( err)==-1)  perror(s); exit (1); 
#define CHK_SSL(err)if (( err)==-1)  ERR_print_errors_fp( stderr ); exit (2); 
void main()

  int err ;
  int sd; // socket 句柄
  struct sockaddr_in sa; //sockaddr_in 结构体
  SSL_CTX* ctx; //SSL上下文句柄
  SSL* ssl; //SSL结构体指针
  X509* server_cert ; //X509 结构体,用户保存服务器端证书
  char* str ;
  char buf[4096];
  const SSL_METHOD *meth; //SSL 协议
  WSADATA wsaData;
  //初始化windows Socket环境
  if (WSAStartup(MAKEWORD(2, 0), &wsaData))
  
   return;
  
  //初始化OpenSSL环境
  SSL_load_error_strings () ;
  SSLeay_add_ssl_algorithms();
  //设置SSL协议版本为V2V3自适应
  meth = SSLv23_client_method();
  ctx = SSL_CTX_new(meth);
  CHK_NULL(ctx);
  //以常规的SOCKET编程的方式创建socket并连接到服务器端
  sd = socket(AF_INET, SOCK_STREAM, 0);
  CHK_ERR(sd, "socket");
  memset(&sa, '\\0 ' ,sizeof(sa));
  sa. sin_family = AF_INET;
  sa.sin_addr.s_addr = inet_addr ("127.0.0.1"); //服务端地址
  sa. sin_port = htons(8443); //服务端口
  //连接服务器
  err = connect(sd, (struct sockaddr*) &sa, sizeof(sa));
  CHK_ERR(err, "connect");
  //使用现有的TCP连接开启SSL协议
  ssl = SSL_new(ctx);
  CHK_NULL(ssl);
  SSL_set_fd(ssl , sd);
  //启动SSL连接
  err = SSL_connect(ssl);
  CHK_SSL(err);
  //打印SSL连接的算法
  printf ("SSL connection using %s\\n", SSL_get_cipher(ssl ));
  //获得服务端证书
  server_cert = SSL_get_peer_certificate ( ssl );
  CHK_NULL(server_cert);
  printf ("Server certificate :\\n");
  //获得服务器证书名称
  str = X509_NAME_oneline(X509_get_subject_name(server_cert),0,0);
  CHK_NULL(str);
  printf ("subject : %s\\n", str );
  OPENSSL_free(str);
  //获得服务器证书颁发者名称
  str = X509_NAME_oneline(X509_get_issuer_name(server_cert),0,0);
  CHK_NULL(str);
  printf (" issuer : %s\\n", str );
  OPENSSL_free(str);
  //释放X509结构体
  X509_free( server_cert );
  //发送消息到服务端
  err = SSL_write(ssl , "Hello World!", strlen ("Hello World!"));
  CHK_SSL(err);
  //读取服务端的消息
  err = SSL_read (ssl , buf, sizeof(buf) - 1);
  CHK_SSL(err);
  buf[err] = ' \\0 ' ;
  printf ("Got %d chars:'%s '\\ n", err , buf);
  SSL_shutdown(ssl); //发送SSL关闭消息
  //释放内存
  closesocket (sd);
  SSL_free(ssl);
  SSL_CTX_free(ctx);

  (2)  server.c:
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <winsock2.h>
#include <windows.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define CERTF "cert .cer" //服务端证书
#define KEYF "key.pem" //服务器端私钥
#define ROOTCERTF "root.cer"//根证书
#define CHK_NULL(x)if ((x)==NULL) exit (1)
#define CHK_ERR(err,s)if (( err)==-1)  perror(s); exit (1); 
#define CHK_SSL(err)if (( err)==-1)  ERR_print_errors_fp( stderr ); exit (2); 

int main()

  int err ;
  int listen_sd ; //监听句柄
  int sd;
  struct sockaddr_in sa_serv; //sockaddr_in 结构体,用于保存服务器端协议和端口信息
  struct sockaddr_in sa_cli ;
  size_t client_len;
  SSL_CTX* ctx;//SSL 上下文句柄
  SSL* ssl; //SSL结构体指针
  X509* client_cert ; //X509 结构体,用户保存客户端证书
  char* str ;
  char buf [4096];
  const SSL_METHOD *meth;//SSL 协议
  WSADATA wsaData;
  //初始化windows Socket环境
  if (WSAStartup(MAKEWORD(2, 0), &wsaData))
  
   exit (1);
  
  //初始化OpenSSL环境
  SSL_load_error_strings () ;
  SSLeay_add_ssl_algorithms();
  //设置SSL协议版本为V2V3自适应
  meth = SSLv23_server_method();
  //新建SSL上下文句柄
  ctx = SSL_CTX_new(meth);
  if (!ctx)
  ERR_print_errors_fp( stderr );
  exit (2);
  
  //设置服务器证书
  if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) 
  ERR_print_errors_fp( stderr );
  exit (3);
  
  //设置服务器私钥
  if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) 
  ERR_print_errors_fp( stderr );
  exit (4);
  
  //检查私钥和证书是否匹配
  if (!SSL_CTX_check_private_key(ctx)) 
  fprintf ( stderr , "Private key does not match the certificate public key.\\n");
  exit (5);
  
  //新建Socket
  listen_sd = socket(AF_INET, SOCK_STREAM, 0);
  CHK_ERR(listen_sd, "socket");
  //初始化 ,设置为sa_serv TCP协议,端口为8443
  memset(&sa_serv, '\\0 ' ,sizeof(sa_serv));
  sa_serv. sin_family = AF_INET;
  sa_serv.sin_addr.s_addr = INADDR_ANY;
  sa_serv. sin_port = htons(8443);
  //绑定端口
  err = bind( listen_sd , (struct sockaddr*) &sa_serv, sizeof(sa_serv));
  CHK_ERR(err, "bind");
  //开始接收TCP连接
  err = listen ( listen_sd , 5);
  CHK_ERR(err, "listen");
  client_len = sizeof(sa_cli);
  while(1)
  //接受客户端TCP连接
  sd = accept(listen_sd,(struct sockaddr*)&sa_cli,(int*)&client_len);
  CHK_ERR(sd, "accept");
  // closesocket ( listen_sd );
  //打印客户端信息
  printf ("Connection from %s, port %u\\n", inet_ntoa ( sa_cli .sin_addr), ntohs( sa_cli . sin_port ));
  //新建SSL
  ssl = SSL_new(ctx);
  CHK_NULL(ssl);
  //设置链接句柄到SSL结构体
  SSL_set_fd(ssl , sd);
  //接受SSL链接
  err = SSL_accept(ssl);
  CHK_SSL(err);
  //获得SSL链接用到的算法
  printf ("SSL connection using %s\\n", SSL_get_cipher(ssl ));
 //获得客户端证书
  client_cert = SSL_get_peer_certificate ( ssl );
  if ( client_cert != NULL)
  printf ("Client certificate :\\n");
  str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
  CHK_NULL(str);
  printf ("\\t subject : %s\\n", str );
  OPENSSL_free(str);
  str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
  CHK_NULL(str);
  printf ("\\t issuer : %s\\n", str );
  OPENSSL_free(str);
  X509_free( client_cert );
   
  else
  printf ("Client does not have certificate .\\n");
  //读取客户端发送的消息
  err = SSL_read(ssl, buf, sizeof(buf) - 1);
  CHK_SSL(err);
  buf[err] = ' \\0 ' ;
  printf ("Got %d chars:'%s '\\ n", err , buf);
  //发送消息到客户端
  err = SSL_write(ssl , "I hear you.", strlen ("I hear you."));
  CHK_SSL(err);
 
 //清除内存
 closesocket (sd);
 SSL_free(ssl);
 SSL_CTX_free(ctx);
 return 0;

3.结果

  (1) 服务器端等待客户端通信请求:

  

  (2) 运行客户端:

   运行客户端与服务器端连接成功

  

 (3) 服务器端接收客户端消息:

  

  (4) 用MFC构建客户端和服务器端:



4.问题及解决方法

  (1) 主要是一些传参问题,比如经常遇到将unsigned型赋给了const型。

  例如:sd = accept(listen_sd,(struct sockaddr*)&sa_cli,(int*)&client_len);

  第三个参数就存在传参不匹配,红色的int*为修改后的。

  (2) 开始未将cert .cer" //服务端证书,"key.pem" //服务器端私钥,"root.cer"//根证书等文件引入工程,导致找不到文件错误,这使客户端无法验证服务器的真伪,从而无法建立安全连接。

  (3) 出现参数不匹配错误:

  Const SSL_METHOD *meth;
  meth =SSLv23_server_method(); //使用SSLV2或V3协议

   红色const为新加的。


以上是关于SSL/TLS编程的主要内容,如果未能解决你的问题,请参考以下文章

关于SSL/TLS协议信息泄露漏洞(CVE-2016- 2183)解决方案

SSL/TLS协议详解

使用wireshark分析SSL/TLS

SSL/TLS编程

SSL/TLS编程

如何在NGINX网站服务器中实施SSL完美前向保密技术