欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

openSSL解析证书及建立安全通道通信实验

程序员文章站 2022-06-29 21:50:59
...

写在前面

  • 实验一可根据输入证书路径做解析

  • 实验二需要配套证书,文件夹结构如下

      certs
      │  
      ├─ca
      │      ca.crt
      │      ca.csr
      │      ca.key
      │      
      ├─client
      │      client.crt
      │      client.csr
      │      client.key
      │      client.pem
      │      
      └─server
             server.crt
             server.csr
             server.key
             server.pem
    

实验一

实验内容

利用openSSL或其他密码库(如python,go等),编写一个解码X.509数字证书的程序,能够解析证书中的基本ASN1项内容并打印输出

实验分析

该实验我尝试了多种方式来实现,在本实验报告中两种简单一种难:

  • Simple

      1.用pyOpenssl实现
      2.用Openssl把DER格式的证书转成X509格式,然后使用x509_print函数直接输出整个证书
    
  • hard

      把DER格式转为X509格式再 逐项 使用函数分析输出(效果不如x509_print好)
    

实验代码

第一种 用pyOpenssl

import OpenSSL
from dateutil import parser
print("请输入证书路径")
path = input()
content = open(path,"rb").read()
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, content)

print ("证书版本:",cert.get_version() + 1)
print ("证书***:",hex(cert.get_serial_number()))
print ("证书中使用的签名算法:",cert.get_signature_algorithm().decode("UTF-8"))
certIssue = cert.get_issuer()
print ("颁发者:",certIssue.commonName)
datetime_struct = parser.parse(cert.get_notBefore().decode("UTF-8"))
print ("有效期从:",datetime_struct.strftime('%Y-%m-%d %H:%M:%S'))
datetime_struct = parser.parse(cert.get_notAfter().decode("UTF-8"))
print ("到:",datetime_struct.strftime('%Y-%m-%d %H:%M:%S'))
certSubject = cert.get_subject()
print("使用者:",certSubject.commonName)
print ("证书是否已经过期:",cert.has_expired())
print("公钥长度:" ,cert.get_pubkey().bits())
print("公钥:\n" ,OpenSSL.crypto.dump_publickey(OpenSSL.crypto.FILETYPE_PEM, cert.get_pubkey()).decode("utf-8"))
print("扩展项数目:",cert.get_extension_count())

运行结果
openSSL解析证书及建立安全通道通信实验

第二种 用x509_print

#define _CRT_SECURE_NO_WARNINGS
#define MAX_LENGTH 4096

#include <openssl/x509.h>
extern "C" {
#include "openssl/applink.c"
};

int my_load_cert(unsigned char *str, unsigned long *str_len,
	const char *verify_cert, const unsigned int cert_len)
{
	FILE *fp;
	fp = fopen(verify_cert, "rb");
	if (NULL == fp)
	{
		fprintf(stderr, "fopen fail\n");
		return -1;
	}

	*str_len = fread(str, 1, cert_len, fp);
	fclose(fp);
	return 0;
}

X509 *der_to_x509(const unsigned char *der_str, unsigned int der_str_len)
{
	X509 *x509;
	x509 = d2i_X509(NULL, &der_str, der_str_len);
	if (NULL == x509)
	{
		fprintf(stderr, "d2i_X509 fail\n");

		return NULL;
	}
	return x509;
}
int main()
{
	OpenSSL_add_all_algorithms();

	char cert[MAX_LENGTH];

	unsigned char ca_der[MAX_LENGTH];
	unsigned long ca_der_len;
	X509 *ca = NULL;
	
	cout << "请输入证书地址" << endl;
	char *path = new char[80];
	cin.getline(path, 80);

	/* x509初始化 */
	sprintf(cert, path);
	my_load_cert(ca_der, &ca_der_len, cert, MAX_LENGTH);
	ca = der_to_x509(ca_der, ca_der_len);

	int ret;
	BIO *b;
	b = BIO_new(BIO_s_file());
	BIO_set_fp(b, stdout, BIO_NOCLOSE);
	ret = X509_print(b, ca);
	BIO_free(b);
	X509_free(ca);

	return 0;
}

