博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

SSL协议分析和封装

Posted on 2016-07-06 11:14  bw_0927  阅读(1194)  评论(0)    收藏  举报

http://lnwdl.blog.163.com/blog/static/388304122012106953224/

 

 

目的:

internet网络中,点对点的通信双方面临着许多安全问题。诸如:数据的完整性,对方身份的有效性,通信的私密性以及数据的非否认性等等。Internet本身不是一个安全的通信媒介,那么我们就要通过其他方式来解决上述问题,这就SSL协议的产生的原因。

解决方式:

数据的完整性可以用数据摘要来解决;通信双方的身份的有效性和数据的非否认性可以用数字签名来解决;通信的私密性可以用加密的方式来解决。那么以上的问题都可在公钥体系下得到解决,前提是,可信的通道已经建立起来。PKI系统中没有提出在网络中建立通道的方式,SSL协议的精髓就在于给出了一个在网络中建立点对点通信的通道的的方式,然后后在这个通道中进行信息的加密传输。

实现:

SSL协议工作在传输层(Transport Layer),由于信道协商时具有时序的要求,所以它利用网络层提供的的面向的链接的服务建立通道,协商加密参数,进行传输数据的加密和解密工作。对于应用层的程序来说,SSL提供一种透明的传输服务。SSL协议分为四种,分别是:改变加密口令,警告,握手,应用数据。握手主要是协商加密参数,与改变加密口令协议配合建立通信通道;应用数据主要是对传输数据的加密和解密;警告用于报告各种异常,以及通知连接断开。其中最为复杂的要属握手协议了。

SSL建立连接的步骤:

1)           客户端向服务器发送ClientHello消息,【int ssl3_client_hello(SSL *s)】。消息中包括客户端支持的SSL的最高协议版本号,一个随机数randon_client,客户端所支持的加密套件列表,客户端支持的压缩方法列表。

2)           服务器端接收ClientHelloint ssl3_get_client_hello(SSL *s)】,发送ServerHello消息【int ssl3_send_server_hello(SSL *s)】。ServerHello消息中包括,会话所使用的协议版本,一个随机数randon_server,从客户端的加密列表中选取的加密套件,从客户端的压缩方法列表中选取的压缩方法。

3)           服务器将自己的证书发送给客户端【int ssl3_send_server_certificate(SSL *s)】。如果服务器需要验证客户端的证书,那么再向客户端发送证书请求【int ssl3_send_certificate_request(SSL *s)】;如果不需要,直接发送ServerHelloDone的消息【int ssl3_send_server_done(SSL *s)】,代表服务器端的hello消息发完了。

4)           客户端接受并验证服务器的证书【int ssl3_get_server_certificate(SSL *s)】。如果服务器发送证书请求,将证书发给服务器【int ssl3_send_client_certificate(SSL *s)】。

5)           客户端发送ClientKeyExchange消息【int ssl3_send_client_key_exchange(SSL *s)】。消息里面携带另一个随机数,我们称之为预主密钥(PreMasterSecret此时客户端的信息需要用服务器证书里面的公钥进行加密了

6)           客户端/服务器端接着发送改变加密口令ChangeCipherSpec(独立于握手协议)的消息。告诉对方接下来就要使用新的加密套件和密钥进行通信了。

7)           客户端/服务器端发送finish消息。消息的内容是双方对所有的握手消息和各自计算出来的主密钥(MasterSecret进行摘要,用新的加密算法加密。收到消息后在与自己算出的进行对比。

如果上述步骤的任意条件不满足,即发送alert断开连接。上面涉及到的传输的数字主要有三个,分别是random_clientrandom_serverPreMasterSecret。导出的数字是MasterSecret,和传输时加解密用到的参数。这里不打算详细介绍推倒过程,大致的关系如下图所示:

SSL协议分析和封装 - 无线电 - 无线电的博客

利用OPENSSL编程的架构

OPENSSL是一个开源的软件库,提供了许多关于传输层的安全编程的接口。对SSL协议也进行了封装,使我们能够方便的建立自己的SSL连接。如果您对网络编程有了解的话,利用OPENSSL编写SSL的代码将会非常简单。下面简单介绍利用OPENSSL进行SSL编程的架构。

客户端结构:

1)           准备好网络层的数据

