跳到主要内容

4 篇博文 含有标签「https」

查看所有标签

HTTPS: CA证书验证

· 阅读需 3 分钟

在当今数字化的世界中,信息的安全性至关重要。无论是在线购物、银行交易还是简单的网页浏览,我们都依赖于安全的通信渠道来保护我们的敏感信息。而在这个保护信息的背后,CA证书扮演着至关重要的角色。

什么是CA证书?

CA(证书颁发机构)是一个可信任的第三方实体,负责验证和签发数字证书。数字证书本质上是一种由CA签名的文件,用于确认在网络通信中各方的身份。

CA证书包含两个主要部分:一是网站信息,二是数字签名。

  • 网站信息通常包括域名、有效期、公钥、证书颁发者等。
  • 数字签名是由网站信息的哈希值通过证书颁发者的私钥加密而来。

这两部分合起来构成了一个数字证书。

证书校验

在签发证书的过程中,数字签名是由证书颁发者使用私钥对网站信息进行加密得到的。根据非对称加密的特性,证书的验证流程如下:

  1. 网站信息经过摘要计算得到一个哈希值。
  2. 数字签名经过证书颁发者的公钥解密得到一个哈希值。

这两个哈希值应该是相同的,如果相同则表示校验通过,证书是可信的。

证书验证流程大致如下:

CA证书验证流程

证书链验证

在证书验证的过程中,我们必须要知道证书颁发者的公钥。通常,操作系统中预装了一些根证书。如果我们访问的网站的证书是由根证书机构签发的,我们直接使用根证书的公钥来进行验证即可。

然而,并非所有的网站证书都是由根证书机构进行签发的,而是一级一级往下签发的,流程大致如下:

CA证书链验证

在访问网站时,服务器会将中间的1,2,3级证书都一起发送给我们。我们通过3级证书的颁发者(即二级证书)的公钥,对三级证书进行验证,然后再通过二级证书的颁发者(即一级证书)的公钥对二级证书进行验证,以此类推,直到验证到最顶层。接着,检查最顶层证书的颁发者是否在根证书之中,并用根证书进行最终的验证。如果验证通过,整个证书链都是可信的。

从TCP到HTTPS代码实现-https服务器

· 阅读需 4 分钟

https 就是在http和tcp之间加一道加密的过程, 在代码上的实现和一般的TCP Server区别就两点

  1. 在accpet之后要由ssl接管套接字, 协商加密算法,交换密钥等.
  2. 之后的send(), recv()替换成SSL的 SSL_write(), SSL_read()

代码实现

https_client.c

#include <openssl/bio.h>  
#include <openssl/ssl.h>
#include <openssl/err.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define SERVER_PORT 443
#define CA_CERT_FILE "server/ca.crt"
#define SERVER_CERT_FILE "server/server.crt"
#define SERVER_KEY_FILE "server/server.key"

SSL_CTX *ssl_ctx_int();
SSL *client_ssl_init(SSL_CTX *ctx, int fd);
int bind_and_listen();

int main(int argc, char **argv)
{
printf("Server Running at hppts://127.0.0.1/\n");

int data_len;
struct sockaddr_in addr;
int listen_fd, accept_fd;
socklen_t len = sizeof(addr);
SSL_CTX *ctx = ssl_ctx_int();
listen_fd = bind_and_listen();
int times = 0;
while(1){
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};

accept_fd = accept(listen_fd, (struct sockaddr *)&addr, &len);
SSL *ssl = client_ssl_init(ctx, accept_fd);
data_len = SSL_read(ssl,recvbuf, sizeof(recvbuf));
fprintf(stdout, "[%d] Get %d data:\n%s\n",times++, data_len, recvbuf);

sprintf(sendbuf, "HTTP/1.0 200 OK\r\n\r\n<h1>hello ssl! [%d]</h1>", times);
SSL_write(ssl, sendbuf, strlen(sendbuf));

SSL_free (ssl);
close(accept_fd);
}
SSL_CTX_free (ctx);
return 0;
}

ssl_ctx_int()

SSL初始化,服务器加载证书私钥

SSL_CTX  *ssl_ctx_int(){
SSLeay_add_ssl_algorithms();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ERR_load_BIO_strings();
SSL_CTX *ctx = SSL_CTX_new (SSLv23_method());
if(ctx == NULL){
printf("SSL_CTX_new error!\n");
exit(0);
}

// 是否要求校验对方证书 此处不验证客户端身份所以为: SSL_VERIFY_NONE
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);