运行结果
openSSL解析证书及建立安全通道通信实验

第三种 逐项解析输出

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <cstdio>  
#include <cstring> 
#include <string>
#include <fstream>
#include <ctime>
#include <iomanip>

#include <openssl/ossl_typ.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/conf.h>
extern "C" {
#include "openssl/applink.c"
};

using namespace std;
//#pragma comment(lib,"libssl.lib")
//#pragma comment(lib,"libcrypto.lib")

#define MAX_LEGTH 4096
int my_load_cert(unsigned char *str, unsigned long *str_len,
	const char *verify_cert, const unsigned int cert_len)
{
	FILE *fp;
	fp = fopen(verify_cert, "rb");
	if (NULL == fp)
	{
		fprintf(stderr, "fopen fail\n");
		return -1;
	}

	*str_len = fread(str, 1, cert_len, fp);
	fclose(fp);
	return 0;
}

X509 *der_to_x509(const unsigned char *der_str, unsigned int der_str_len)
{
	X509 *x509;
	x509 = d2i_X509(NULL, &der_str, der_str_len);
	if (NULL == x509)
	{
		fprintf(stderr, "d2i_X509 fail\n");

		return NULL;
	}
	return x509;
}

void hex_encode(unsigned char* readbuf, void *writebuf, size_t len)
{
	for (size_t i = 0; i < len; i++) {
		char *l = (char*)(2 * i + ((intptr_t)writebuf));
		sprintf(l, "%02x", readbuf[i]);
	}
}

void version(X509* cert) {
	//版本号
	int ver = X509_get_version(cert);
	cout << "Version: V" << ver + 1 << endl;
}

void serial(X509* cert) {
	//***
	ASN1_INTEGER *asn1_i = NULL;
	BIGNUM *bignum = NULL;
	char *serial = NULL;
	asn1_i = X509_get_serialNumber(cert);
	bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);
	serial = BN_bn2hex(bignum);
	cout << "Serial: " << serial << endl;
}

void signAlgo(X509* cert) {
	//签名算法
	EVP_PKEY *pubkey = X509_get_pubkey(cert);
	int sig_nid = X509_get_signature_nid(cert);
	
	if (sig_nid == NID_undef) {
		cout << "unable to find specified signature algorithm name." << endl;
	}
	else {
		cout << "Signature Algorithm: " << string(OBJ_nid2ln(sig_nid)) << endl;
	}
}

string Entity(X509_NAME *sub) {
	BIO * bio_out = BIO_new(BIO_s_mem());
	X509_NAME_print(bio_out, sub, 0);
	BUF_MEM *bio_buf;
	BIO_get_mem_ptr(bio_out, &bio_buf);
	string ject = string(bio_buf->data, bio_buf->length);
	BIO_free(bio_out);
	return ject;
}

void iss(X509* cert) {
	//颁发者
	X509_NAME *issuer = X509_get_subject_name(cert);
	cout << "Issuer: " << Entity(issuer) << endl;
}

int mypint(const char ** s, int n, int min, int max, int e)
{
	int retval = 0;
	while (n) {
		if (**s < '0' || **s > '9') { e = 1; return 0; }
		retval *= 10;
		retval += **s - '0';
		--n; ++(*s);
	}
	if (retval < min || retval > max) e = 1;
	return retval;
}

