流程

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_readSSL上下文中读取解密后的应用层数据.

 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