没有读过前几篇文章的同学建议先阅读:
SSL从理论到实践(一)——密码学的相关概念
SSL从理论到实践(二)——SSL
SSL从理论到实践(三)——证书文件
SSL从理论到实践(四)——Keytool
要使用SSL,首先要生成证书。由于JDK自带的keytool工具默认不能生成BKS类型的密钥库,而Android客户端只支持BKS类型的密钥库,所以要先扩展keytool使其能够生成BKS密钥库。
1. BouncyCastle配置
1.1下载bcprov-ext-jdk15on-158.jar(截止2017/11/19日最新)
下载地址:http://www.bouncycastle.org/latest_releases.html
1.2将bcprov-ext-jdk15on-158.jar复制到 jdk_home\jre\lib\ext下
1.3在jdk_home\jre\lib\security\目录中找到 java.security 在其中增加一行(xx表示数字)
security.provider.xx=org.bouncycastle.jce.provider.BouncyCastleProvider
配置后如下图:
2.使用keytool工具生成密钥库、导出证书、导入信任库
2.1生成服务器端的密钥库kserver.keystore,采用默认的JKS类型
keytool -genkeypair -alias server -keystore kserver.keystore -validity 36500 -keyalg ec
(SSLSocket签名算法默认为DSA,Android6.0(API 23)以后KeyStore发生更改,不再支持DSA,但仍支持ECDSA。所以需要指定签名算法:-keyalg ec)
2.2从服务器端密钥库kserver.keystore中导出服务器证书
keytool -exportcert -alias server -file server.cer -keystore kserver.keystore
2.3将服务器端导出的证书导入到客户端信任密钥库tclient.bks中,客户端信任密钥库(tclient.bks)会自动生成,并且此时要特别指明信任密钥库是BKS类型
keytool -importcert -alias server -file server.cer -keystore tclient.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
2.4生成客户端密钥库kclient.bks
keytool -genkeypair -alias client -keystore kclient.bks -storetype BKS -validity 36500 -provider org.bouncycastle.jce.provider.BouncyCastleProvider -keyalg ec
2.5导出客户端证书
keytool -exportcert -alias client -file client.cer -keystore kclient.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
2.6将客户端导出的证书导入到服务器端信任密钥库tserver.keystore中,服务器端信任密钥库(tserver.keystore)会自动生成
keytool -importcert -v -alias client -file client.cer -keystore tserver.keystore
2.7将客户端密钥库kclient.bks转成IOS支持的pkcs12格式,会生成kclient.p12文件
keytool -importkeystore -srckeystore kclient.bks -srcstoretype bks -destkeystore kclient.p12 -deststoretype pkcs12
至此,我们已经生成了Java、Android端和IOS需要的密钥库和证书文件,接下来我们只需要将使用到的文件复制到项目中就可以了。
3.SSLSocket服务器端代码 (Java实现)
public class SocketServer {
private static final String SERVER_KEY_PASSWORD = "123456";
private static final String TRUST_KEY_PASSWORD = "123456";
private static final String SERVER_KEYSTORE_PATH = "src/kserver.keystore";
private static final String TRUST_KEYSTORE_PATH = "src/tserver.keystore";
private static final int PORT = 2345;//端口号
public static void main(String[] args) {
startServer();
}
private static void startServer() {
ServerSocket serverSocket = null;
try {
serverSocket = createServerSocket();
serverSocket.bind(new InetSocketAddress(PORT));
while (true) {
Socket socket = serverSocket.accept();
OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello, I am Server!".getBytes());
outputStream.flush();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
System.out.println("receive:" + new String(buffer,0,len));
try {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static ServerSocket createServerSocket() throws Exception {
SSLContext sslContext = SSLContext.getInstance("TLS");// 使用协议 TLS
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(SERVER_KEYSTORE_PATH),SERVER_KEY_PASSWORD.toCharArray());
keyManagerFactory.init(keyStore, SERVER_KEY_PASSWORD.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
KeyStore trustkeyStore = KeyStore.getInstance("JKS");
trustkeyStore.load(new FileInputStream(TRUST_KEYSTORE_PATH),TRUST_KEY_PASSWORD.toCharArray());
trustManagerFactory.init(trustkeyStore);
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
SSLServerSocket sslServerSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket();
if (sslServerSocket != null) {
sslServerSocket.setNeedClientAuth(true);// 需要双向认证
}
return sslServerSocket;
}
}
4.SSLSocket客户端代码(Android实现,记得添加权限< uses-permission android:name = “android.permission.INTERNET” />)
public class MainActivity extends Activity {
private static final String CLIENT_KEY_PASSWORD = "123456";
private static final String TRUST_KEY_PASSWORD = "123456";
private static final String CLIENT_KEYSTORE_PATH = "kclient.bks";
private static final String TRUST_KEYSTORE_PATH = "tclient.bks";
private static final String IP = "192.168.99.52";//ip和端口号根据服务器不同自行修改
private static final int PORT = 2345;//ip和端口号根据服务器不同自行修改
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread() {
public void run() {
startSocket();
};
}.start();
}
public void startSocket(){
byte[] buffer = new byte[1024];
Socket socket = null;
InputStream reader = null;
OutputStream writer = null;
try {
socket = createSocket();
socket.setReuseAddress(true);
socket.setSoLinger(true, 0); //socket关闭时,立即释放资源
socket.setTcpNoDelay(true); //数据不作缓冲,立即发送
socket.setTrafficClass(0x04 | 0x10); //高可靠性和最小延迟传输
socket.setKeepAlive(true);
socket.setSoTimeout(10000);
socket.connect(new InetSocketAddress(IP, PORT), 3000);
reader = socket.getInputStream();
int len = reader.read(buffer);
Log.i("read", "receive:" + new String(buffer,0,len));
writer = socket.getOutputStream();
writer.write("Hello, I am Client!".getBytes());
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
if (reader != null) {
reader.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private Socket createSocket() throws Exception {
//取得TLS协议的SSLContext实例
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(getResources().getAssets().open(CLIENT_KEYSTORE_PATH), CLIENT_KEY_PASSWORD.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(clientKeyStore, CLIENT_KEY_PASSWORD.toCharArray());
//取得BKS类型的密钥库实例,这里特别注意:手机只支持BKS密钥库,不支持Java默认的JKS密钥库
KeyStore trustKeyStore = KeyStore.getInstance("BKS");
trustKeyStore.load(getResources().getAssets().open(TRUST_KEYSTORE_PATH), TRUST_KEY_PASSWORD.toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustKeyStore);
//初始化SSLContext实例
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
//以下两步获得SSLSocket实例
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return sslSocketFactory.createSocket();
}
}
5.SSLSocket客户端代码(IOS实现——由热心的IOS同事生浩提供)
#import "GCDAsyncSocket.h"
@interface ViewController ()<GCDAsyncSocketDelegate>
@property (nonatomic,strong)GCDAsyncSocket *socket;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_socket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[_socket connectToHost:@"192.168.99.52" onPort:2345 error:nil];
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"接收到数据:%@",str);
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
NSLog(@"发送 hello 成功");
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
if (err) {
NSLog(@"%@",err.description);
}
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(@"进入SSL验证");
[self starSSL:sock];
}
- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL))completionHandler
{
// // SSL 证书
NSLog(@"进入 didReceiveTrust");
NSString *certFilePath1 = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"];
NSData *certData1 = [NSData dataWithContentsOfFile:certFilePath1];
OSStatus status = -1;
SecTrustResultType result = kSecTrustResultDeny;
if(certData1)
{
SecCertificateRef cert1;
cert1 = SecCertificateCreateWithData(NULL, (__bridge_retained CFDataRef) certData1);
// 设置证书用于验证
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)[NSArray arrayWithObjects:(__bridge id)cert1,nil]);
// 验证服务器证书和本地证书是否匹配
status = SecTrustEvaluate(trust, &result);
}
else
{
NSLog(@"local certificates could not be loaded");
completionHandler(NO);
}
if ((status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)))
{
//成功通过验证,证书可信
completionHandler(YES);
}
else
{
CFArrayRef arrayRefTrust = SecTrustCopyProperties(trust);
NSLog(@"error in connection occured\n%@", arrayRefTrust);
completionHandler(NO);
}
}
- (void)socketDidSecure:(GCDAsyncSocket *)sock
{
NSLog(@"握手成功");
NSString *dataStr = @"hello server";
NSData *data = [dataStr dataUsingEncoding:NSUTF8StringEncoding];
[_socket readDataWithTimeout:-1 tag:0];
[_socket writeData:data withTimeout:-1 tag:0];
}
- (void)starSSL:(GCDAsyncSocket*)sock {
/**********单向验证***************/
// 配置 SSL/TLS 设置信息
// NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3];
// //允许自签名证书手动验证
// [settings setObject:@YES forKey:GCDAsyncSocketManuallyEvaluateTrust];
// 如果不是自签名证书,而是那种权威证书颁发机构注册申请的证书
// 那么这个settings字典可不传。
// [sock startTLS:settings]; // 开始SSL握手
/**********双向验证***************/
NSMutableDictionary *sslSettings = [[NSMutableDictionary alloc] init];
// SSL 证书
NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"kclient" ofType:@"p12"]];
CFDataRef inPKCS12Data = (CFDataRef)CFBridgingRetain(pkcs12data);
// c语言字符串
CFStringRef password = CFSTR("123456");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
CFRelease(options);
CFRelease(password);
if(securityError == errSecSuccess)
NSLog(@"Success opening p12 certificate.");
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef myIdent = (SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
SecIdentityRef certArray[1] = { myIdent };
CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
[sslSettings setObject:(id)CFBridgingRelease(myCerts) forKey:(NSString *)kCFStreamSSLCertificates];
[sslSettings setObject:[[NSArray alloc] initWithObjects:(__bridge id)(myIdent), nil] forKey:GCDAsyncSocketSSLCertificates];
// SSLProtocol
[sslSettings setObject:@YES forKey:GCDAsyncSocketManuallyEvaluateTrust];
[sslSettings setObject:@0 forKey:GCDAsyncSocketSSLProtocolVersionMax];
// 此方法是GCDScoket 设置ssl验证的唯一方法,需要穿字典
[sock startTLS:sslSettings];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
6.使用keytool和openssl生成C需要的密钥文件和证书文件
C语言需要导入的私钥和证书是pem格式的,而我们之前生成的文件都是der格式的,所以我们需要先将der格式转换成pem格式,然后从pem格式的文件中拷出私钥和证书。
6.1将服务器密钥库kserver.keystore转成pkcs12格式,生成kserver.p12文件
keytool -importkeystore -srckeystore kserver.keystore -srcstorepass 123456 -destkeystore kserver.p12 -deststoretype pkcs12 -deststorepass 123456
(123456是密钥库的密码,请对照自己的密码进行修改)
6.2利用openssl将kserver.p12文件的格式由der转成pem,会生成kserver.pem文件
openssl pkcs12 -in kserver.p12 -out kserver.pem -passin pass:123456 -passout pass:123456
6.3新建文件server.key和server.crt,将kserver.pem文件中的私钥信息复制到server.key中,将kserver.pem文件中的证书信息复制到server.crt中。其中私钥和证书信息如下:
6.4利用openssl将kclient.p12文件(之前生成的IOS密钥库)的格式由der转成pem,会生成kclient.pem文件
openssl pkcs12 -in kclient.p12 -out kclient.pem -passin pass:123456 -passout pass:123456
6.5新建文件client.key和client.crt,将kclient.pem文件中的私钥信息复制到client.key中,将kclient.pem文件中的证书信息复制到client.crt中(过程同6.3)
至此,C语言需要的四个文件(server.key、server.crt、client.key和client.crt)我们也已经生成完成。
7.SSLSocket头文件“ssl_common.h”
#ifndef _SSL_COMMON_H_
#define _SSL_COMMON_H_
#define FAIL -1
#define MAXBUF 1024
#define PRIKEY_PASSWD "123456" //prikey password
#define ALGO_TYPE "ALL" //algorithm type
#define SERVER_CERT "certs/server.crt" //vbox cert file
#define SERVER_KEYF "certs/server.key" //vbox key file
#define CLIENT_CERT "certs/client.crt" //client cert file
#define CLIENT_KEYF "certs/client.key" //client key file
#endif
8.SSLSocket服务端代码(C实现)
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "ssl_common.h"
/*---------------------------------------------------------------------*/
/*--- open_listener - create server socket ---*/
/*---------------------------------------------------------------------*/
int open_listener(int port)
{
int sd;
struct sockaddr_in addr;
sd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
{
perror("can't bind port");
abort();
}
if ( listen(sd, 10) != 0 )
{
perror("Can't configure listening port");
abort();
}
return sd;
}
/*---------------------------------------------------------------------*/
/*--- init_server_ctx - initialize SSL server and create contexts ---*/
/*---------------------------------------------------------------------*/
SSL_CTX* init_server_ctx(void)
{
SSL_METHOD *method;
SSL_CTX *ctx;
SSL_library_init(); /* init algorithms library */
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
OpenSSL_add_all_ciphers();
method = SSLv23_server_method(); /* create new server-method instance */
//method = TLSv1_server_method();
ctx = SSL_CTX_new(method); /* screate new context from method */
if ( ctx == NULL )
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
/*---------------------------------------------------------------------*/
/*--- verify_callback - SSL_CTX_set_verify callback function. ---*/
/*---------------------------------------------------------------------*/
int verify_callback(int ok, X509_STORE_CTX *store)
{
char data[256];
if (ok)
{
fprintf(stderr, "verify_callback\n{\n");
X509 *cert = X509_STORE_CTX_get_current_cert(store);
int depth = X509_STORE_CTX_get_error_depth(store);
int err = X509_STORE_CTX_get_error(store);
fprintf(stderr, "certificate at depth: %i\n", depth);
memset(data, 0, sizeof(data));
X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
fprintf(stderr, "issuer = %s\n", data);
X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
fprintf(stderr, "subject = %s\n", data);
fprintf(stderr, "error status: %i:%s\n}\n", err, X509_verify_cert_error_string(err));
}
return ok;
}
/*---------------------------------------------------------------------*/
/*--- load_certificates - load from files. ---*/
/*---------------------------------------------------------------------*/
void load_certificates(SSL_CTX* ctx, char* CaFile, char* CertFile, char* KeyFile)
{
#if 1
/* set maximum depth for the certificate chain */
//SSL_CTX_set_verify_depth(ctx, 1);
/* set voluntary certification mode*/
//SSL_VERIFY_NONE SSL_VERIFY_PEER
//SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
/* set mandatory certification mode*/
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);
/* load CA certificate file */
if (SSL_CTX_load_verify_locations(ctx, CaFile, NULL) <=0)
{
ERR_print_errors_fp(stderr);
abort();
}
#endif
/* set the local certificate from CertFile */
if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
/* set server private key password */
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)PRIKEY_PASSWD);
/* set the private key from KeyFile (may be the same as CertFile) */
if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
/* verify private key */
if ( !SSL_CTX_check_private_key(ctx) )
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
/* set SSL cipher type */
SSL_CTX_set_cipher_list(ctx, ALGO_TYPE);
}
/*---------------------------------------------------------------------*/
/*--- show_certs_info - print out certificates. ---*/
/*---------------------------------------------------------------------*/
void show_certs_info(SSL* ssl)
{
X509 *cert;
char *line;
/* Get connect use algorithm type */
//printf("SSL connection using %s\n", SSL_get_cipher(ssl));
/* Get certificates (if available) */
cert = SSL_get_peer_certificate(ssl);
if ( cert != NULL )
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line);
X509_free(cert);
}
else
printf("No certificates.\n");
}
/*---------------------------------------------------------------------*/
/*--- server_handler - SSL servlet ---*/
/*---------------------------------------------------------------------*/
void server_handler(SSL* ssl) /* Serve the connection -- threadable */
{
char buf[1024];
char reply[1280];
int sd, bytes;
if (FAIL == SSL_accept(ssl)) /* do SSL-protocol accept */
{
printf("Accept Fail\n");
ERR_print_errors_fp(stderr);
}
else
{
show_certs_info(ssl); /* get any certificates */
bytes = SSL_read(ssl, buf, sizeof(buf)-1); /* get request */
if (FAIL == bytes)
{
printf("SSL_read Fail\n");
ERR_print_errors_fp(stderr);
}
buf[bytes] = '\0';
printf("Client msg: \"%s\"\n", buf);
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
snprintf(reply, sizeof(reply), "Hello,I am server!", buf);
bytes = SSL_write(ssl, reply, strlen(reply)); /* send reply */
if (FAIL == bytes)
{
printf("SSL_write Fail\n");
ERR_print_errors_fp(stderr);
}
}
sd = SSL_get_fd(ssl); /* get socket connection */
//SSL_shutdown(ssl); /* shutdown SSL link */
SSL_free(ssl); /* release SSL state */
close(sd); /* close connection */
}
/*---------------------------------------------------------------------*/
/*--- main - create SSL socket server. ---*/
/*---------------------------------------------------------------------*/
int main(int count, char *strings[])
{
SSL_CTX *ctx;
int server;
char *portnum;
if ( count != 2 )
{
printf("Usage: %s <portnum>\n", strings[0]);
exit(0);
}
portnum = strings[1];
ctx = init_server_ctx(); /* initialize SSL */
load_certificates(ctx, CLIENT_CERT, SERVER_CERT, SERVER_KEYF); /* load certs */
server = open_listener(atoi(portnum)); /* create server socket */
while (1)
{ struct sockaddr_in addr;
int len = sizeof(addr);
SSL *ssl;
/* accept connection as usual */
int client = accept(server, (struct sockaddr*)&addr, &len);
printf("Connection from %d, port %d\n", addr.sin_addr.s_addr, addr.sin_port);
ssl = SSL_new(ctx); /* get new SSL state with context */
SSL_set_fd(ssl, client); /* set connection socket to SSL state */
server_handler(ssl); /* service connection */
}
close(server); /* close server socket */
SSL_CTX_free(ctx); /* release context */
return 0;
}
9.SSLSocket客户端代码(C实现)
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>
#include <string.h>
#include <sys/socket.h>
#include <resolv.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "ssl_common.h"
/*---------------------------------------------------------------------*/
/*--- open_connection - create socket and connect to server. ---*/
/*---------------------------------------------------------------------*/
int open_connection(const char *hostname, int port)
{
int sd;
struct hostent *host;
struct sockaddr_in addr;
if ( (host = gethostbyname(hostname)) == NULL )
{
perror(hostname);
abort();
}
sd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = *(long*)(host->h_addr);
if ( connect(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 )
{
close(sd);
perror(hostname);
abort();
}
return sd;
}
/*---------------------------------------------------------------------*/
/*--- init_client_ctx - initialize the SSL engine. ---*/
/*---------------------------------------------------------------------*/
SSL_CTX* init_client_ctx(void)
{
SSL_METHOD *method;
SSL_CTX *ctx;
SSL_library_init(); /* init algorithms library */
OpenSSL_add_all_algorithms(); /* load & register all cryptos, etc. */
SSL_load_error_strings(); /* load all error messages */
method = SSLv23_client_method(); /* create new server-method instance */
//method = TLSv1_client_method();
ctx = SSL_CTX_new(method); /* create new context from method */
if ( ctx == NULL )
{
ERR_print_errors_fp(stderr);
abort();
}
return ctx;
}
/*---------------------------------------------------------------------*/
/*--- verify_callback - SSL_CTX_set_verify callback function. ---*/
/*---------------------------------------------------------------------*/
int verify_callback(int ok, X509_STORE_CTX *store)
{
char data[256];
if (ok)
{
fprintf(stderr, "verify_callback\n{\n");
X509 *cert = X509_STORE_CTX_get_current_cert(store);
int depth = X509_STORE_CTX_get_error_depth(store);
int err = X509_STORE_CTX_get_error(store);
fprintf(stderr, "certificate at depth: %i\n", depth);
memset(data, 0, sizeof(data));
X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
fprintf(stderr, "issuer = %s\n", data);
X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
fprintf(stderr, "subject = %s\n", data);
fprintf(stderr, "error status: %i:%s\n}\n", err, X509_verify_cert_error_string(err));
}
return ok;
}
/*---------------------------------------------------------------------*/
/*--- load_certificates - load from files. ---*/
/*---------------------------------------------------------------------*/
void load_certificates(SSL_CTX* ctx, char* CaFile, char* CertFile, char* KeyFile)
{
#if 1
/* set maximum depth for the certificate chain */
//SSL_CTX_set_verify_depth(ctx, 1);
/* set verify mode*/
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
/* load CA certificate file */
if (SSL_CTX_load_verify_locations(ctx, CaFile, NULL) <=0)
{
ERR_print_errors_fp(stderr);
abort();
}
#endif
/* set the local certificate from CertFile */
if ( SSL_CTX_use_certificate_file(ctx, CertFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
/* set server private key password */
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)PRIKEY_PASSWD);
/* set the private key from KeyFile (may be the same as CertFile) */
if ( SSL_CTX_use_PrivateKey_file(ctx, KeyFile, SSL_FILETYPE_PEM) <= 0 )
{
ERR_print_errors_fp(stderr);
abort();
}
/* verify private key */
if ( !SSL_CTX_check_private_key(ctx) )
{
fprintf(stderr, "Private key does not match the public certificate\n");
abort();
}
/* set SSL cipher type */
SSL_CTX_set_cipher_list(ctx, ALGO_TYPE);
}
/*---------------------------------------------------------------------*/
/*--- show_certs_info - print out the certificates. ---*/
/*---------------------------------------------------------------------*/
void show_certs_info(SSL* ssl)
{ X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl); /* get the server's certificate */
if ( cert != NULL )
{
printf("Server certificates:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("Subject: %s\n", line);
free(line); /* free the malloc'ed string */
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("Issuer: %s\n", line);
free(line); /* free the malloc'ed string */
X509_free(cert); /* free the malloc'ed certificate copy */
}
else
printf("No certificates.\n");
}
/*---------------------------------------------------------------------*/
/*--- main - create SSL context and connect ---*/
/*---------------------------------------------------------------------*/
int main(int count, char *strings[])
{
SSL_CTX *ctx;
int server;
SSL *ssl;
char buf[1024];
int bytes;
char *hostname, *portnum;
if ( count != 3 )
{
printf("usage: %s <hostname> <portnum>\n", strings[0]);
exit(0);
}
hostname = strings[1];
portnum = strings[2];
ctx = init_client_ctx(); /* initialize SSL */
load_certificates(ctx, SERVER_CERT, CLIENT_CERT, CLIENT_KEYF); /* load certs */
server = open_connection(hostname, atoi(portnum));
ssl = SSL_new(ctx); /* create new SSL connection state */
SSL_set_fd(ssl, server); /* attach the socket descriptor */
if ( SSL_connect(ssl) == FAIL ) /* perform the connection */
{
ERR_print_errors_fp(stderr);
}
else
{
char *msg = "Hi! I am Client!";
printf("Connected with %s encryption\n", SSL_get_cipher(ssl));
show_certs_info(ssl); /* get any certs */
SSL_write(ssl, msg, strlen(msg)); /* encrypt & send message */
memset(buf, 0, sizeof(buf));
bytes = SSL_read(ssl, buf, sizeof(buf)-1); /* get reply & decrypt */
buf[bytes] = '\0';
printf("Server msg: \"%s\"\n", buf);
SSL_shutdown(ssl); /* shutdown SSL link */
SSL_free(ssl); /* release connection state */
}
close(server); /* close socket */
SSL_CTX_free(ctx); /* release context */
return 0;
}
参考
1.SSL协议详解
2.让SSL/TLS协议流行起来:深度解读SSL/TLS实现
3.使用wireshark观察SSL/TLS握手过程–双向认证/单向认证
4.Android 6.0 SSL通信
5.SSL/TLS的原理以及互联网究竟是如何工作的(1-5)
6.JDK 中的证书生成和管理工具 keytool
7.那些证书相关的玩意儿(SSL,X.509,PEM,DER,CRT,CER,KEY,CSR,P12等)