void asnTotime(ASN1_TIME *t) {
	const char *s;
	int generalized;
	struct tm time;
	int err = 0;

	if (t->type == V_ASN1_GENERALIZEDTIME) {
		generalized = 1;
	}
	else if (t->type == V_ASN1_UTCTIME) {
		generalized = 0;
	}
	else {
		cout << "error" << endl;
		return;
	}
	s = (char *)t->data; // Data should be always null terminated
	if (s == NULL || s[t->length] != '\0') {
		cout << "error" << endl;
	}
	if (generalized) {
		time.tm_year = mypint(&s, 4, 0, 9999, err) - 1900;
	}
	else {
		time.tm_year = mypint(&s, 2, 0, 99, err);
		if (time.tm_year < 50)
			time.tm_year += 100;
	}
	time.tm_mon = mypint(&s, 2, 1, 12, err) - 1;
	time.tm_mday = mypint(&s, 2, 1, 31, err);
	time.tm_hour = mypint(&s, 2, 0, 23, err);
	time.tm_min = mypint(&s, 2, 0, 59, err);
	if (*s >= '0' && *s <= '9') {
		time.tm_sec = mypint(&s, 2, 0, 59, err);
	}
	else {
		time.tm_sec = 0;
	}
	time.tm_year = time.tm_year + 1900;
	time.tm_mon = time.tm_mon + 1;
	time.tm_hour = time.tm_hour + 8;
	cout << time.tm_year << "年" << time.tm_mon << "月" << time.tm_mday << "日 ";
	cout << time.tm_hour << ":" << time.tm_min << ":" << time.tm_sec << endl;
}

void validTime(X509* cert) {
	//时间
	int err = 0;
	ASN1_TIME *start = X509_get_notBefore(cert);
	ASN1_TIME *end = X509_get_notAfter(cert);
	cout << "Validate " << endl;
	cout << setw(10) << "from: "; asnTotime(start);
	cout << setw(10) << "to: "; asnTotime(end);
}

void sub(X509* cert) {
	//使用者
	X509_NAME *subject = X509_get_subject_name(cert);
	cout << "Subject: " << Entity(subject) << endl;
}

void pubKey(X509* cert) {
	//公钥
	EVP_PKEY *pubkey = X509_get_pubkey(cert);	

	X509_PUBKEY *key = X509_get_X509_PUBKEY(cert);
	ASN1_OBJECT *ppkalg;
	const unsigned char *pk;
	int ppklen;
	X509_ALGOR *pa;
	X509_PUBKEY_get0_param(&ppkalg, &pk, &ppklen, &pa, key);
	//cout << ppklen << endl;
	
	RSA *rsa_key;
	rsa_key = EVP_PKEY_get1_RSA(pubkey);
	cout << "Subject Public Key Info: " << endl;
	RSA_print_fp(stdout, rsa_key, 0);
}

void issuKeyHash(X509* cert) {
	//颁发机构**标识符
	int i = 0;
	int crit = 0;
	char value[512] = { 0 };
	AUTHORITY_KEYID *akeyid = NULL;

	akeyid = (AUTHORITY_KEYID*)X509_get_ext_d2i(cert, NID_authority_key_identifier, &crit, NULL);

	strcat_s(value, 512, "KeyID=");
	for (i = 0; i < akeyid->keyid->length; i++)
	{
		char keyid[8] = { 0 };
		sprintf_s(keyid, 8, "%x ", akeyid->keyid->data[i]);
		strcat_s(value, 512, keyid);
	}
	cout << "Issuer Key Identifier: " << value << endl;
}

void subKeyHash(X509* cert) {
	//使用者**标识符
	int i = 0;
	int crit = 0;
	char value[512] = { 0 };
	ASN1_OCTET_STRING *skid = NULL;

	skid = (ASN1_OCTET_STRING*)X509_get_ext_d2i(cert, NID_subject_key_identifier, &crit, NULL);
	
	for (i = 0; i < skid->length; i++)
	{
		char keyid[8] = { 0 };
		sprintf_s(keyid, 8, "%x ", skid->data[i]);
		strcat_s(value, 512, keyid);
	}
	cout << "User Key Identifier: " << value << endl;
}

