赞
踩
- Version : 表示客户端支持的SSL/TLS协议版本
- Random : 客户端随机数
- Session ID : 和会话恢复有关
- Cipher Suites : 客户端支持的密码套件列表
- Compression Methods : 客户端支持的压缩方法
- Extension : 扩展项
- Version : 服务端根据客户端传递的版本号,选择一个双方都支持的版本
- Random : 服务端随机数
- Session ID : 和会话恢复有关
- Cipher Suite : 根据客户端传递过来的密码套件列表,选择一个双方都支持的密码套件
- Compression Method : 压缩算法
- Extension : 扩展项
struct {
select(KeyExchangeAlgorithm){
case rsa:
EncryptedPreMasterSecret;
case dhe_dss:
case dhe_rsa:
case df_dss:
case dh_rsa:
case dh_anon:
ClientDiffieHellmanPublic;
case ec_diffie)hellman:
ClientECHiffieHellmanPublic;
}exchange_keys;
} ClientKeyExchange;
- Client MAC Key
- Server MAC Key
- Client Key
- Server Key
- Client IV
- Server IV
接下来用Nginx搭建一个HTTPS网站,实现SSL/TLS的双向认证。
打开Nginx的配置文件/usr/local/nginx/conf/nginx.conf,HTTPS服务默认是注释的,去掉注释,并且需要再添加两个字段ssl_verify_client 和 ssl_client_certificate
# HTTPS server server { listen 10088 ssl; server_name localhost; # 服务端证书 ssl_certificate server.crt; # 服务端证书密钥 ssl_certificate_key server.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; # 表示开启客户端证书校验 ssl_verify_client on; # 放置CA证书 ssl_client_certificate ca.crt; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { root html; index index.html index.htm; } }
这里需要用到三张证书,CA证书,服务端证书和客户端证书,可以通过OpenSSL命令行工具来生成。
CA证书
- openssl genrsa -out ca.key 2048
- openssl req -new -x509 -key ca.key -out ca.crt -days 365
服务端证书
- openssl genrsa -out server.key 2048
- openssl req -new -key server.key -out server.csr
- openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt -days 365
客户端证书
- openssl genrsa -out client.key 2048
- openssl req -new -key client.key -out client.csr
- openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt -days 365
- openssl pkcs12 -export -in client.crt -inkey client.key -out client.pfx
服务端证书以及私钥和CA证书生成后,放到Nginx配置文件所在目录下,然后将客户端的pfx证书拷贝到windows平台下,打开Edge浏览器的设置界面,在隐私、搜索和服务栏找到管理证书,然后把刚才的client.pfx证书导入即可。这里注意下,导入后要关掉浏览器重新打开。
然后去访问对应https网站,浏览器会弹出对话框,让我们选择对应的客户端证书。只有选择证书并确认后,才可以成功访问到对应的https网站。没有客户端证书或者未成功选择证书,都是无法访问的。如果是单向认证的话,就不需要客户端证书了。
接下来就用WireShark抓包工具详细分析SSL/TLS双向认证的握手过程。
先看下整体流程,抓包可以看到,在进行SSL/TLS握手前,先要进行TCP的三次握手,建立连接。关于TCP的握手过程,这里就不介绍了,可参考我的这一篇文章 抓包分析TCP协议
接下来对SSL/TLS握手的每条信息,详细看一下
Client Hello
Server Hello
Server Certificate
Server Key Exchange
Certificate Request
Server Hello Done
Client Certificate
Client Key ExChange
Certificate Verify
Change Cipher Spec
Finished [Encrypted Handshake Message]
Application Data
这就是整个SSL/TLS协议握手的过程。
上面展示的是SSL/TLS双向认证的过程,可以关掉Nginx对客户端的证书校验,实现单向认证。
这是单向认证时抓的包,可以与上面的双向认证比对下,主要少了服务端请求客户端发送证书的消息Certificate Request,客户端发送证书消息Client Certificate,客户端证书校验消息Certificate verify。其他流程和双向认证是一样的。
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/rsa.h> #include <openssl/crypto.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> #define CERTF "server.crt" /*服务端的证书(需经CA签名)*/ #define KEYF "server.key" /*服务端的私钥(建议加密存储)*/ #define CACERT "ca.crt" /*CA 的证书*/ #define PORT 10088 /*准备绑定的端口*/ int main (){ SSL_load_error_strings(); /*为打印调试信息作准备*/ OpenSSL_add_ssl_algorithms(); /*初始化*/ SSL_CTX* ctx = SSL_CTX_new(TLSv1_server_method()); if(ctx == NULL) { printf("SSL_CTX_new failed.\n"); return -1; } SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); /*是否验证客户端证书,双向认证时开启*/ SSL_CTX_load_verify_locations(ctx,CACERT,NULL); /*若验证,则放置CA证书*/ if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } if (!SSL_CTX_check_private_key(ctx)) { printf("Private key does not match the certificate public key\n"); return -1; } SSL_CTX_set_cipher_list(ctx,"RC4-MD5"); /*开始正常的TCP socket过程.*/ printf("Begin TCP socket...\n"); int listenSock = socket(AF_INET, SOCK_STREAM, 0); if(listenSock == -1){ perror("socket"); return -1; } struct sockaddr_in sa_serv; 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(PORT); if(bind(listenSock, (struct sockaddr*) &sa_serv, sizeof (sa_serv)) == -1){ perror("bind"); return -1; } /*接受TCP链接*/ if(listen (listenSock, 5) == -1){ perror("listen"); return -1; } struct sockaddr_in sa_cli; socklen_t client_len = sizeof(sa_cli); int connfd = accept (listenSock, (struct sockaddr*) &sa_cli, &client_len); if(connfd == -1){ perror("accept"); close (listenSock); return -1; } printf ("[%s:%d] connected...\n", inet_ntoa(sa_cli.sin_addr), sa_cli.sin_port); /*TCP连接已建立,进行服务端的SSL过程. */ printf("Begin server side SSL\n"); SSL* ssl = SSL_new (ctx); if(ssl == NULL){ printf("SSL_new failed.\n"); return -1; } SSL_set_fd (ssl, connfd); int sslSock = SSL_accept (ssl); if(sslSock == -1){ ERR_print_errors_fp(stderr); return -1; } printf("SSL_accept finished\n"); /*打印所有加密算法的信息(可选)*/ printf ("SSL connection using %s\n", SSL_get_cipher(ssl)); /*得到客户端的证书并打印些信息(可选) */ X509* client_cert = SSL_get_peer_certificate (ssl); if (client_cert != NULL) { printf ("Client certificate:\n"); char* subStr = X509_NAME_oneline(X509_get_subject_name (client_cert), 0, 0); if(subStr == NULL){ printf("X509_NAME_oneline subject failed.\n"); return -1; } printf ("subject: %s\n", subStr); //Free (subStr); char* issStr = X509_NAME_oneline(X509_get_issuer_name (client_cert), 0, 0); if(issStr == NULL){ printf("X509_NAME_oneline subject failed.\n"); return -1; } printf ("issuer: %s\n", issStr); //Free (issStr); X509_free (client_cert);/*如不再需要,需将证书释放 */ }else{ printf ("Client does not have certificate\n"); } char buf[4096] = {0}; /* 数据交换开始,用SSL_write,SSL_read代替write,read */ int readSize = SSL_read(ssl, buf, sizeof(buf) - 1); if(readSize == -1){ ERR_print_errors_fp(stderr); return -1; } printf ("SSL_read buf[%d] = {%s}\n", readSize, buf); if(SSL_write (ssl, "Welcome to Connect to Server!", strlen("Welcome to Connect to Server!")) == -1){ ERR_print_errors_fp(stderr); return -1; } shutdown (connfd, 2); SSL_free (ssl); SSL_CTX_free (ctx); return 0; }
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <openssl/rsa.h> #include <openssl/crypto.h> #include <openssl/x509.h> #include <openssl/pem.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/rand.h> /*所有需要的参数信息都在此处以#define的形式提供*/ #define CERTF "client.crt" /*客户端的证书(需经CA签名)*/ #define KEYF "client.key" /*客户端的私钥(建议加密存储)*/ #define CACERT "ca.crt" /*CA 的证书*/ #define PORT 10088 /*服务端的端口*/ #define SERVER_ADDR "127.0.0.1" /*服务段的IP地址*/ int main () { /*初始化*/ OpenSSL_add_ssl_algorithms(); //载入所有SSL错误消息 SSL_load_error_strings(); /*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/ /*申请SSL会话环境*/ SSL_CTX *ctx = SSL_CTX_new(TLSv1_client_method()); if(ctx == NULL){ printf("SSL_CTX_new failed!\n"); return -1; } /*是否验证服务端证书*/ SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); /*若验证对方,则放置CA证书*/ SSL_CTX_load_verify_locations(ctx,CACERT,NULL); /*加载自己的证书*/ if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } /*加载自己的私钥,以用于签名*/ if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); return -1; } /*调用了以上两个函数后,检验一下自己的证书与私钥是否配对*/ if (!SSL_CTX_check_private_key(ctx)) { printf("Private key does not match the certificate public key\n"); return -1; } /*以下是正常的TCP socket建立过程*/ printf("Begin tcp socket...\n"); int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1){ perror("socket"); return -1; } struct sockaddr_in sa; memset (&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = inet_addr(SERVER_ADDR); /* Server IP */ sa.sin_port = htons(PORT); /* Server Port number */ if(connect(sock, (struct sockaddr*) &sa, sizeof(sa)) == -1){ perror("connect"); return -1; } /* TCP 链接已建立.开始 SSL 握手过程 */ printf("Begin SSL negotiation \n"); /*申请一个SSL套接字*/ SSL* ssl = SSL_new (ctx); if(ssl == NULL){ printf("SSL_new failed.\n"); return -1; } /*绑定读写套接字*/ SSL_set_fd(ssl, sock); if(SSL_connect(ssl) == -1){ ERR_print_errors_fp(stderr); return -1; } /*打印所有加密算法的信息(可选)*/ printf ("SSL connection using %s\n", SSL_get_cipher(ssl)); /*得到服务端的证书并打印些信息(可选) */ X509* server_cert = SSL_get_peer_certificate (ssl); if(server_cert == NULL){ printf("SSL_get_peer_certificate failed.\n"); return -1; } printf ("Server certificate:\n"); char* subStr = X509_NAME_oneline(X509_get_subject_name (server_cert),0,0); if(subStr == NULL){ printf("X509_NAME_oneline subject failed.\n"); return -1; } printf("subject: %s\n", subStr); // Free(subStr); char* issStr = X509_NAME_oneline (X509_get_issuer_name(server_cert),0,0); if(issStr == NULL){ printf("X509_NAME_oneline issuer failed.\n"); return -1; } printf ("issuer: %s\n", issStr); // Free (issStr); X509_free(server_cert); /*如不再需要,需将证书释放 */ /* 数据交换开始,用SSL_write,SSL_read代替write,read */ printf("Begin SSL data exchange\n"); if(SSL_write(ssl, "Hello, I am client!", strlen("Hello, I am client!")) == -1){ ERR_print_errors_fp(stderr); return -1; } char buf[4096] = {0}; int readSize = SSL_read(ssl, buf, sizeof(buf) - 1); if(readSize == -1){ ERR_print_errors_fp(stderr); return -1; } printf ("SSL_read buf[%d] = {%s}\n", readSize, buf); SSL_shutdown (ssl); /* send SSL/TLS close_notify */ shutdown (sock, 2); SSL_free (ssl); SSL_CTX_free (ctx); return 0; }
all: server client
server: server.cpp
g++ -o server server.cpp -lssl -lcrypto
client: client.cpp
g++ -o client client.cpp -lssl -lcrypto
clean:
rm server client
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。