fd = socket();

connect();

2)           准备好链路层的数据

meth = SSLv23_client_method();

ctx = SSL_CTX_new(meth);

ssl = SSL_new(ctx);

3)           绑定,连接

SSL_set_fd(ssl, fd);

SSL_connect(ssl);

4)           读写

SSL_write()/SSL_read();

5)           清理

SSL_shutdown(ssl);

shutdown(fd, SHUT_RDWR);

SSL_free(ssl);

服务器结构:

1)           准备好网络层的数据

fd = socket();

bind();

listen();

accept();

2)           准备好链路层的数据

meth = SSLv23_server_method();

ctx = SSL_CTX_new(meth);

ssl = SSL_new(ctx);

3)           绑定,连接

SSL_set_fd(ssl, fd);

SSL_connect(ssl);

4)           读写

SSL_read()/SSL_write();

5)           清理

SSL_shutdown(ssl);

shutdown(fd, SHUT_RDWR);

SSL_free(ssl);

 

 

 

一个简单的例子

下面是基于OPENSSL库在linux系统下的一个简单例子
公共文件:
public.h
 
  
#ifndef __PUBLIC_H__
#define __PUBLIC_H__
#include <openssl/x509.h>
#include <openssl/ssl.h>
#define SERVER_PORT 8888
#define SERVER_ADDR "127.0.0.1"
#define PRO_SSL_V2 0
#define PRO_SSL_V3 1
#define PRO_SSL_V23 2
#define PRO_TLS_V1 3
#define PRO_DTLS_V1 4
#define PRO_USED_IN_THIS PRO_SSL_V23 
#define SERVER_PART 0
#define CLIENT_PART 1
#define DEBUG 0
extern const SSL_METHOD * SelectMeth(int protocol, int role);
extern void ShowCertName(X509 *cert);
#endif
public.c
#include "public.h"
const SSL_METHOD * SelectMeth(int protocol, int role)
{
 const SSL_METHOD * meth;
 switch (role)
 {
  case SERVER_PART:
#ifdef DEBUG
   fprintf(stderr, "%s|%d|SelectMeth path server\n", __FILE__, __LINE__);
#endif
   switch (protocol)
   {
    case PRO_SSL_V2:
     meth = SSLv2_server_method();
     break;
    case PRO_SSL_V3:
     meth = SSLv3_server_method();
     break;
    case PRO_SSL_V23:
     meth = SSLv23_server_method();
     break;
    case PRO_TLS_V1:
     meth = TLSv1_server_method();
     break;
    case PRO_DTLS_V1:
     meth = DTLSv1_server_method();
     break;
    default:
     meth = NULL;
   }
   break;
  case CLIENT_PART:
#ifdef DEBUG
   fprintf(stderr, "%s|%d|SelectMeth path client\n", __FILE__, __LINE__);
#endif
   switch (protocol)
   {
    case PRO_SSL_V2:
     meth = SSLv2_client_method();
     break;
    case PRO_SSL_V3:
     meth = SSLv3_client_method();
     break;
    case PRO_SSL_V23:
     meth = SSLv23_client_method();
     break;
    case PRO_TLS_V1:
     meth = TLSv1_client_method();
     break;
    case PRO_DTLS_V1:
     meth = DTLSv1_client_method();
     break;
    default:
     meth = NULL;
   }
   break;
  default:
#ifdef DEBUG
   fprintf(stderr, "%s|%d|SelectMeth path ERROR\n", __FILE__, __LINE__);
#endif
   meth = NULL;
 }
 return meth;
}
void ShowCertName(X509 *cert)
{
 X509_NAME *name;
 char *str;
 if (NULL == cert)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|ShowName parameter ERROR\n", __FILE__, __LINE__);
#endif
  return;
 }
 fprintf(stdout, "Client certificate: \n");
 name = X509_get_subject_name(cert);
 str = X509_NAME_oneline(name, NULL, 0);
 if (str != NULL)
  fprintf(stdout, "\tsubject: %s\n", str);
 OPENSSL_free(str);
 name = X509_get_issuer_name(cert);
 str = X509_NAME_oneline(name, NULL, 0);
 if (str != NULL)
  fprintf(stdout, "\tissuer: %s\n", str);
 OPENSSL_free(str);
 return;
}

