前言
最近在整理电脑上项目工程时,发现之前用来测试gmtls通信编写的c/s测试代码。因此顺便整理了下,记录下来,方便以后回忆。
概述
进行SSL编程实现之前,我们肯定已经对SSL原理有了一定的了解。这里,小编也不再赘述。(尚不了解的可以参考这篇文章:SSL/TLS原理 详细整理版)
实现流程
原理理解的差不多了,就可以开始使用gmssl编程实现基于tls的socket通讯了(PS. gmssl中已经做了大部分事情了,我们只需要调用一些基本的接口就可以完成TLS socket通讯)。具体分为以下几步:
- 初始化
这一步主要是初始化openssl库,创建会话上下文等。调用的主要接口如下:
//SSL 库初始化
SSL_library_init();
//载入所有 SSL 算法
//OpenSSL_add_all_algorithms();
//加载SSL错误信息
SSL_load_error_strings();
//指定服务端使用的协议
const SSL_METHOD *GMTLS_server_method(void); /* GMTLSv1.1 */
//指定客户端使用的协议
const SSL_METHOD *GMTLS_client_method(void); /* GMTLSv1.1 */
//建立SSL上下文
SSL_CTX *SSL_CTX_new(const SSL_METHOD *meth);
- 加载证书和私钥
证书和私钥包括CA证书,服务端签名证书和私钥,服务端加密证书和私钥,客户端证书和私钥。调用的接口有:
//指定所支持的密码套件
int SSL_CTX_set_cipher_list(SSL_CTX *, const char *str);
//此函数用来便是加载CA证书文件的
int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath);
//加载自己的证书文件.
int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
//加载自己的私钥,以用于签名.
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
//检查用户私钥是否正确
int SSL_CTX_check_private_key(const SSL_CTX *ctx);
//加载私钥时一般要一个验证密码,这个密码是由生成私钥的一方来设定的,客户端得到这个密码后要通过一个接口传入验证
void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u);
//缺省mode是SSL_VERIFY_NONE,如果想要验证对方的话,便要将此项变成SSL_VERIFY_PEER.SSL/TLS中缺省只验证server,如果没有设置 SSL_VERIFY_PEER的话,客户端连证书都不会发过来.
void SSL_CTX_set_verify(SSL_CTX *ctx, int mode, SSL_verify_cb callback);
- 第三步,建立ssl socket连接
首先要建立普通的socket连接,TCP连接成功后再与ssl进行关联。三个接口就可以完成:
//创建SSL对象
SSL *SSL_new(SSL_CTX *ctx);
//将普通的SOCKET加入到ssl
int SSL_set_fd(SSL *s, int fd);
//服务端接受客户端ssl连接
int SSL_accept(SSL *ssl);
//客户端连接服务端SSL
int SSL_connect(SSL *ssl);
- 第四步,发送和接收
//发送数据
int SSL_write(SSL *ssl, const void *buf, int num);
//接收数据
int SSL_read(SSL *ssl, void *buf, int num);
- 第五步,释放资源
最后就是记得把所有占用的资源都释放掉。首先要把普通的socket关闭,然后是和ssl相关的资源释放。调用的主要接口如下:
SSL_CTX_free(ctx);
SSL_shutdown(ssl);
SSL_free(ssl);
ERR_free_strings();
PS. 想了解更多接口信息可以参考这篇文章:openssl编程——SSL实现
注意事项
- GmSSL实现gmtls协议时,服务端必须设置双证书(签名证书和加密证书)才能正常通信;
- 在设置双证书时,需要先设置签名证书,然后再设置加密证书;
- 如果服务端只设置了一种加密套件,那么客户端要么接受要么返回错误。加密套件的选择是由服务端做出的。
- 这里提供一个很好的国密网站:免费申请国密证书,功能很强大,大家可以在正上面申请证书哦~
示例
服务端
// TestServer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#ifdef WIN32
#include "Winsock2.h"
#pragma comment(lib, "ws2_32.lib")
#else
#include "sys/socket.h"
#endif
#include <iostream>
#include <conio.h>
using namespace std;
#include <openssl/err.h>
#include <openssl/ssl.h>
#define IPADDRESS "127.0.0.1"
#define PORT 443
#define LISTENQ 1
#define MAXBUF 1024
#define CA_CERT_FILE "GMCert_GMCA01.cert.pem"
#define SIGN_CERT_FILE "server.cert.pem"
#define SIGN_KEY_FILE "server.key.pem"
#define ENC_CERT_FILE "server_enc.cert.pem"
#define ENC_KEY_FILE "server_enc.key.pem"
#define CIPHER_
int config_ssl_ctx(SSL_CTX *ctx)
{
//SSL_CTX_set_cipher_list(ctx, "SM2-WITH-SMS4-SM3");
#if 1
// 是否要求校验对方证书 若不验证客户端身份则设置为: SSL_VERIFY_NONE
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
#else
//验证对方
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
//若验证,则放置CA证书
int ret = SSL_CTX_load_verify_locations(ctx, CA_CERT_FILE, NULL);
if (ret < 0)
{
printf("SSL_CTX_load_verify_locations failed.");
}
//设置pass phrase
SSL_CTX_set_default_passwd_cb_userdata(ctx, "12345678");
#endif
//双证书模式,需要先设置签名证书,然后再设置加密证书
//载入服务端数字签名证书
if (SSL_CTX_use_certificate_file(ctx, SIGN_CERT_FILE, SSL_FILETYPE_PEM) <= 0)
{
ERR_print_errors_fp(stderr);
return(1);
}
//载入服务端签名私钥
if (SSL_CTX_use_PrivateKey_file