什么是Netty?
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
Netty实现双向认证SSL连接的解决方案
双向证书认证的双方称为client和server,首先为client和server生成证书。由于仅仅是自己学习使用,因此可以在本地自建一个CA,然后用CA的证书分别签发client和server的证书。CA的创建和签发使用OpenSSL。
在windows环境上安装OpenSSL,然后依据OpenSSL目录下的openssl.cnf中[ CA_default ]的配置创建相应的文件夹和文件
[html]
demoCA/—-CA的根目录
|–newcerts/—-CA签发出去的证书
|–private/—-CA自己的私钥,默认名称是cakey.pem
|–serial—-存放证书序列号的文件
|–index.txt—-签发过的证书的记录,文本文件
serial这个文件中可以初始写入一行记录,包含两个字符01,表示下一个签发的证书采用的序列号是01
接下来生成CA自己的公私钥(public/private key),生成证书签名请求(CSR, Certificate Signing Request)文件并对该请求进行自签名
在openssl的根目录下运行
[html]
opensslgenrsa-out./demoCA/private/cakey.pem2048
genrsa —- 同时生成public key和private key
很多人将genrsa解释为只生成private key,这是不对的。可以用下面的命令从文件中解出公钥
[html]
opensslrsa-incakey.pem-pubout>capublickey.pub
注意最后的数字2048表示生成的RSA公私钥的长度
JDK7中对证书检查要求公钥的长度最少为1024位,否则会抛出异常
Java.security.cert.CertPathValidatorException: Algorithm constraints check failed
该长度限制是可以配置的,配置文件路径是JAVA_HOME/jre/lib/security/java.security
jdk.certpath.disabledAlgorithms=MD2, RSA keySize < 1024
然后用上面生成的公私钥文件创建一个证书签名请求文件
openssl req -new -key ./demoCA/private/cakey.pem -out careq.pem
req —- 创建CSR或者证书
-key —- openssl从这个文件中读取private key
careq.pem的内容格式是
[html]
—–BEGINCERTIFICATEREQUEST—–
MIICnzCCAYcCAQAwWjELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlpKMQswCQYDVQQH
……
ZYu4AZp0VzqnQzCTeYTbC+AsA0RrPVjr95Il46AHvhq2JQpFw8DhrS8Ja1VburI4
ngFK
—–ENDCERTIFICATEREQUEST—–
最后将该请求文件给CA机构做签名,但我们现在是想在本地建CA,因此自己对该文件进行自签名即可。
[html]
opensslca-selfsign-incareq.pem-outcacert.pem
其实,上面生成CSR然后做自签名的两个步骤可合并到一步完成
[html]
opensslreq-new-x509-key./demoCA/private/cakey.pem-outcacert.pem
至此,我们已经建立了自己的CA,接下去来分别签发client和server的证书。
以创建client的证书为例。由于jdk自带的keytool工具可以方便的创建key store和公私钥,因此公私钥和csr的创建直接使用keytool
key store和trust store分别对应于ssl握手证书认证中自己的证书和自己所信任的证书列表,二者的文件格式相同,不同之处是key store里面包含ssl握手一方的公私钥和证书,trust store里面包含ssl握手一方所信任的证书,一般没有这些证书所对应的私钥
生成client的keystore 和key pair
[html]
keytool-genkey-aliasclient-keyalgRSA-keystoreclient.keystore-keysize2048
生成csr
[html]
keytool-certreq-aliasclient-keystoreclient.keystore-fileclient.csr
用本地CA对该csr签名
client证书中我们想添加证书的一项扩展,比如client id,用来区分client的身份,因此需要额外的一份扩展文件client.cnf,内容如下
[html]
[v3_req]
1.2.3.412=ASN1:UTF8String:0000001444
可以将该csr和client.cnf文件拷贝到openssl根目录下,运行
[html]
opensslca-inclient.csr-outclient.pem-config./openssl.cnf-extensionsv3_req-extfileclient.cnf
将签过名的client.pem导入到keystore文件中
在导入之前,需要先将CA的证书导入keystore文件
[html]
keytool-keystoreclient.keystore-importcert-aliasCA-filecacert.pem
然后导入client自己的证书。注意alias是client,与生产keystore和key pair的必须匹配
[html]
keytool-keystoreclient.keystore-importcert-aliasclient-fileclient.pem
keystore文件内容的查看可以使用
keytool -list -v -keystore client.keystore
或者使用可视化工具KeyStore Explorer查看
[html]
<spanstyle=”white-space:pre”></span>keytool-import-aliascacert-filecacert.pem-keystoreclienttruststore.keystore
由于clienttruststore.keystore文件尚不存在,此命令首先创建该文件并将CA的证书导入该trust store
server的证书和key store和trust store可类似创建
[html]
keytool-genkey-aliasserver-keyalgRSA-keystoreserver.keystore-keysize2048
keytool-certreq-aliasserver-keystoreserver.keystore-fileserver.csr
opensslca-inserver.csr-outserver.pem-config./openssl.cnf
keytool-keystoreserver.keystore-importcert-aliasCA-filecacert.pem
keytool-keystoreserver.keystore-importcert-aliasserver-fileserver.pem
keytool-import-aliasca-filecacert.pem-keystoreservertruststore.keystore
由于netty 5现在只有alpha版本,因此保险起见使用4.0.24.final版本的netty。
netty的SSLContext提供了newClientContext来为client创建ssl context,但查看其源码未发现能支持双向认证,即client端的ssl context只接收一个trust store,而不能指定自己的证书以供server端校验。仿照netty example下的securechat的ssl实现但做了修改
首先创建一个能提供client和server的ssl context的工具类,分别加载server和client的key store和trust store里面的证书
[java]
publicclassSslContextFactory
{
privatestaticfinalStringPROTOCOL=“TLS”;//TODO:whichprotocolswillbeadopted?
privatestaticfinalSSLContextSERVER_CONTEXT;
privatestaticfinalSSLContextCLIENT_CONTEXT;
static
{
SSLContextserverContext=null;
SSLContextclientContext=null;
StringkeyStorePassword=“aerohive”;
try
{
KeyStoreks=KeyStore.getInstance(“JKS”);
ks.load(SslContextFactory.class.getClassLoader().getResourceAsStream(“cert\\server.keystore”),keyStorePassword.toCharArray());
//Setupkeymanagerfactorytouseourkeystore
KeyManagerFactorykmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks,keyStorePassword.toCharArray());
//truststore
KeyStorets=KeyStore.getInstance(“JKS”);
ts.load(SslContextFactory.class.getClassLoader().getResourceAsStream(“cert\\servertruststore.keystore”),keyStorePassword.toCharArray());
//setuptrustmanagerfactorytouseourtruststore
TrustManagerFactorytmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
//InitializetheSSLContexttoworkwithourkeymanagers.
serverContext=SSLContext.getInstance(PROTOCOL);
serverContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(),null);
}catch(Exceptione)
{
thrownewError(“Failedtoinitializetheserver-sideSSLContext”,e);
}
try
{
//keystore
KeyStoreks=KeyStore.getInstance(“JKS”);
ks.load(SslContextFactory.class.getClassLoader().getResourceAsStream(“cert\\client.keystore”),keyStorePassword.toCharArray());
//Setupkeymanagerfactorytouseourkeystore
KeyManagerFactorykmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks,keyStorePassword.toCharArray());
//truststore
KeyStorets=KeyStore.getInstance(“JKS”);
ts.load(SslContextFactory.class.getClassLoader().getResourceAsStream(“cert\\clienttruststore.keystore”),keyStorePassword.toCharArray());
//setuptrustmanagerfactorytouseourtruststore
TrustManagerFactorytmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
clientContext=SSLContext.getInstance(PROTOCOL);
clientContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(),null);
}catch(Exceptione)
{
thrownewError(“Failedtoinitializetheclient-sideSSLContext”,e);
}
SERVER_CONTEXT=serverContext;
CLIENT_CONTEXT=clientContext;
}
publicstaticSSLContextgetServerContext()
{
returnSERVER_CONTEXT;
}
publicstaticSSLContextgetClientContext()
{
returnCLIENT_CONTEXT;
}
……
}
io.netty.example.securechat.SecureChatClientInitializer类的构造器接收一个io.netty.handler.ssl.SslContext类型的对象,这个SslContext的对象最终被用来创建SslHandler,而上面factory产生的是javax.NET.ssl.SSLContext的对象,因此可以做改动如下
[java]
publicclassClientInitializerextendsChannelInitializer
{
privatefinal.ssl.SSLContextsslCtx;
publicClientInitializer(.ssl.SSLContextsslCtx)
{
this.sslCtx=sslCtx;
}
@Override
publicvoidinitChannel(SocketChannelch)throwsException
{
ChannelPipelinepipeline=ch.pipeline();
SSLEnginesslEngine=sslCtx.createSSLEngine(Client.HOST,Client.PORT);
sslEngine.setUseClientMode(true);
pipeline.addLast(newSslHandler(sslEngine));
//OntopoftheSSLhandler,addthetextlinecodec.
pipeline.addLast(newDelimiterBasedFrameDecoder(8192,Delimiters.lineDelimiter()));
pipeline.addLast(newStringDecoder());
pipeline.addLast(newStringEncoder());
//andthenbusinesslogic.
pipeline.addLast(newClientHandler());
}
}
最后添加jvm参数
[html]
-D.debug=ssl,handshake
SSL证书采用了技术含量比较高的加密技术。日后GDCA()将会持续为大家推荐更多关于SSL证书的技术知识。让大家正确认识SSL证书,快速无误部署HTTPS安全协议。更多资讯,请关注GDCA。
猜你喜欢