Makefile:
public.o:
 gcc public.c -c -o public.o
server.o:
 gcc server.c -c -o server.o
client.o:
 gcc client.c -c -o client.o
server: server.o public.o
 gcc server.o public.o -l ssl -l pthread -o server
client: client.o public.o
 gcc client.o public.o -l ssl -o client
all: server client
clean:
 rm -rf *.o
veryclean: clean
 rm -rf server client

多线程服务端:
server.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include "public.h"
#define CERTF  "server.cert"
#define KEYF  "server.key"
#define PASSWD "12345"
#define CACERT  "cacert.pem"
#define CAPATH "/etc/pki/CA"
#define LISTEN_QUEUE_LEN 128
#define BUFFER_LEN 1024
static pthread_mutex_t *lock_cs;
static long *lock_count;
pthread_t pthreads_thread_id(void)
{
 pthread_t ret;
 ret = pthread_self();
 return ret;
}
void pthreads_locking_callback(int mode, int type, char *file, int line)
{
 if (mode & CRYPTO_LOCK)
 {
  pthread_mutex_lock(&(lock_cs[type]));
  lock_count[type]++;
 }
 else
 {
  pthread_mutex_unlock(&(lock_cs[type]));
 }
}
int verify_callback_server(int ok, X509_STORE_CTX *ctx)
{
 fprintf(stdout, "verify_callback_server\n");
 return ok;
}
void *thread_fun(void *arg)
{
 int s, AcceptSock;
 SSL * ssl;
 X509 *client_cert;
 char *str;
 char buf[BUFFER_LEN];
 int ret;
 ssl = (SSL *)arg;
 s = SSL_get_fd(ssl);
 
 ret = SSL_accept(ssl);
 if (ret < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_accept ERROR\n", __FILE__, __LINE__);
#endif
  return;
 }
 client_cert = SSL_get_peer_certificate(ssl);
 if (NULL == client_cert)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_get_peer_certificate ERROR\n", __FILE__, __LINE__);
#endif
 }
 else
 {
  ShowCertName(client_cert);
  X509_free(client_cert);
 }
 memset(buf, 0, BUFFER_LEN);
 ret = SSL_read(ssl, buf, BUFFER_LEN - 1);
 if (ret < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_read ERROR\n", __FILE__, __LINE__);
  SSL_free(ssl);
  shutdown(s, SHUT_RDWR);
  return;
#endif
 }
 fprintf(stdout, "get: %s\n", buf);
 ret = SSL_write(ssl, "I hear you", strlen("I hear you"));
 if (ret < 0)
 {
  fprintf(stderr, "%s|%d|SSL_write ERROR\n", __FILE__, __LINE__);
 }
 SSL_free(ssl);
 shutdown(s, SHUT_RDWR);
 return;
}
int CheckCTXEnv(SSL_CTX *ctx, const char *cafile, const char *certfile, const char *keyfile)
{
 int ret1, ret2;
 int s_server_verify;
 STACK_OF(X509_NAME) *cert_names;
 if (NULL == ctx || NULL == cafile || NULL == certfile || NULL == keyfile)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|CheckCTXEnv parameters ERROR\n", __FILE__, __LINE__);
#endif
  return -1;
 }
 
 ret1 = SSL_CTX_load_verify_locations(ctx, CACERT, CAPATH);
 ret2 = SSL_CTX_set_default_verify_paths(ctx);
 if (!ret1 || !ret2)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|find cafile ERROR\n", __FILE__, __LINE__);