// 加载CA的证书
if(!SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL)){
printf("SSL_CTX_load_verify_locations error!\n");
ERR_print_errors_fp(stderr);
exit(0);
}

// 加载自己的证书
if(SSL_CTX_use_certificate_file(ctx, SERVER_CERT_FILE, SSL_FILETYPE_PEM) <= 0){
printf("SSL_CTX_use_certificate_file error!\n");
ERR_print_errors_fp(stderr);
exit(0);
}

//加载自己的私钥 私钥的作用是,ssl握手过程中,对客户端发送过来的随机
//消息进行加密,然后客户端再使用服务器的公钥进行解密,若解密后的原始消息跟
//客户端发送的消息一直,则认为此服务器是客户端想要链接的服务器
if(SSL_CTX_use_PrivateKey_file(ctx, SERVER_KEY_FILE, SSL_FILETYPE_PEM) <= 0){
printf("SSL_CTX_use_PrivateKey_file error!\n");
ERR_print_errors_fp(stderr);
exit(0);
}

// 判定私钥是否正确
if(!SSL_CTX_check_private_key(ctx)){
printf("SSL_CTX_check_private_key error!\n");
ERR_print_errors_fp(stderr);
exit(0);
}
return ctx;
}

client_ssl_init()

和客户端ssl握手,协商算法,交换公钥等

SSL *client_ssl_init(SSL_CTX *ctx, int fd)
{
if (ctx == NULL){
printf("The SSL_CTX is NULL\n");
exit(0);
}
// 将连接付给SSL
SSL *ssl = SSL_new (ctx);
if(!ssl){
printf("SSL_new error!\n");
ERR_print_errors_fp(stderr);
exit(0);
}
SSL_set_fd (ssl, fd);
if(SSL_accept (ssl) != 1){
int icode = -1;
ERR_print_errors_fp(stderr);
int iret = SSL_get_error(ssl, icode);
printf("SSL_accept error! code = %d, iret = %d\n", icode, iret);
}

return ssl;
}

bind_and_listen()

建立套接字,绑定并监听,这个没什么说的

int bind_and_listen()
{
int listen_fd;

listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if( listen_fd == -1 ){
printf("socket error\n");
exit(0);
}
int one = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) {
printf("setsockopt error\n");
close(listen_fd);
}
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(SERVER_PORT);

if(bind(listen_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0 ){
printf("Bind error\n");
exit(0);
}

if(listen(listen_fd, 5) < 0){
printf("listen error\n");
exit(0);
}

return listen_fd;
}

编译运行

把上面的代码写到一个文件当中命名为https_server.c

gcc https_server.c -lssl -lcrypto -o https_server

此时还不能运行,还要生成服务器密钥,证书才可以, 看一下代码中宏定义的路径,生成证书放到相应路径

sudo ./https_server #监听443端口 需要root权限

然后打开浏览器,访问 https://127.0.0.1/, 因为证书是自制的,所以一般会拦截. 测试Chrome浏览器会拦截无法访问, 用Firefox忽略风险可以继续访问.

从TCP到HTTPS代码实现-https客户端

· 阅读需 3 分钟

C语言实现https请求, 先贴上代码, 以下代码来自<<HTTP权威指南>>, 稍作修改

https_client.c

#include <stdio.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>

#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

