Netty中使用SSL 双向认证(包括证书生成)

转载:https://blog.csdn.net/u013399759/article/details/53325001

1. 前期准备工作

双向证书认证的双方称为client和server,首先为client和server生成证书。由于仅仅是自己学习使用,因此可以在本地自建一个CA,然后用CA的证书分别签发client和server的证书。CA的创建和签发使用OpenSSL。
在windows环境上安装OpenSSL,然后依据OpenSSL目录下的openssl.cnf中[ CA_default ]的配置创建相应的文件夹和文件

demoCA/ —- CA的根目录
|– newcerts/—- CA签发出去的证书
|– private/ —- CA自己的私钥,默认名称是cakey.pem
|– serial —- 存放证书序列号的文件
|– index.txt —- 签发过的证书的记录,文本文件

serial这个文件中可以初始写入一行记录,包含两个字符01,表示下一个签发的证书采用的序列号是01
接下来生成CA自己的公私钥(public/private key),生成证书签名请求(CSR, Certificate Signing Request)文件并对该请求进行自签名
在openssl的根目录下运行

openssl genrsa -out ./demoCA/private/cakey.pem 2048

genrsa —- 同时生成public key和private key
很多人将genrsa解释为只生成private key,这是不对的。可以用下面的命令从文件中解出公钥

openssl rsa -in cakey.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的内容格式是

—–BEGIN CERTIFICATE REQUEST—–
MIICnzCCAYcCAQAwWjELMAkGA1UEBhMCQ04xCzAJBgNVBAgTAlpKMQswCQYDVQQH
… …
ZYu4AZp0VzqnQzCTeYTbC+AsA0RrPVjr95Il46AHvhq2JQpFw8DhrS8Ja1VburI4
ngFK
—–END CERTIFICATE REQUEST—–

最后将该请求文件给CA机构做签名,但我们现在是想在本地建CA,因此自己对该文件进行自签名即可。

openssl ca -selfsign -in careq.pem -out cacert.pem

其实,上面生成CSR然后做自签名的两个步骤可合并到一步完成

openssl req -new -x509 -key ./demoCA/private/cakey.pem -out cacert.pem

至此,我们已经建立了自己的CA,接下去来分别签发client和server的证书。

2.创建client和server的证书、key store和trust store

以创建client的证书为例。由于jdk自带的keytool工具可以方便的创建key store和公私钥,因此公私钥和csr的创建直接使用keytool
key store和trust store分别对应于ssl握手证书认证中自己的证书和自己所信任的证书列表,二者的文件格式相同,不同之处是key store里面包含ssl握手一方的公私钥和证书,trust store里面包含ssl握手一方所信任的证书,一般没有这些证书所对应的私钥

a.生成client的keystore 和key pair

keytool -genkey -alias client -keyalg RSA -keystore client.keystore -keysize 2048

b.生成csr

keytool -certreq -alias client -keystore client.keystore -file client.csr

c.用本地CA对该csr签名

client证书中我们想添加证书的一项扩展,比如client id,用来区分client的身份,因此需要额外的一份扩展文件client.cnf,内容如下

[v3_req]
1.2.3.412=ASN1:UTF8String:0000001444

d.可以将该csr和client.cnf文件拷贝到openssl根目录下,运行

openssl ca -in client.csr -out client.pem -config ./openssl.cnf -extensions v3_req -extfile client.cnf

e.将签过名的client.pem导入到keystore文件中

在导入之前,需要先将CA的证书导入keystore文件

keytool -keystore client.keystore -importcert -alias CA -file cacert.pem

然后导入client自己的证书。注意alias是client,与生产keystore和key pair的必须匹配

keytool -keystore client.keystore -importcert -alias client -file client.pem

keystore文件内容的查看可以使用

keytool -list -v -keystore client.keystore

或者使用可视化工具KeyStore Explorer查看

f.创建client的trust store

由于server的证书也是本地CA签发的,因此client只要信任CA的证书那么自然会信任CA签发出的证书,所以我们只需将CA的证书导入trust store即可

keytool -import -alias cacert -file cacert.pem -keystore clienttruststore.keystore

由于clienttruststore.keystore文件尚不存在,此命令首先创建该文件并将CA的证书导入该trust store

server端创建