#endif
  return -2;
 }
 ret1 = SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM);
 if (ret1 <= 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_CTX_use_certificate_file ERROR\n", __FILE__, __LINE__);
#endif
  return -3;
 }
 SSL_CTX_set_default_passwd_cb_userdata(ctx, PASSWD);
 ret1 = SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM);
 if (ret1 <= 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_CTX_use_PrivateKey_file ERROR\n", __FILE__, __LINE__);
#endif
  return -4;
 }
 ret1 = SSL_CTX_check_private_key(ctx);
 if (!ret1)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_CTX_check_private_key ERROR\n", __FILE__, __LINE__);
#endif
  return -5;
 }
 s_server_verify = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
   | SSL_VERIFY_CLIENT_ONCE;
 SSL_CTX_set_verify(ctx, s_server_verify, verify_callback_server);
 cert_names = SSL_load_client_CA_file(CACERT);
 if (cert_names != NULL)
  SSL_CTX_set_client_CA_list(ctx, cert_names);
 else
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_CTX_check_private_key ERROR\n", __FILE__, __LINE__);
#endif
  return -6;
 }
 return 0;
}
int HandleSockData(void)
{
 int ret;
 int sd;
 struct sockaddr_in sa;
 sd = socket(AF_INET, SOCK_STREAM, 0);
 if (sd < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|soecket ERROR\n", __FILE__, __LINE__);
#endif
  return -1;
 }
 sa.sin_family = AF_INET;
 sa.sin_addr.s_addr = inet_addr(SERVER_ADDR);
 sa.sin_port = htons(SERVER_PORT);
 ret = bind(sd, (const struct sockaddr *)&sa, sizeof(sa));
 if (ret)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|bind ERROR\n", __FILE__, __LINE__);
#endif
  return -2;
 }
 ret = listen(sd, LISTEN_QUEUE_LEN);
 if (ret)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|listen ERROR\n", __FILE__, __LINE__);
#endif
  return -3;
 }
 return sd;
}
SSL_CTX *HandleCTXData(void)
{
 const SSL_METHOD *meth;
 SSL_CTX *ctx;
 int ret;
 meth = SelectMeth(PRO_USED_IN_THIS, SERVER_PART);
 if (NULL == meth)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SelectMeth ERROR\n", __FILE__, __LINE__);
#endif
  return NULL;
 }
 ctx = SSL_CTX_new(meth);
 if (NULL == ctx)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_CTX_new ERROR\n", __FILE__, __LINE__);
#endif
  return NULL;
 }
 ret = CheckCTXEnv(ctx, CACERT, CERTF, KEYF);
 if (ret)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|CheckCTXEnv ERROR\n", __FILE__, __LINE__);
#endif
  SSL_CTX_free(ctx);
  return NULL;
 }
 return ctx;
}
int main()
{
 int sd, accptsock;
 SSL_CTX *ctx;
 SSL *ssl;
 int ret;
 int i;
 int num_locks;
 pthread_t pid;
 SSLeay_add_ssl_algorithms();
 sd = HandleSockData();
 if (sd < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|HandNetData ERROR\n", __FILE__, __LINE__);
#endif
  return -1;
 }
 ctx = HandleCTXData();
 if (NULL == ctx)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|HandleCTXData ERROR\n", __FILE__, __LINE__);
#endif
  return -2;
 }
 num_locks = CRYPTO_num_locks();
 lock_cs = OPENSSL_malloc(num_locks * sizeof(pthread_mutex_t));
 lock_count = OPENSSL_malloc(num_locks * sizeof(long));
 for (i = 0; i < num_locks; i++)
 {
  lock_count[i] = 0;
  pthread_mutex_init(&(lock_cs[i]), NULL);
 }
 CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
 CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
 while (1)
 {
  struct timeval tv;
  fd_set fdset;
  pthread_t pid;
  tv.tv_sec = 1;
  tv.tv_usec = 0;
  FD_ZERO(&fdset);
  FD_SET(sd, &fdset);
  select(sd + 1, &fdset, &fdset, NULL, (struct timeval *)&tv);
  if (FD_ISSET(sd, &fdset))
  {
   accptsock = accept(sd, NULL, NULL);
   ssl = SSL_new(ctx);
   if (NULL == ssl)
   {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_new ERROR\n", __FILE__, __LINE__);
#endif
    continue;
   }
   ret = SSL_set_fd(ssl, accptsock);
   if (ret > 0)
   {
    ret = pthread_create(&pid, NULL, &thread_fun, (void *)ssl);
    pthread_detach(pid);
   }
   else
    continue;
  }
 }
 OPENSSL_free(lock_cs);
 OPENSSL_free(lock_count);
 SSL_CTX_free(ctx);
 return 0;
} 
单线程客户端
client.c
 #include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include "public.h"