void main(int argc, char **argv)
{
SSL *ssl = NULL;
SSL_CTX *ctx = NULL;
const SSL_METHOD *client_method;
X509 *server_cert;
int sd,err;
char *str,*hostname,outbuf[4096],inbuf[4096],host_header[512];
struct hostent *host_entry;
struct sockaddr_in server_socket_address;
struct in_addr ip;

/* (1) 初始化openssl库 */
SSL_library_init();
ERR_load_crypto_strings();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();

client_method = SSLv23_client_method( );
ctx = SSL_CTX_new(client_method);
if (!ctx) {
fprintf (stderr, "SSL_CTX_new failed:\n");
ERR_print_errors_fp (stderr);
return;
}
printf("(1) SSL context initialized\n\n");

/* (2) 把域名转换成ip地址 */
hostname = argv[1];
host_entry = gethostbyname(hostname);
bcopy(host_entry->h_addr, &(ip.s_addr), host_entry->h_length);
printf("(2) '%s' has IP address '%s'\n\n", hostname, inet_ntoa(ip));

/* (3) 用tcp连接到server的443端口 */
sd = socket(AF_INET, SOCK_STREAM, 0);
memset(&server_socket_address, '\0', sizeof(server_socket_address));
server_socket_address.sin_family = AF_INET;
server_socket_address.sin_port = htons(443);
memcpy(&(server_socket_address.sin_addr.s_addr),
host_entry->h_addr, host_entry->h_length);
err = connect(sd, (struct sockaddr*) &server_socket_address,
sizeof(server_socket_address));
if (err < 0) { perror("can't connect to server port"); exit(1); }
printf("(3) TCP connection open to host '%s', port %d\n\n",
hostname, server_socket_address.sin_port);

/* (4) 在tcp连接上进行ssl握手 */
ssl = SSL_new(ctx); /* 创建ssl句柄 ,之后的send,recv都在ssl句柄上进行 */
if (!ssl) {
fprintf (stderr, "SSL_new failed:\n");
ERR_print_errors_fp (stderr);
return;
}

SSL_set_fd(ssl, sd); /* 把ssl句柄绑定到scoket */
err = SSL_connect(ssl); /* 启动ssl握手 */
printf("(4) SSL endpoint created & handshake completed\n\n");

/* (5) 打印出协商的好的加密密文 */
printf("(5) SSL connected with cipher: %s\n\n", SSL_get_cipher(ssl));

/* (6) 打印服务器的证书 */
server_cert = SSL_get_peer_certificate(ssl);
printf("(6) server's certificate was received:\n\n");
str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0);
printf(" subject: %s\n", str);
str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0);
printf(" issuer: %s\n\n", str);
/* 这里对证书进行验证 */
X509_free(server_cert);

/* (7) 握手完成 --- 开始在ssl上发送http请求 */
sprintf(host_header,"Host: %s:443\r\n",hostname);
strcpy(outbuf,"GET / HTTP/1.1\r\n");
strcat(outbuf,host_header);
strcat(outbuf,"Connection: close\r\n");
strcat(outbuf,"\r\n");
err = SSL_write(ssl, outbuf, strlen(outbuf));
shutdown (sd, 1); /* send EOF to server */
printf("(7) sent HTTP request over encrypted channel:\n\n%s\n",outbuf);


/* (8) 通过ssl句柄读取服务器响应 */
printf ("(8) got back %d bytes of HTTP response:\n");
do{
memset(inbuf, 0, sizeof(inbuf));
err = SSL_read(ssl, inbuf, sizeof(inbuf) - 1);
printf ("%s",inbuf);
inbuf[err] = '\0';
}while(err > 0);
/* (9) 释放连接,句柄 */
SSL_shutdown(ssl);
close(sd);
SSL_free(ssl);
SSL_CTX_free(ctx);
printf("(9) all done, cleaned up and closed connection\n\n");
}

编译运行

如果提示找不到openssl的头文件, 自行搜索openssl库的安装

gcc -o https_client https_client.c -lssl -lcrypto

访问百度

./https_client www.baidu.com

访问本地, 一般本地domain Name是localhost,

./https_client localhost

如果域名不能解析, 自己代码中去掉域名解析的部分,自己把地址替换成ip

openssl证书链验证

· 阅读需 2 分钟

证书链

浏览器是怎么保证访问的网站是正经的官方网站而不是其他的钓鱼网站呢,Chome浏览器访问网站时,可信任的网站地址旁边会有一个绿色的锁标准,表明该网站是可信任的,它是怎么知道该网站是可信任的呢。

因为浏览器会内置一些证书,其他证书都是有这些证书签发的, 通过内置的证书来验证其他证书的有效性。这些浏览器内置的证书叫做Root CA(根CA证书), 其他网站的证书都是由Root CA证书一层一层往下签发的。

证书认证原理

  1. 服务器首先生成一个密钥对,把公钥提交给CA
  2. CA用自己的私钥对服务器提供的公钥进行签名得到证书
  3. https服务器在与客户端进行连接的时候会将证书和公钥一起发给客户端,客户端用CA的公钥对证书进行验证,对比一致则证明该证书确实是CA发布的。

通过以上的机制就就确认的网站的真实性。

openssl生成证书链

  1. 生成Root CA密钥和自签证书
openssl genrsa -out ca.key 2048
openssl req -new -x509 -key ca.key -out ca.crt -days 3650
  1. 生成server端密钥和证书请求
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
  1. 用Root CA给server颁发证书前先构造环境(ubuntu 18.04测试通过)
cp /etc/ssl/openssl.cnf .
mkdir demoCA/newcerts -p
touch demoCA/index.txt
touch demoCA/index.txt.attr
echo "00" > demoCA/serial
  1. 用自签Root CA颁发证书。注意,根证书和要签发的证书,国家/省/城市字段要一样,否者会失败
openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key -config openssl.cnf