keytool -genkey -alias server -keyalg RSA -keystore server.keystore -keysize 2048
keytool -certreq -alias server -keystore server.keystore -file server.csr
openssl ca -in server.csr -out server.pem -config ./openssl.cnf
keytool -keystore server.keystore -importcert -alias CA -file cacert.pem
keytool -keystore server.keystore -importcert -alias server -file server.pem
keytool -import -alias ca -file cacert.pem -keystore servertruststore.keystore

NETTY创建client和server

由于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里面的证书

public class SslContextFactory
{
private static final String PROTOCOL = "TLS"; // TODO: which protocols will be adopted?
private static final SSLContext SERVER_CONTEXT;
private static final SSLContext CLIENT_CONTEXT;
static {
SSLContext serverContext = null;
SSLContext clientContext = null;
String keyStorePassword = "aerohive";
try
{
KeyStore ks = KeyStore.getInstance("JKS"); ks.load(SslContextFactory.class.getClassLoader().getResourceAsStream("cert\server.keystore"), keyStorePassword.toCharArray());
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyStorePassword.toCharArray());
// truststore
KeyStore ts = KeyStore.getInstance("JKS"); ts.load(SslContextFactory.class.getClassLoader().getResourceAsStream("cert\servertruststore.keystore"), keyStorePassword.toCharArray());
// set up trust manager factory to use our trust store
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
// Initialize the SSLContext to work with our key managers.
serverContext = SSLContext.getInstance(PROTOCOL);
serverContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
} catch (Exception e)
{
throw new Error("Failed to initialize the server-side SSLContext", e);
}
try
{
// keystore
KeyStore ks = KeyStore.getInstance("JKS"); ks.load(SslContextFactory.class.getClassLoader().getResourceAsStream("cert\client.keystore"), keyStorePassword.toCharArray());
// Set up key manager factory to use our key store
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyStorePassword.toCharArray());
// truststore
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(SslContextFactory.class.getClassLoader().getResourceAsStream("cert\clienttruststore.keystore"), keyStorePassword.toCharArray());
// set up trust manager factory to use our trust store
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ts);
clientContext = SSLContext.getInstance(PROTOCOL);
clientContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
} catch (Exception e)
{
throw new Error("Failed to initialize the client-side SSLContext", e);
}
SERVER_CONTEXT = serverContext;
CLIENT_CONTEXT = clientContext;
}
public static SSLContext getServerContext()
{
return SERVER_CONTEXT;
}
public static SSLContext getClientContext()
{
return CLIENT_CONTEXT;
}
}

io.netty.example.securechat.SecureChatClientInitializer类的构造器接收一个io.netty.handler.ssl.SslContext类型的对象,这个SslContext的对象最终被用来创建SslHandler,而上面factory产生的是javax.net.ssl.SSLContext的对象,因此可以做改动如下

public class ClientInitializer extends ChannelInitializer
{
private final javax.net.ssl.SSLContext sslCtx;
public ClientInitializer(javax.net.ssl.SSLContext sslCtx)
{
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) throws Exception
{
ChannelPipeline pipeline = ch.pipeline();
SSLEngine sslEngine = sslCtx.createSSLEngine(Client.HOST, Client.PORT);
sslEngine.setUseClientMode(true);
pipeline.addLast(new SslHandler(sslEngine));
// On top of the SSL handler, add the text line codec.
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
// and then business logic.
pipeline.addLast(new ClientHandler());
}
}

最后添加jvm参数

-Djavax.net.debug=ssl,handshake
-Ddeployment.security.TLSv1.1=true -Ddeployment.security.TLSv1.2=true

来查看ssl握手过程控制台的log

具体实现请参考附件源码。

http://download.csdn.net/detail/virgilli/8373319

附录:
openssl的配置
对证书签名时,遇到openssl异常failed to update database TXT_DB error

可以有三种方法解决这个问题

方法一:

修改demoCA下 index.txt.attr

unique_subject = yes
改为
unique_subject = no

方法二:
删除demoCA下的index.txt,并再touch下
方法三:

将 common name设置成不同的

有可能是因为签名的csr文件的subject中的一项或几项在该CA之前签发过的证书中已经出现过或者是csr中提供的国家/省份等等的名称与CA自己的不相同,这些限制都可以在openssl.cnf文件中修改
unique_subject=no
[ policy_match ]
countryName = match

stateOrProvinceName = match

organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335