void keyUse(X509* cert) {
	//**用法
	char use[512] = { 0 };
	ASN1_BIT_STRING* lASN1UsageStr;
	lASN1UsageStr = (ASN1_BIT_STRING *)X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL);
	if (lASN1UsageStr)
	{
		char temp[32] = { 0 };
		unsigned short usage = lASN1UsageStr->data[0];
		if (lASN1UsageStr->length > 1)
		{
			usage |= lASN1UsageStr->data[1] << 8;
		}
		sprintf_s(temp, 32, "(%x)", usage);

		if (usage & KU_DIGITAL_SIGNATURE)
		{
			strcat_s(use, 512, "Digital Signature, ");
		}
		if (usage & KU_NON_REPUDIATION)
		{
			strcat_s(use, 512, "Non-Repudiation, ");
		}
		if (usage & KU_KEY_ENCIPHERMENT)
		{
			strcat_s(use, 512, "Key Encipherment, ");
		}
		if (usage & KU_DATA_ENCIPHERMENT)
		{
			strcat_s(use, 512, "Data  Encipherment, ");
		}
		if (usage & KU_KEY_AGREEMENT)
		{
			strcat_s(use, 512, "Key  Agreement, ");
		}
		if (usage & KU_KEY_CERT_SIGN)
		{
			strcat_s(use, 512, "Certificate Signature, ");
		}
		if (usage & KU_CRL_SIGN)
		{
			strcat_s(use, 512, "CRL Signature, ");
		}

		strcat_s(use, 512, temp);
	}
	else {
		cout << "Null Usage" << endl;
	}
	cout << "Usage:" << use << endl;
}

void basicCon(X509* cert) {
	//基本约束
	int crit = 0;
	char constraint[512] = { 0 };
	BASIC_CONSTRAINTS *bcons = NULL;
	bcons = (BASIC_CONSTRAINTS*)X509_get_ext_d2i(cert, NID_basic_constraints, &crit, NULL);
	if (!bcons->ca)
	{
		strcat_s(constraint, 512, "Subject Type=End Entity; ");
		strcat_s(constraint, 512, "Path Length Constraint=None");
	}
	else
	{
		char temp[128] = { 0 };
		sprintf_s(temp, 128, "Path Length Constraint=%d", bcons->pathlen);
		strcat_s(constraint, 512, "Subject Type=CA; ");
		strcat_s(constraint, 512, temp);
	}
	BASIC_CONSTRAINTS_free(bcons);
	cout << "Basic Constraint: " << constraint << endl;
}

void fingerPrint(X509* cert) {
	//指纹
	int SHA1LEN = 20;
	char *buf = new char[SHA1LEN];

	const EVP_MD *digest = EVP_sha1();
	unsigned len;

	int rc = X509_digest(cert, digest, (unsigned char*)buf, &len);
	if (rc == 0 || len != SHA1LEN) {
		cout << "fail" << endl;
	}

	char *strbuf = new char[2 * SHA1LEN + 1];
	hex_encode((unsigned char*)buf, strbuf, SHA1LEN);
	cout << "Fingerprint: "<< strbuf << endl;
}

void isValid(X509* cert) {
	//证书状态,任何>= 1的值都被认为是CA证书,而0不是CA证书
	int raw = X509_check_ca(cert);
	if (raw >= 1) {
		cout << "Valid Cert" << endl;
	}
	else {
		cout << "Invalid Cert" << endl;
	}
}

int main()
{
	OpenSSL_add_all_algorithms();
	
	char cert[MAX_LEGTH];

	unsigned char ca_der[MAX_LEGTH];
	unsigned long ca_der_len;
	X509 *ca = NULL;

	cout << "请输入证书地址" << endl;
	char *path = new char[80];
	cin.getline(path, 80);
	/* x509初始化 */
	sprintf(cert, path);
	my_load_cert(ca_der, &ca_der_len, cert, MAX_LEGTH);
	ca = der_to_x509(ca_der, ca_der_len);
	
	version(ca);
	serial(ca);
	signAlgo(ca);
	iss(ca);
	validTime(ca);
	sub(ca);
	pubKey(ca);
	subKeyHash(ca);
	keyUse(ca);
	basicCon(ca);
	fingerPrint(ca);

	return 0;
}

运行结果
openSSL解析证书及建立安全通道通信实验

心得体会

