SSL/TLS 基本概念
SSL和TLS的关系与区别
简单一句话概括就是TLS是基于SSL发布的新版本,更新更安全
参考一下文章:
SSL/TLS 单向认证步骤
SSL/TLS双向认证步骤
SSL/TLS认证证书生成
使用以下脚本生成:
- 首先自己做CA,需要生成CA的秘钥(
ca.key
)和证书(ca.crt
) - 服务器的秘钥(
server.key
)和被CA签过的证书(server.crt
)。(server.csr
是中间文件,用来给CA生成server.crt
时使用,如果CA不是自己,则需要将这个发送给服务器和客户端都信任的CA,由它帮我们生成server.crt
) - 客户端的秘钥(
client.key
)和被CA签过的证书(client.crt
)。(同理,client.csr
是中间文件)
运行脚本会要求填写一些信息,总共会填写三次,第一次是CA的信息,第二次是服务器的信息,第三次是客户端的信息
脚本最后注释掉的部分可以用来验证证书是否可用
#!/bin/bash
echo ""
echo "========generate CA cert and key files========"
echo ""
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -out ca.crt
echo ""
echo "========generate Server cert and key files========"
echo ""
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
echo ""
echo "========generate Client cert and key files========"
echo ""
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr
echo ""
echo "========sign server cert with CA========"
echo ""
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
#openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
echo ""
echo "========sign client cert with CA========"
echo ""
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
#openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# openssl s_server -accept 10001 -key server.key -cert server.crt
# openssl s_client -connect localhost:10001 -CAfile ca.crt
# openssl s_server -accept 10001 -key server.key -cert server.crt -Verify 5
# openssl s_client -connect localhost:10001 -cert client.crt -key client.key -CAfile ca.crt
单向认证时,客户端需要验证服务器是否是可信任的,客户端应该持有CA证书ca.crt
,服务器应该持有服务器证书server.crt
和服务器秘钥server.key
。
双向认证时,客户端需要验证服务器是否是可信任的,而且服务器也要验证客户端是否是可信任的,客户端应该持有CA证书ca.crt
和客户端证书client.crt
以及客户端秘钥client.
,服务器应该持有服务器证书server.crt
和服务器秘钥server.key
。
socket + SSL/TLS 实现
服务器
python
import socket, ssl, time, threading
HOST = ''
PORT = 3456
BUFSIZE = 1024
ADDR = (HOST,PORT)
def receiveThread(connstream):
count = 0
while True:
data = connstream.recv(BUFSIZE)
if not data:
print("client disconnect")
break;
count+=1
print(count," receive from data:",data.decode("utf-8"))
connstream.send(data)
print("ack complete")
connstream.shutdown(socket.SHUT_RDWR)
connstream.close()
# main
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
context.load_cert_chain(certfile="server.crt", keyfile="server.key")
bindsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
bindsocket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
bindsocket.bind(ADDR)
bindsocket.listen(5)
while True:
newsocket,addr = bindsocket.accept()
try:
connstream = context.wrap_socket(newsocket, server_side=True)
except Exception as e:
print("SSL connect fail,cause:")
print(e)
continue
print("hello client,ip:")
print(addr)
t = threading.Thread(target=receiveThread,args=(connstream,))
t.setDaemon(True)
t.start()
客户端
python
import socket, ssl, pprint,time
HOST = '127.0.0.1'
PORT = 3456
BUFSIZE = 1024
ADDR = (HOST,PORT)
#socket create success
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# require a certificate from the server
# single authentication (单向验证)
# ssl_sock = ssl.wrap_socket(s,ca_certs="ca.crt",cert_reqs=ssl.CERT_REQUIRED)
# mutual authentication(双向验证)
ssl_sock = ssl.wrap_socket(s,ca_certs="ca.crt", certfile="client.crt", keyfile="client.key", cert_reqs=ssl.CERT_REQUIRED)
#socket connect success
ssl_sock.connect(ADDR)
# note that closing the SSLSocket will also close the underlying socket
pprint.pprint(ssl_sock.getpeercert())
count = 0
while True:
data = "hello this message from client"
if not data:
break
count+=1
print(count," send data:",data)
ssl_sock.send(data.encode("utf-8"))
data=ssl_sock.recv(BUFSIZE)
if not data:
break
print("received data:",data.decode("utf-8"))
time.sleep(3)
ssl_sock.close()
C ( linux )
先安装opennssl和libssl
编译使用
gcc -o ssl_client.o ssl_client.c -lssl -lcrypto
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define SSL_CLIENT_RSA_CERT "client.crt"
#define SSL_CLIENT_RSA_KEY "client.key"
#define SSL_CLIENT_RSA_CA_CERT "ca.crt"
#define SSL_SERVER_ADDR "127.0.0.1"
#define SSL_SERVER_PORT 3456
#define OFF 0
#define ON 1
int main(void)
{
int verify_peer = ON;
int verify_mutual = ON;
SSL_METHOD *client_meth;
SSL_CTX *ssl_client_ctx;
int clientsocketfd;
// struct sockaddr_un serveraddr;
struct sockaddr_in servaddr;
int handshakestatus;
SSL *clientssl;
char buffer[1024] = "Client Hello World";
int ret;
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
client_meth = (SSL_METHOD*)SSLv23_client_method();
ssl_client_ctx = SSL_CTX_new(client_meth);
if(!ssl_client_ctx)
{
printf("ctx create fail\n");
ERR_print_errors_fp(stderr);
return -1;
}
if(verify_peer)
{
if(verify_mutual)
{
if(SSL_CTX_use_certificate_file(ssl_client_ctx, SSL_CLIENT_RSA_CERT, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
return -1;
}
if(SSL_CTX_use_PrivateKey_file(ssl_client_ctx, SSL_CLIENT_RSA_KEY, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
return -1;
}
if(SSL_CTX_check_private_key(ssl_client_ctx) != 1)
{
printf("Private and certificate is not matching\n");
return -1;
}
}
// See function man pages for instructions on generating CERT files
if(!SSL_CTX_load_verify_locations(ssl_client_ctx, SSL_CLIENT_RSA_CA_CERT, NULL))
{
ERR_print_errors_fp(stderr);
return -1;
}
SSL_CTX_set_verify(ssl_client_ctx, SSL_VERIFY_PEER, NULL);
SSL_CTX_set_verify_depth(ssl_client_ctx, 1);
}
printf("new socket\n");
if((clientsocketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("Error on socket creation\n");
return -1;
}
// memset(&serveraddr, 0, sizeof(struct sockaddr_un));
// serveraddr.sun_family = AF_INET;
// serveraddr.sun_path[0] = 0;
// strncpy(&(serveraddr.sun_path[1]), SSL_SERVER_ADDR, strlen(SSL_SERVER_ADDR) + 1);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SSL_SERVER_PORT);
if( inet_pton(AF_INET, SSL_SERVER_ADDR, &servaddr.sin_addr) <= 0)
{
printf("inet_pton error for %s\n",SSL_SERVER_ADDR);
return -1;
}
printf("connect now\n");
if(connect(clientsocketfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_un)) < 0)
{
printf("connect error:%s,%d",strerror(errno),errno);
return -1;
}
printf("connect socket success\n");
clientssl = SSL_new(ssl_client_ctx);
if(!clientssl)
{
printf("Error SSL_new\n");
return -1;
}
SSL_set_fd(clientssl, clientsocketfd);
ERR_print_errors_fp(stderr);
printf("ssl new success\n");
if((ret = SSL_connect(clientssl)) != 1)
{
printf("0000\n");
printf("Handshake Error %d\n", SSL_get_error(clientssl, ret));
ERR_print_errors_fp(stderr);
return -1;
}
printf("ssl connect success\n");
if(verify_peer)
{
X509 *ssl_client_cert = NULL;
char* line;
ssl_client_cert = SSL_get_peer_certificate(clientssl);
if(ssl_client_cert)
{
long verifyresult;
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(ssl_client_cert), 0, 0);
printf("Subject: %s\n", line);
free(line); /* free the malloc'ed string */
line = X509_NAME_oneline(X509_get_issuer_name(ssl_client_cert), 0, 0);
printf("Issuer: %s\n", line);
free(line); /* free the malloc'ed string */
verifyresult = SSL_get_verify_result(clientssl);
if(verifyresult == X509_V_OK)
printf("Certificate Verify Success\n");
else
printf("Certificate Verify Failed\n");
X509_free(ssl_client_cert);
}
else
printf("There is no client certificate\n");
}
printf("Send to SSL server:%s\n", buffer);
SSL_write(clientssl, buffer, strlen(buffer) + 1);
SSL_read(clientssl, buffer, sizeof(buffer));
printf("Received from SSL server:%s\n", buffer);
SSL_shutdown(clientssl);
close(clientsocketfd);
SSL_free(clientssl);
SSL_CTX_free(ssl_client_ctx);
}
C (mbedtls)
下载mbedtls
然后使用ssl_client1.c
(需要修改服务器地址端口和ca证书)和ssl_client2.c
(需要修改服务器地址、端口、ca证书、客户端证书、客户端秘钥)进行验证
其它
测试HTTPS延迟
curl -w "TCP handshake: %{time_connect}, SSL handshake: %{time_appconnect}\n" -so /dev/null https://www.alipay.com
上面命令中的w参数表示指定输出格式,time_connect变量表示TCP握手的耗时,time_appconnect变量表示SSL握手的耗时(更多变量请查看文档和实例),s参数和o参数用来关闭标准输出。