#define CERTF  "client.cert"
#define KEYF  "client.key"
#define PASSWD "54321"
#define CACERT  "cacert.pem"
#define CAPATH "/etc/pki/CA"
#define BUFFER_LEN 1024
SSL *HandleSSLData(SSL_CTX *ctx, int sock)
{
 SSL *ssl;
 int ret;
 ssl = SSL_new(ctx);
 if (NULL == ssl)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_new ERROR\n", __FILE__, __LINE__);
#endif
  return NULL;
 }
 SSL_set_fd(ssl, sock);
 ret = SSL_connect(ssl);
 if (ret < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_connect ERROR\n", __FILE__, __LINE__);
#endif
  return NULL;
 }
 return ssl;
}
SSL_CTX *HandleCTXData(void)
{
 const SSL_METHOD *meth;
 SSL_CTX *ctx;
 int ret;
 meth = SelectMeth(PRO_USED_IN_THIS, CLIENT_PART);
 if (NULL == meth)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SelectMeth ERROR\n", __FILE__, __LINE__);
#endif
  return NULL;
 }
 ctx = SSL_CTX_new(meth);
 if (NULL == ctx)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_CTX_new ERROR\n", __FILE__, __LINE__);
#endif
  return NULL;
 }
 return ctx;
}
int HandleSockData(void)
{
 int ret;
 int sd;
 struct sockaddr_in sa;
 sd = socket(AF_INET, SOCK_STREAM, 0);
 if (sd < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|soecket ERROR\n", __FILE__, __LINE__);
#endif
  return -1;
 }
 sa.sin_family = AF_INET;
 sa.sin_addr.s_addr = inet_addr(SERVER_ADDR);
 sa.sin_port = htons(SERVER_PORT);
 ret = connect(sd, (const struct sockaddr *)&sa, sizeof(sa));
 if (ret)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|connect ERROR\n", __FILE__, __LINE__);
#endif
  return -2;
 }
 return sd;
}
int main()
{
 int sd;
 SSL_CTX *ctx;
 SSL *ssl;
 X509 *server_cert;
 char buff[BUFFER_LEN];
 int ret;
 SSLeay_add_ssl_algorithms();
 sd = HandleSockData();
 if (sd < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|HandNetData ERROR\n", __FILE__, __LINE__);
#endif
  return -1;
 }
 ctx = HandleCTXData();
 if (NULL == ctx)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|HandleCTXData ERROR\n", __FILE__, __LINE__);
#endif
  return -2;
 }
 ssl = HandleSSLData(ctx, sd);
 if (NULL == ssl)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|HandleSSLData ERROR\n", __FILE__, __LINE__);
#endif
  return -3;
 }
 server_cert = SSL_get_peer_certificate(ssl);
 ShowCertName(server_cert);
 ret = SSL_write(ssl, "hello world!", strlen("hello world!"));
 if (ret < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_write ERROR\n", __FILE__, __LINE__);
#endif
  return -4;
 }
 memset(buff, 0, BUFFER_LEN);
 ret = SSL_read(ssl, buff, sizeof(buff) -1 );
 if (ret < 0)
 {
#ifdef DEBUG
  fprintf(stderr, "%s|%d|SSL_read ERROR\n", __FILE__, __LINE__);
#endif
  return -5;
 }
 buff[ret] = '\0';
 fprintf(stdout, "get %s\n", buff);
 SSL_shutdown(ssl);
 SSL_free(ssl);
 shutdown(sd, SHUT_RDWR);
 return 0;
}