一开始觉得难点在于转格式,后来发现难点在于调用函数,对OpenSSL库函数的不了解对于调用解析造成困难。网上搜索到的解决方案并不适用于我,总显示“不允许使用不完整的类类型”,网上解释是头文件的定义没找到对应的cpp文件,我觉得可能是我安装或在vs中引入OpenSSL的过程有什么问题,因为OpenSSL包中app文件夹里的示例代码不报错,另外建立项目调用就有问题。

最后我放弃使用类指针调用返回属性值,改用get和set函数,对于想得到的属性值先输一个get_,然后vs会返回可得的所有函数,选中较相关的函数进行尝试,最后得到现在的逐项解析证书基本项的代码。

实验二

实验内容

利用 openssl 开源库(或其他开源实现),编写一个 c/s 应用要求客户端能输入任意消息,服务器端能将该消息显示,或者记录到文件中。

实验分析

基于OpenSSL的程序要遵循以下几个步骤:

  1. OpenSSL初始化

     OpenSSL_add_ssl_algorithms();
     SSL_load_error_strings();
     SSL_library_init();
    
  2. 选择会话协议

    在利用OpenSSL开始SSL会话之前,需要为客户端和服务器制定本次会话采用的协议,目前能够使用的协议包括TLSv1.0、SSLv2、SSLv3、SSLv2/v3。客户端和服务器必须使用相互兼容的协议,否则SSL会话将无法正常进行。

  3. 创建会话环境

    在OpenSSL中创建的SSL会话环境称为CTX,使用不同的协议会话,其环境也不一样的。申请SSL会话环境的OpenSSL函数是:SSL_CTX *SSL_CTX_new(SSL_METHOD *method);

    当SSL会话环境申请成功后,还要根据实际的需要设置CTX的属性,通常的设置是指定SSL握手阶段证书的验证方式和加载自己的证书。

     制定证书验证方式的函数是:
     int SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int(*verify_callback),int(X509_STORE_CTX *));
     
     为SSL会话环境加载CA证书的函数是:
     SSL_CTX_load_verify_location(SSL_CTX *ctx,const char *Cafile,const char *Capath);
     
     为SSL会话加载用户证书的函数是:
     SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,int type);
     
     为SSL会话加载用户私钥的函数是:
     SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,int type);
     
     在将证书和私钥加载到SSL会话环境之后,就可以调用下面的函数来验证私钥和证书是否相符
     int SSL_CTX_check_private_key(SSL_CTX *ctx);
    
  4. 建立SSL套接字

    SSL套接字是建立在普通的TCP套接字基础之上,在建立SSL套接字时可以使用下面的一些函数:

     SSL *SSl_new(SSL_CTX *ctx);
     //申请一个SSL套接字
     int SSL_set_fd(SSL *ssl,int fd);)
     //绑定读写套接字
     int SSL_set_rfd(SSL *ssl,int fd);
     //绑定只读套接字
     int SSL_set_wfd(SSL *ssl,int fd);
     //绑定只写套接字
    
  5. 完成SSL握手

    在成功创建SSL套接字后,客户端应使用函数SSL_connect( )来完成握手过程:int SSL_connect(SSL *ssl);

    而对服务器来讲,则应使用函数SSL_ accept ( )来完成握手过程:int SSL_accept(SSL *ssl);

    握手过程完成之后,询问通信双方的证书信息是可选的

     从SSL套接字中提取对方的证书信息,已被SSL验证过:
     X509 *SSL_get_peer_certificate(SSL *ssl);
     
     得到证书所用者的名字:
     X509_NAME *X509_get_subject_name(X509 *a);
    
  6. 进行数据传输

    当SSL握手完成之后,就可以进行安全的数据传输了,在数据传输阶段,需要使用SSL_read( )SSL_write( )来完成对套接字的读写操作:

     int SSL_read(SSL *ssl,void *buf,int num);
     int SSL_write(SSL *ssl,const void *buf,int num);
    
  7. 结束SSL通信

    当客户端和服务器之间的数据通信完成之后,调用下面的函数来释放已经申请的SSL资源:

     int SSL_shutdown(SSL *ssl);
     //关闭SSL套接字
     void SSl_free(SSL *ssl);
     //释放SSL套接字
     void SSL_CTX_free(SSL_CTX *ctx); 
     //释放SSL会话环境
    

