ssl双向验证
程序员文章站
2022-04-23 11:55:11
...
一般情况下ssl加密连接都只是单向认证,是客户端验证服务端的证书。例如常见的web服务,是客户端验证服务端的证书,防止遇到假冒的网站,正规的网站都有权威机构签名的证书,浏览器会帮助用户校验服务端的证书,如果证书是可信的,说明网站是可信的可如果证书不可信,浏览器会有提示,提示一般在地址栏前面有个锁形符号。
服务端在发送完server hello后将自己的证书发送给客户端,客户端此时可选的验证服务端的证书。客户端虽然是可选验证,但是连接仍然是加密连接,只是没有验证连接对端的身份。客户端用服务端证书的公钥加密一个预主**发送给服务端,以供双方后续产生加***。
双向认证是在建立一个加密连接时,双方互相验证对方的身份,此时在上述一般情况下ssl加密连接的基础上,客户端也要提供自己的证书以供服务端验证,如果证书不正确,服务端会断开连接。双向认证时双方可以使用**协商算法协商出一个预主**,供后续产生加***。
服务端的代码如下:
int loadCertFile(SSL_CTX* ctx, const char* CertFile, const char* KeyFile, const char* caFile)
{
if(!ctx)
{
return errcode;
}
// load cert
if(CertFile && SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0)
{
return errcode;
}
// load private key
if(KeyFile && SSL_CTX_use_PrivateKey_file(ctx,KeyFile, SSL_FILETYPE_PEM) <= 0)
{
return errcode;
}
// check private key
if(CertFile && KeyFile && !SSL_CTX_check_private_key(ctx))
{
return errcode;
}
// load ca file
if(caFile && !SSL_CTX_load_verify_locations(ctx, caFile, 0))
{
return errcode;
}
return SEC_OK;
}
// client
int openConnection(const char* ipAddr, int port)
{
int sd;
struct sockaddr_in addr;
LOG_INFO("openConnection -->> ipAddr:%s; port:%d\n", ipAddr, port);
sd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if(inet_pton(AF_INET, ipAddr, &addr.sin_addr) <= 0)
{
return errcode;
}
if(connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
{
close(sd);
return errcode;
}
return sd;
}
int rv = 0;
SSL_CTX* ctx;
SSL *ssl;
int listenfd, connfd;
struct sockaddr_in clientaddr;
SSL_library_init();
ctx = initServerSslCtx();
rv = loadCertFile(ctx, pszServerCert, pszServerKey, pszServerCA);
if(rv)
{
return rv;
}
SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); // 设置验证对方的证书
listenfd = openListener(atoi(nPort)); // 打开监听端口
connfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len); //接受连接
ssl = SSL_new(ctx);
SSL_set_accept_state(ssl);
SSL_set_fd(ssl, connfd);
rv = SSL_accept(ssl);
if(rv != 1)
{
return ERRCODE;
}
建立ssl连接后就可以使用SSL_write和SSL_read进行读写操作了。
客户端的代码如下:
nt openConnection(const char* ipAddr, int port)
{
int sd;
struct sockaddr_in addr;
LOG_INFO("openConnection -->> ipAddr:%s; port:%d\n", ipAddr, port);
sd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if(inet_pton(AF_INET, ipAddr, &addr.sin_addr) <= 0)
{
return errcode;
}
if(connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0)
{
close(sd);
return errcode;
}
return sd;
}
SSL_CTX* initCliSslCtx(void)
{
const SSL_METHOD *method;
SSL_CTX *ctx;
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
method = SSLv23_client_method();
ctx = SSL_CTX_new(method);
if(ctx == NULL)
{
return NULL;
}
return ctx;
}
SSL* getCliSsl(void*phSession, int sockfd)
{
int rv = 0;
SSL_CTX *ssl_ctx;
SSL *ssl;
char recvline[1];
ssl_ctx = initCliSslCtx();
if(!ssl_ctx)
{
return NULL;
}
SSL_CTX_set_verify(ssl_ctx,SSL_VERIFY_PEER,NULL);
rv = loadCertFile(ssl_ctx, pszClientCert, pszClientKey, pszClientCA);
if(rv)
{
return NULL;
}
ssl = SSL_new(ssl_ctx); // create new SSL connection state
if(!ssl)
{
return NULL;
}
SSL_set_connect_state(ssl);
SSL_set_fd(ssl, sockfd); // attache socet descriptor
if(SSL_connect(ssl) != 1) // perform the connection
{
return NULL;
}
SSL_read(ssl, recvline, 0);
SSL_CTX_free(ssl_ctx);
return ssl;
}
int sockfd;
SSL* ssl;
sockfd = openConnection(ipaddr, atoi(port));
ssl = getCliSsl(phSession, sockfd);
建立ssl连接后就可以使用SSL_write和SSL_read进行读写操作了。
具体工程代码可参考github仓库:https://github.com/liyakai/toy_server