流程
https
是建立在SSL/TLS
上的.网络库实现了http客户端协议栈,那么怎么给它添加https
功能呢.
大体流程是:
- 建立
tcp
连接
SSL/TLS
协议握手
- 应用层数据加密传输
参考这篇博文
下面通过libev
做网络库,基于openssl1.1.1
发起一个TLS
连接,使用GET
方法发起一个请求,以此为例说明.
建立连接
1
2
3
4
5
6
7
8
9
|
static int new_tcp_client(const char* ip,int port)
{
int fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
setnonblock(fd);
struct sockaddr_in addr;
setaddress(ip,port, &addr);
connect(fd,(struct sockaddr*)(&addr),sizeof(addr));
return fd;
}
|
初始化SSL
上下文
这里使用BIO memory模式,这种模式可以很方便和底层网络库解耦
一个BIO in
和一个BIO out
分别用来过滤输入和输出数据
1
2
3
4
5
6
7
8
9
|
ctx.ssl_ctx = SSL_CTX_new(TLS_method());
//SSL_CTX_set_verify(ctx.ssl_ctx, SSL_VERIFY_PEER, verify_callback);
SSL_CTX_set_info_callback(ctx.ssl_ctx, sslinfo_callback);
ctx.ssl = SSL_new(ctx.ssl_ctx);
ctx.bio_in = BIO_new(BIO_s_mem());
ctx.bio_out = BIO_new(BIO_s_mem());
SSL_set_bio(ctx.ssl, ctx.bio_in, ctx.bio_out);
SSL_set_connect_state(ctx.ssl);
SSL_set_mode(ctx.ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
|
SSL/TLS
握手
1
|
SSL_do_handshake(ctx->ssl);
|
握手函数调用后,再从BIO out
里取出client端的握手数据发向对端.
1
2
3
4
5
6
7
8
9
10
11
12
|
int64_t read;
char* data = NULL;
read = BIO_get_mem_data(ctx->bio_out, &data); // NOLINT
printf("%" PRIu64 " bytes of ssl data ready to sent to the peer\n", read);
write_to_network(ctx, data, read);
// Clear the BIO buffer.
// NOTE: the (void) avoids the -Wunused-value warning.
(void)BIO_reset(ctx->bio_out);
|
后续要处理读回调中的对端发回的握手数据,并喂入BIO in
中
重复这个流程直到握手完成.
注意从SSL
握手流程中可以看到最后触发握手完成应该是服务端发送一段数据,所以在读回调中判断一下是否完成握手.
写应用数据
发送的应用层数据先写使用SSL_write
写入SSL
上下文,再从BIO out
中读出,就是加密后的数据了
再将加密后的数据发送到对端.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
if (!ctx->handshake_done)
{
printf("warning cannot send application data while ssl handshake was not finished\n");
return;
}
if (len == 0)
{
return;
}
int written;
written = SSL_write(ctx->ssl, (const void*)(buffer), len);
if (written < 0)
{
printf("SSL_write() failed\n");
return;
}
else if (written != len)
{
printf("OpenSSL SSL_write() wrote less (%d bytes) than given data (%ld bytes)\n", written, len);
return;
}
printf("OpenSSL SSL_write() wrote (%d bytes) \n", written);
// flush pending data.
write_pending_data(ctx);
|
读应用数据
读取应用数据先从网络层中读取数据,写入BIO in
后,使用SSL_read
从SSL
上下文中读取解密后的应用层数据.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
char buf[1024];
int eof = 0;
// Write the received DTLS data into the sslBioFromNetwork.
while(1) {
int len = recv(w->fd, buf, sizeof(buf), 0);
if(len <= 0 && errno != EAGAIN) {
printf("read from network, error: %d, len: %d, eof\n", errno, len);
eof = 1;
break;
}
if (len < 0) {
break;
}
printf("read frome network %d bytes\n", len);
written = BIO_write(ctx->bio_in, buf, len);
if (written != len)
{
printf("OpenSSL BIO_write() wrote less (%d bytes) than given data (%d bytes)\n", written, len);
}
}
char appdata[40960] = {0};
// Must call SSL_read() to process received DTLS data.
read = SSL_read(ctx->ssl, appdata, sizeof(appdata));
|
完整代码
ev_ssl.c