实验代码

server.cpp

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <winsock.h>

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

using namespace std;
#define PORT 4433

extern "C"
{
#include <openssl/applink.c>
};

int main() {
	cout << "SERVER" << endl;

	OpenSSL_add_ssl_algorithms();
	SSL_load_error_strings();
	SSL_library_init();

	//选择会话协议
	const SSL_METHOD *meth;
	meth = SSLv23_server_method();

	//创建会话环境
	SSL_CTX *ctx;
	ctx = SSL_CTX_new(meth);
	if ((ctx) == NULL)exit(-1);

	//设置CTX的属性
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
	SSL_CTX_load_verify_locations(ctx, "./certs/ca/ca.crt", NULL);
	int ssl_server_cert = SSL_CTX_use_certificate_file(ctx, "./certs/server/server.crt", SSL_FILETYPE_PEM);
	if (ssl_server_cert <= 0) {
		ERR_print_errors_fp(stderr);
		exit(-2);
	}
	int ssl_server_pri = SSL_CTX_use_PrivateKey_file(ctx, "./certs/server/server.key", SSL_FILETYPE_PEM);
	if (ssl_server_pri <= 0)
	{
		ERR_print_errors_fp(stderr);
		exit(-3);
	}
	int check = SSL_CTX_check_private_key(ctx);
	if (!check) {
		printf("Private key does not match the certificate public key\n");
		exit(-4);
	}

	//开始TCP socket过程
	WSAData wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		cout << "WSAStartup error" << endl;
		return 0;
	}
	cout << "Begin TCP socket..." << endl;
	SOCKET listen_sd;
	listen_sd = socket(AF_INET, SOCK_STREAM, 0);
	if ((listen_sd) == INVALID_SOCKET) { 
		cout << "again" << endl;
		int er = WSAGetLastError();
		cout << er << endl;
		exit(-2);
	}

	struct sockaddr_in sa_serv;
	memset(&sa_serv, '\0', sizeof(sa_serv));
	sa_serv.sin_family = AF_INET;
	sa_serv.sin_addr.s_addr = INADDR_ANY;
	sa_serv.sin_port = htons(PORT);
	int err;
	err = bind(listen_sd, (struct sockaddr*)&sa_serv, sizeof(struct sockaddr_in));
	if (err == -1) { perror("bind"); exit(-2); }

	//接受TCP链接
	err = listen(listen_sd, 10);
	if (err == -1) { perror("listen"); exit(-2); }
	cout << "Listening...." << endl;
	cout << "Ready for Accept,Waitting..." << endl;

	int client_len = sizeof(struct sockaddr_in);
	struct sockaddr_in sa_cli;
	int sd = accept(listen_sd, (struct sockaddr*) &sa_cli, &client_len);
	if (sd == -1) { perror("accept"); exit(-2); }
	cout << "Get the Client" << endl;
	printf("Connection from %lx, port %x\n", sa_cli.sin_addr.s_addr, sa_cli.sin_port);

	//TCP连接已建立,进行服务端的SSL过程
	cout << "Begin server side SSL" << endl;
	SSL* ssl;
	ssl = SSL_new(ctx);
	if (ssl == NULL)exit(-1);
	SSL_set_fd(ssl, sd);
	err = SSL_accept(ssl);
	if (err == -1) { 
		cout << "Error:" << stderr << endl;
		ERR_print_errors_fp(stderr); 
		exit(-3); 
	}
	cout << "SSL_accept finished" << endl;	

	char buf[4096];
	err = SSL_read(ssl, buf, sizeof(buf) - 1);
	if (err == -1) { ERR_print_errors_fp(stderr); exit(-3); }
	buf[err] = '\0';
	printf("client:'%s'\n", buf);
	printf("server:");
	cout << buf << endl;

	//关闭
	int shut = SSL_shutdown(ssl);
	SSL_free(ssl);
	SSL_CTX_free(ctx);

	return 0;
}

client.cpp

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <cstdlib>
#include <winsock.h>

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

using namespace std;
#define PORT 4433
#define SERVER_ADDR "127.0.0.1"

extern "C"
{
#include <openssl/applink.c>
};

int main() {
	cout << "CLIENT" << endl;

	//初始化
	OpenSSL_add_ssl_algorithms();
	SSL_load_error_strings();
	SSL_library_init();

	//选择会话协议
	const SSL_METHOD *meth;
	meth = SSLv23_client_method();

	//创建会话环境
	SSL_CTX *ctx;
	ctx = SSL_CTX_new(meth);
	if ((ctx) == NULL) exit(-1);

	//设置CTX的属性
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
	SSL_CTX_load_verify_locations(ctx, "./certs/ca/ca.crt", NULL);
	int ssl_user_cert = SSL_CTX_use_certificate_file(ctx, "./certs/client/client.crt", SSL_FILETYPE_PEM);
	if (ssl_user_cert <= 0)
	{
		ERR_print_errors_fp(stderr);
		exit(-2);
	}
	int ssl_user_pri = SSL_CTX_use_PrivateKey_file(ctx, "./certs/client/client.key", SSL_FILETYPE_PEM);
	if (ssl_user_pri <= 0)
	{
		ERR_print_errors_fp(stderr);
		exit(-3);
	}
	int check = SSL_CTX_check_private_key(ctx);
	if (!check)
	{
		printf("Private key does not match the certificate public key\n");
		exit(-4);
	}

	struct sockaddr_in sa;
	memset(&sa, '\0', sizeof(sa));
	sa.sin_family = AF_INET;
	sa.sin_addr.s_addr = inet_addr(SERVER_ADDR);/* Server IP */
	sa.sin_port = htons(PORT);/* Server Port number */

	//初始化供进程调用的Winsock相关的dll
	WSAData wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
	{
		cout << "WSAStartup error" << endl;
		return 0;
	}

	cout << "Begin TCP Socket..." << endl;
	SOCKET sd;
	sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sd == INVALID_SOCKET) {
		cout << "again" << endl;
		int er = WSAGetLastError();
		cout << er << endl;
		exit(-2);
	}

	int err;
	err = connect(sd, (struct sockaddr*)&sa, sizeof(struct sockaddr));
	if (err == -1) { perror("connect"); exit(-2); }
	cout << "Get the server" << endl;

	//开始 SSL 握手过程
	cout << "Begin SSL negotiation" << endl;
	SSL* ssl = SSL_new(ctx);
	if (ssl == NULL) exit(-1);
	SSL_set_fd(ssl, sd);
	err = SSL_connect(ssl);
	if (err == -1) {
		cout << "Error:" << stderr << endl;
		ERR_print_errors_fp(stderr);
		exit(-3);
	}

	cout << "Begin SSL data exchange" << endl;
	cout << "client:" << "Hello world" << endl;
	err = SSL_write(ssl, "Hello world", strlen("Hello World!"));

	//关闭
	int shut = SSL_shutdown(ssl);
	SSL_free(ssl);
	SSL_CTX_free(ctx);

	system("pause");
	return 0;
}

运行结果
openSSL解析证书及建立安全通道通信实验

心得体会

在同一个solution下建立两个project,先运行server再运行client。整体运行脉络很清楚,基本上可以简化到五步:初始化→创建会话→建立SSL握手→数据传输→结束通信。另外在windows下进行SSL连接之前,还要初始化供进程调用的Winsock相关的dll,否则connect会出错。

运行时要注意的就是在vs2017下,设server项目为启动项目,运行后对client项目进行调试启动新项目,这样就可以同时运行server和client进行SSL握手。

相关标签: openSSL ASN1 SSL