这篇帖子,不会过多解释X509证书的基础理论知识,也不会介绍太多SSL/TLS的基本信息,重点介绍如何用java实现SSL协议需要的X509规范的证书。

之前的博文,介绍过用openssl创建证书,并配合EMQ进行发布订阅的工作逻辑,基于openssl创建证书和秘钥,还算是比较简便的,然后,基于java创建证书的过程,就有些许的小不方便,能找到的公开资料并不是太多,看到的都是基于keytool指令进行构建的介绍,但是呢,这种方案,对于我们的物联网安全应用,似乎不是很和谐。于是,啃了一段时间的java.security的相关资料(系统的也没有,都是比较零散的),最终还是折腾出了基本所需的创建证书导入证书等基本操作方案。

用java代码创建证书的逻辑,相对比较的灵活,比openssl灵活,整个过程完全可以由自己控制,遵循SSL规范,证书需要什么参数,逐个配置进去即可,有些参数是非必须的,比如extension参数,有很多可以不用。

X509证书规范,是需要CA签发intermediate certificate,然后由intermediate certificate签发下级证书,最终创建出leaf certificate或者叫用户证书。证书和私钥是配对使用的,证书中含有公钥,公钥加密的数据需要对应的私钥解密,私钥加密的数据需要对应的公钥进行解密,这些基本信息,或者叫基本理论,需要具备

接下来,需要介绍如何通过java创建证书以及导出导入证书的代码实现,以及遇到的一些问题,或者说注意事项。本博文的介绍,是基于RSA算法进行代码展示的。

1. 创建根证书

    /**
* 创建根证书, 并保存根证书到指定路径的文件中, crt和key分开存储文件。
* 创建SSL根证书的逻辑,很重要,此函数调用频次不高,创建根证书,也就是自签名证书。
*
* @param algorithm 私钥安全算法,e.g. RSA
* @param keySize 私钥长度,越长越安全,RSA要求不能小于512, e.g. 2048
* @param digestSignAlgo 信息摘要以及签名算法 e.g. SHA256withRSA
* @param subj 证书所有者信息描述,e.g. CN=iotp,OU=tkcloud,O=taikang,L=wuhan,S=hubei,C=CN
* @param validDays 证书有效期天数,e.g. 3650即10年
* @param rootCACrtPath 根证书所要存入的全路径,e.g. /opt/certs/iot/rootCA.crt
* @param rootCAKeyPath 根证书对应秘钥key所要存入的全路径,e.g. /opt/certs/iot/rootCA.key
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeyException
* @throws IOException
* @throws CertificateException
* @throws SignatureException
* @throws UnrecoverableKeyException
* @return 私钥和证书对的map对象
*/
public static HashMap<PrivateKey, X509Certificate> createRootCA(String algorithm, int keySize, String digestSignAlgo,
String subj, long validDays, String rootCACrtPath, String rootCAKeyPath) { //参数分别为 公钥算法 签名算法 providerName(因为不知道确切的 只好使用null 既使用默认的provider)
CertAndKeyGen cak = null;
try {
cak = new CertAndKeyGen(algorithm, digestSignAlgo,null);
//生成一对key 参数为key的长度 对于rsa不能小于512
cak.generate(keySize);
cak.setRandom(new SecureRandom()); //证书拥有者subject的描述name
X500Name subject = new X500Name(subj); //给证书配置扩展信息
PublicKey publicKey = cak.getPublicKey();
PrivateKey privateKey = cak.getPrivateKey();
CertificateExtensions certExts = new CertificateExtensions();
certExts.set("SubjectKeyIdentifier", new SubjectKeyIdentifierExtension((new KeyIdentifier(publicKey)).getIdentifier()));
certExts.set("AuthorityKeyIdentifier", new AuthorityKeyIdentifierExtension(new KeyIdentifier(publicKey), null, null));
certExts.set("BasicConstraints", new BasicConstraintsExtension(false,true,)); //配置证书的有效期,并生成根证书(自签名证书)
X509Certificate certificate = cak.getSelfCertificate(subject, new Date(),validDays * 24L * 60L * 60L, certExts); HashMap<PrivateKey, X509Certificate> rootCA = new HashMap<PrivateKey, X509Certificate>();
rootCA.put(privateKey, certificate); exportCrt(certificate, rootCACrtPath);
exportKey(privateKey, rootCAKeyPath); // String rootPath = "E:\\2018\\IOT\\MQTT\\javassl\\jsseRoot.keystore";
// String rootPfxPath = "E:\\2018\\IOT\\MQTT\\javassl\\jsseRoot.pfx";
// /**
// * 通过下面的指令,可以将keystore里面的内容转为DER格式的证书jsseRoot.cer
// * keytool -export -alias rootCA -storepass abcdef -file jsseRoot.cer -keystore jsseRoot.keystore
// *
// * 通过下面的指令,可以将DER格式的证书转化为OPENSSL默认支持的PEM证书:
// * openssl x509 -inform der -in jsseRoot.cer -out jsseRoot.pem
// */
// saveJks("rootCA", privateKey, "abcdef", new Certificate[]{certificate}, rootPath);
//
// /**
// * 通过下面的指令,可以获取证书的私钥
// * openssl pkcs12 -in jsseRoot.pfx -nocerts -nodes -out jsseRoot.key
// */
// savePfx("rootCA", privateKey, "abcdef", new Certificate[]{certificate}, rootPfxPath);
return rootCA; } catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} return null;
}

这里涉及到的两个重要的函数,exportCrt以及exportKey,下面也给出来,以享读者。

exportCrt:

    /**
* 将JAVA创建的证书内容导出到文件, 基于BASE64转码了。
*
*
* @param devCrt 设备证书对象
* @param crtPath 设备证书存储路径
*/
public static void exportCrt(Certificate devCrt, String crtPath) {
BASE64Encoder base64Crt = new BASE64Encoder();
FileOutputStream fosCrt = null;
try {
fosCrt = new FileOutputStream(new File(crtPath));
String cont = BEGIN_CERTIFICATE + NEW_LINE;
fosCrt.write(cont.getBytes());
base64Crt.encodeBuffer(devCrt.getEncoded(), fosCrt);
cont = END_CERTIFICATE;
fosCrt.write(cont.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (CertificateEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fosCrt != null) {
try {
fosCrt.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

exportKey:

   /**
* 导出私钥内容到文件中,以base64编码。
* 注意,java生成的私钥文件默认是PKCS#8的格式,加载的时候,要注意对应关系。
*
* @param key
* @param keyPath
*/
public static void exportKey(PrivateKey key, String keyPath) {
BASE64Encoder base64Crt = new BASE64Encoder();
FileOutputStream fosKey = null;
try {
fosKey = new FileOutputStream(new File(keyPath));
String cont = BEGIN_RSA_PRIVATE_KEY + NEW_LINE;
fosKey.write(cont.getBytes());
base64Crt.encodeBuffer(key.getEncoded(), fosKey);
cont = END_RSA_PRIVATE_KEY;
fosKey.write(cont.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fosKey != null) {
try {
fosKey.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

上述代码中,存在几个参数:

private static final String NEW_LINE = System.getProperty("line.separator");
/** 证书摘要及签名算法组 */
public static final String MSG_DIGEST_SIGN_ALGO = "SHA256withRSA";
/** 在将java生成的证书导出到文件的时候,需要将下面两行信息对应的添加到证书内容的头部后尾部 */
private static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----";
private static final String END_CERTIFICATE = "-----END CERTIFICATE-----"; /** 在将java生成的私钥导出到文件的时候,需要将下面两行信息对应的添加到私钥内容的头部后尾部 */
private static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----";
private static final String END_RSA_PRIVATE_KEY = "-----END PRIVATE KEY-----";

2. 创建用户证书,也就是leaf certificate.

证书创建过程,有很重要的一个基本理论,就是签发证书的签发者私钥用来对申请证书者的公钥的摘要进行签名(Signature)。

    /**
* 创建X509的证书, 由ca证书完成签名。
*
* subject,issuer都遵循X500Principle规范,
* 即: X500Principal由可分辨名称表示,例如“CN = Duke,OU = JavaSoft,O = Sun Microsystems,C = US”。
*
* @param ca 根证书对象
* @param caKey CA证书对应的私钥对象
* @param publicKey 待签发证书的公钥对象
* @param subj 证书拥有者的主题信息,签发者和主题拥有者名称都转写X500Principle规范,格式:CN=country,ST=state,L=Locality,OU=OrganizationUnit,O=Organization
* @param validDays 证书有效期天数
* @param sginAlgo 证书签名算法, e.g. SHA256withRSA
*
* @return cert 新创建得到的X509证书
*/
public static X509Certificate createUserCert(X509Certificate ca, PrivateKey caKey, PublicKey publicKey,String subj, long validDays, String sginAlgo) { //获取ca证书
X509Certificate caCert = ca; X509CertInfo x509CertInfo = new X509CertInfo(); try {
//设置证书的版本号
x509CertInfo.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); //设置证书的序列号,基于当前时间计算
x509CertInfo.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber((int) (System.currentTimeMillis() / 1000L))); /**
* 下面这个设置算法ID的代码,是错误的,会导致证书验证失败,但是报错不是很明确。 若将生成的证书存为keystore,让后keytool转换
* 会出现异常。
* AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.SHA256_oid);
*/

AlgorithmId algorithmId = AlgorithmId.get
(sginAlgo);
x509CertInfo.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algorithmId)); //设置证书的签发者信息
X500Name issuer = new X500Name(caCert.getIssuerX500Principal().toString());
x509CertInfo.set(X509CertInfo.ISSUER, issuer); //设置证书的拥有者信息
X500Name subject = new X500Name(subj);
x509CertInfo.set(X509CertInfo.SUBJECT, subject); //设置证书的公钥
x509CertInfo.set(X509CertInfo.KEY, new CertificateX509Key(publicKey)); //设置证书有效期
Date beginDate = new Date();
Date endDate = new Date(beginDate.getTime() + validDays * * * * 1000L);
CertificateValidity cv = new CertificateValidity(beginDate, endDate);
x509CertInfo.set(X509CertInfo.VALIDITY, cv); CertificateExtensions exts = new CertificateExtensions(); /*
* 以上是证书的基本信息 如果要添加用户扩展信息 则比较麻烦 首先要确定version必须是v3否则不行 然后按照以下步骤
*
*/
exts.set("SubjectKeyIdentifier", new SubjectKeyIdentifierExtension((new KeyIdentifier(publicKey)).getIdentifier()));
exts.set("AuthorityKeyIdentifier", new AuthorityKeyIdentifierExtension(new KeyIdentifier(ca.getPublicKey()), null, null));
exts.set("BasicConstraints", new BasicConstraintsExtension(false,false,));
x509CertInfo.set("extensions", exts); } catch (CertificateException cee) {
cee.printStackTrace();
} catch (IOException eio) {
eio.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} // 获取CA私钥
PrivateKey caPrivateKey = caKey;
//用CA的私钥给当前证书进行签名,获取最终的下游证书(证书链的下一节点)
X509CertImpl cert = new X509CertImpl(x509CertInfo);
try {
cert.sign(caPrivateKey, sginAlgo);
} catch (InvalidKeyException | CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException e3) {
e3.printStackTrace();
}
return cert;
}

注意,这里我遇到了些问题,在研究如何创建用户证书的逻辑中,上述代码红色部分,很值得注意,X509CertInfo.ALGORITHM_ID这个值的设定,必须和后面证书签名过程中用到的算法配置信息一致,否则会出现错误。

例如:

1. AlgorithmId algorithmId = new AlgorithmId(AlgorithmId.SHA256_oid);
2.
cert.sign(caPrivateKey, sginAlgo); 将上述代码中红色部分的代码,对应修改成上面的代码, 得到的证书内容将是下面的样子(openssl打开):
[root@mq2 new]# openssl x509 -in dev003.crt -text
Certificate:
Data:
Version: (0x2)
Serial Number: (0x5c886d20)
Signature Algorithm: sha256
Issuer: C=CN, ST=Hubei, L=Wuhan, O=Taikang, OU=TKcloud, CN=iotca
Validity
Not Before: Mar :: GMT
Not After : Mar :: GMT
Subject: C=CN, ST=Hubei, L=Wuhan, OU=TKCloud, O=TaiKang, CN=IOTPlatform
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: ( bit)
Modulus:
:af::aa:cf::b8::7f:d7:d6::f3::6d:
9f:ed:b5:f7:d6:::::::dd:b0::6b:
be::8b:3e:ec:3e::1a::::::ea:5f:
::b5:e1:a8:b2::::b8:::bb:fc:d5:
:ab:ef:de:::8e:ae:3f:a0::4e:2f::5e:
a2:cd:::7f:b2:7f:8c:4b:e5::ed::::
b4::ba::4a:8e:::d3::f3::::e8:
c9:5b:7b:aa::::d4:0a:d6::b6::f6::
be:e1::a0::a5::6b:4e:::3e:b5:0b::
:d4:bf:8b:b6::6f::d3:::::::
ce::d8::b9:1d:::9b:f0:8e:::a3::
6d:e7::be:a7::::e5::e6:3e::3d:5b:
a2:c0::dd:dd:7c::a8:a3:5c:4e:7e::::
::8c:::2e:::bb::::dc:1c::
0e:1d:be:3b:e1::8f:a1::c7:5e::::d6:
1d:::8e::f7:bd:a7:::::3f:::
:::ec::c5:ec:fb:0e:b2:e7:f5:6b:8a::
:c1
Exponent: (0x10001)
X509v3 extensions:
X509v3 Authority Key Identifier:
keyid:F9:B0:::::::3A:EB:E2::F6:::EA:E6:F7::6F
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
FD:3D:EA::::::::B6:B9:0E:0E::4C:4B:5F::D8
Signature Algorithm: sha256WithRSAEncryption
5c:dd:3c:c0:9a::e2:bb:f6::::fa:::df::ef:
:4a:3b:e7:f9:ad:::::a8:1e:4d:ef:ad::e8:3d:
2b:4b:c9:cf:7e:d6:7d::c5:a0:da:8e:d0:6f:c6::9f:6a:
::::dd::8c:f4:3a::7d::a2:ca:2f:4a:::
:2d:3d:7b:9a:4a::8a::c0:8e:fc:0e:ac:e9:d7:ce:9b:
:e7::c7:eb::a7:b1:ab::e0:3e:::6a:4e:0e::
:b0:b0::af:7f:f3:fd:e2:1c::c0::f7:::eb:f4:
e0::5c:4d:d1:e0:ef::db::f4:3e:b2:b0:fb:6d:f5::
:d7:d6:::cd::bc:fd:7d::::9b:5d:ad:b6:fc:
:6b:9e:5d:b3:3f::5f:a4:ee:fc:cf:d9:eb::1b:::
8b::5e:c2:d8:dc:::fe:1c:eb::::1f::e1::
4d:4a::e1::::e4:8c:4f::dc:1b:5c:6a:bc:9a:a6:
f5:1f::db:db:3d:::f9:df::::::cd:9b:5d:
de:2b:::::c9:1b:b4::b6:::d8:8d:ea:::
:f0:c5:b3
-----BEGIN CERTIFICATE-----
MIIDkzCCAnugAwIBAgIEXIhtIDANBglghkgBZQMEAgEFADBhMQswCQYDVQQGEwJD
TjEOMAwGA1UECBMFSHViZWkxDjAMBgNVBAcTBVd1aGFuMRAwDgYDVQQKEwdUYWlr
YW5nMRAwDgYDVQQLEwdUS2Nsb3VkMQ4wDAYDVQQDEwVpb3RjYTAeFw0xOTAzMTMw
MjM4MjRaFw0yOTAzMTAwMjM4MjRaMGcxCzAJBgNVBAYTAkNOMQ4wDAYDVQQIEwVI
dWJlaTEOMAwGA1UEBxMFV3VoYW4xEDAOBgNVBAsTB1RLQ2xvdWQxEDAOBgNVBAoT
B1RhaUthbmcxFDASBgNVBAMTC0lPVFBsYXRmb3JtMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEArxGqz2W4FX/X1mbzIG2f7bX31nFSCUBCdd2wJmu+cIs+
7D5jGoMCE0J26l92JLXhqLIWhHS4JwS7/NWTq+/elIKOrj+gU04vEl6izRAAf7J/
jEvlYu0ylUi0BLpASo5DeNMJ8zFJCejJW3uqiCVE1ArWl7YT9oG+4XigNKUBa05J
Ej61C4VW1L+LtkZvMtMhKJYEJ0POc9gHuR0FVZvwjjJioxFt51W+pwaWFeVl5j5Z
PVuiwJHd3XwZqKNcTn6WZEgXUIwoCS5RM7tzg5ncHEAOHb474SKPoTPHXngCZdYd
GSSOIve9p5gjRzQ/iUY5KUbsgMXs+w6y5/VripNGwQIDAQABo00wSzAfBgNVHSME
GDAWgBT5sESDZghoVDrr4ob2KELq5vcgbzAJBgNVHRMEAjAAMB0GA1UdDgQWBBT9
PepXOCaFRmEHtrkODldMS18T2DANBgkqhkiG9w0BAQsFAAOCAQEAXN08wJoB4rv2
FTAj+pYX3yXvMEo75/mtECgyRageTe+tdeg9K0vJz37WfSPFoNqO0G/GSZ9qBSVQ
Jd1VjPQ6QH0yosovSohpIC09e5pKAYo2wI78Dqzp186bNOeYx+shp7GriOA+YEdq
Tg40NLCwlK9//3iHAbAgfcpEev04ERcTdHg73XbNPQ+srD7bfVShdfWhXfNBLz9
fQJBE5tdrbb8V2ueXbM/cF+k7vzP2euSG1RXi5RewtjceAP+HOtwkoEfAOE5TUoJ
4Rhlh+SMTxbcG1xqvJqm9R8h29s9EjT531RxMGeYzZtd3itziIMCyRu0dLZjc9iN
6hlGg/DFsw==
-----END CERTIFICATE-----

java代码System.out.println()后的结果是:

[
[
Version: V3
Subject: CN=IOTPlatform, O=TaiKang, OU=TKCloud, L=Wuhan, ST=Hubei, C=CN
Signature Algorithm: SHA-256, OID = 2.16.840.1.101.3.4.2. Key: Sun RSA public key, bits
modulus:
public exponent:
Validity: [From: Wed Mar :: CST ,
To: Sat Mar :: CST ]
Issuer: CN=iotca, OU=TKcloud, O=Taikang, L=Wuhan, ST=Hubei, C=CN
SerialNumber: [ 5c886d20] Certificate Extensions:
[]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
: F9 B0 3A EB E2 F6 EA ..D.f.hT:....(B.
: E6 F7 6F .. o
]
] []: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
CA:false
PathLen:
] []: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
: FD 3D EA B6 B9 0E 0E 4C .=.W8&.Fa.....WL
: 4B 5F D8 K_..
]
] ]
Algorithm: [SHA256withRSA]
Signature:
: 5C DD 3C C0 9A E2 BB F6 FA DF \.<.......#....
: EF 4A 3B E7 F9 AD A8 1E 4D EF %.0J;....(2E..M.
: AD E8 3D 2B 4B C9 CF 7E D6 7D C5 A0 DA 8E .u.=+K.....#....
: D0 6F C6 9F 6A DD 8C F4 3A .o.I.j.%P%.U..:@
: 7D A2 CA 2F 4A 2D 3D 7B 9A 4A 8A .../J.i -=..J..
: C0 8E FC 0E AC E9 D7 CE 9B E7 C7 EB .............!
: A7 B1 AB E0 3E 6A 4E 0E B0 B0 .....>`GjN....
: AF 7F F3 FD E2 1C C0 F7 EB F4 E0 ..........)....D
: 5C 4D D1 E0 EF DB F4 3E B2 B0 FB 6D F5 \M...u..>...m.R
: D7 D6 CD BC FD 7D 9B 5D AD ....w......A..].
00A0: B6 FC 6B 9E 5D B3 3F 5F A4 EE FC CF D9 EB ..Wk.].?p_......
00B0: 1B 8B 5E C2 D8 DC FE 1C EB ..TW..^...x....p
00C0: 1F E1 4D 4A E1 E4 8C 4F .....9MJ...e...O
00D0: DC 1B 5C 6A BC 9A A6 F5 1F DB DB 3D ...\j.....!..=.
00E0: F9 DF CD 9B 5D DE 2B ..Tq0g...].+s...
00F0: C9 1B B4 B6 D8 8D EA F0 C5 B3 ...t.cs....F.... ]

keytool工具打开出现异常

E:\HOWTO\emqtt-ssl\self1\java>keytool -printcert -file dev003.crt
keytool 错误: java.lang.Exception: 无法解析输入

利用这种错误算法配置生成的证书,paho验证时遇到下面的错误:

MqttException () - javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(ExceptionHelper.java:)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:)
at java.util.concurrent.FutureTask.run(FutureTask.java:)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$(ScheduledThreadPoolExecutor.java:)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:)
at java.lang.Thread.run(Thread.java:)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alerts.getSSLException(Alerts.java:)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:)
at org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule.start(SSLNetworkModule.java:)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(ClientComms.java:)
... more

若将代码配置成正确的样子(如下):

AlgorithmId algorithmId = AlgorithmId.get(sginAlgo);
cert.sign(caPrivateKey, sginAlgo);

此时得到的证书System.out.println()的结果是:

[
[
Version: V3
Subject: CN=IOTPlatform, O=TaiKang, OU=TKCloud, L=Wuhan, ST=Hubei, C=CN
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1. Key: Sun RSA public key, bits
modulus:
public exponent:
Validity: [From: Wed Mar :: CST ,
To: Sat Mar :: CST ]
Issuer: CN=iotca, OU=TKcloud, O=Taikang, L=Wuhan, ST=Hubei, C=CN
SerialNumber: [ 5c886fc2] Certificate Extensions:
[]: ObjectId: 2.5.29.35 Criticality=false
AuthorityKeyIdentifier [
KeyIdentifier [
: F9 B0 3A EB E2 F6 EA ..D.f.hT:....(B.
: E6 F7 6F .. o
]
] []: ObjectId: 2.5.29.19 Criticality=false
BasicConstraints:[
CA:false
PathLen:
] []: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
: 5D 8C FB B3 1C A0 3B 3B C5 ]H....#...;t;..
: 4C L...
]
] ]
Algorithm: [SHA256withRSA]
Signature:
: D2 F1 E8 E9 FE B2 9F 5F 3B D5 6F ......Sg.._Y;.o
: D4 F0 C7 0A D8 0C 9F b........`R0...
: 5B 9D C5 1C BE 3C [..v8B...x....Q<
: A9 AE 7A 8F C9 CE AE D2 .I.zP....pd....
: 4D 1E A6 EB 7C E4 C0 2F 8F 7D M..t......./..U
: C5 F8 F4 FE D4 A1 B3 5D D1 CB 7C 8A 2E 6D 1B .......]......m.
: ED C9 A1 8A 5D C2 B3 4E 5D DB B1 .....]Q...N.]..
: D3 F4 A3 5D E4 CA 2E C1 A1 3D ...b]..P...S...=
: BD AF A3 6D 3E EC CC 9B 8F A8 5F w..G.m>..y....._
: F1 D3 A2 E7 DF 3A FA C2 6E B2 1C DA ....r.:.....n...
00A0: 1C 0B F3 EF 9B 6B D5 5C A9 ). .........k.\.
00B0: B1 AA D0 9B E5 FF 5F 9C BF B5 D9 .w......._f....
00C0: 6B E8 C4 7C 2A 7A A0 F6 FE 2C k...P.X*.v.z...,
00D0: B0 3E F2 E5 CD DD 6C ED A4 1D ..dG>C....lV.q..
00E0: DA B1 AA EB B8 E7 D2 B4 ....W.....s.....
00F0: 5A E0 4A A3 CF 6E A7 F5 AE .'.Z.Jt9x..n.... ]

也就是说,正确的算法配置方法,是证书算法配置和签名用到的算法必须是一致的。

关于这个配置,我们可以看看java代码(jdk)中的关于生成自签名证书的函数(C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar!\sun\security\tools\keytool\CertAndKeyGen.class),可以佐证这个逻辑:

public X509Certificate getSelfCertificate(X500Name var1, Date var2, long var3, CertificateExtensions var5) throws CertificateException, InvalidKeyException, SignatureException, NoSuchAlgorithmException, NoSuchProviderException {
try {
Date var7 = new Date();
var7.setTime(var2.getTime() + var3 * 1000L);
CertificateValidity var8 = new CertificateValidity(var2, var7);
X509CertInfo var9 = new X509CertInfo();
var9.set("version", new CertificateVersion());
var9.set("serialNumber", new CertificateSerialNumber((new Random()).nextInt() & ));
AlgorithmId var10 = AlgorithmId.get(this.sigAlg);
var9.set("algorithmID", new CertificateAlgorithmId(var10));
var9.set("subject", var1);
var9.set("key", new CertificateX509Key(this.publicKey));
var9.set("validity", var8);
var9.set("issuer", var1);
if (var5 != null) {
var9.set("extensions", var5);
} X509CertImpl var6 = new X509CertImpl(var9);
var6.sign(this.privateKey, this.sigAlg);
return var6;
} catch (IOException var11) {
throw new CertificateEncodingException("getSelfCert: " + var11.getMessage());
}
}

3. 证书和私钥导入

   /**
* 得到私钥, 记得这个文件是类似PEM格式的问题,需要将文件头部的----BEGIN和尾部的----END信息去掉
*
* @param privateKey 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//通过PKCS#8编码的Key指令获得私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
if(privateKey.startsWith(BEGIN_RSA_PRIVATE_KEY)) {
int bidx = BEGIN_RSA_PRIVATE_KEY.length();
privateKey = privateKey.substring(bidx);
}
if (privateKey.endsWith(END_RSA_PRIVATE_KEY)) {
int eidx = privateKey.indexOf(END_RSA_PRIVATE_KEY);
privateKey = privateKey.substring(, eidx);
}
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
return key;
}
    /**
* 从经过base64转化后的证书文件中构建证书对象,是一个标准的X509证书,
*
* 且非常重要的是,文件头部含有-----BEGIN CERTIFICATE-----
* 文件的尾部含有 -----END CERTIFICATE-----
* 若没有上述头和尾部,证书验证的时候会报certificate_unknown。
*
* @param crtFile 经过base64处理的证书文件
* @return X509的证书
*/
public static X509Certificate getCertficate(File crtFile) {
//这个客户端证书,是用来发送给服务端的,准备做双向验证用的。
CertificateFactory cf;
X509Certificate cert = null;
FileInputStream crtIn = null;
try {
cf = CertificateFactory.getInstance(X509);
crtIn = new FileInputStream(crtFile);
cert = (X509Certificate) cf.generateCertificate(crtIn);
} catch (CertificateException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if(crtIn != null){
try {
crtIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return cert;
}

导入证书和私钥的地方,需要注意,java代码中有个奇怪的现象,对应私钥的导入,私钥文件内容(字符串)中,不能含有类似BEGIN PRIVATE KEY的头和尾部信息,才能正确的导入并生成PrivateKey对象,但是呢,对应Certificate文件内容(字符串)导入并创建证书对象的时候,文件内容中必须要含有BEGIN CERTIFICATE之类的头部信息,否则导入证书失败

关于从文件导入证书和私钥并构建java对象,验证MQTT环境下,与服务端建立连接的逻辑,给出一个demo:

package com.taikang.iot.scc.ca;

import com.taikang.iot.scc.utils.MySSL;
import org.apache.commons.io.FileUtils;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import javax.net.ssl.SSLSocketFactory;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.concurrent.ScheduledExecutorService; /**
* @Author: chengsh05
* @Date: 2019/3/8 15:22
*/
public class JsseConsumer { public static final String HOST = "ssl://10.95.197.3:8883";
public static final String TOPIC1 = "taikang/rulee";
private static final String clientid = "dev001";
private MqttClient client;
private MqttConnectOptions options;
private String userName = "shihuc"; //非必须
private String passWord = "shihuc"; //非必须
@SuppressWarnings("unused")
private ScheduledExecutorService scheduler;
private String sslPemPath = "E:\\HOWTO\\emqtt-ssl\\self1\\";
private String sslCerPath = "E:\\HOWTO\\emqtt-ssl\\self1\\java\\"; private void start() {
try {
// host为主机名,clientid即连接MQTT的客户端ID,一般以唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
client = new MqttClient(HOST, clientid, new MemoryPersistence());
// MQTT的连接设置
options = new MqttConnectOptions(); String devCrtCtx = FileUtils.readFileToString(new File(sslCerPath,"dev003.crt"), MySSL.CHARSET);
String devKeyCtx = FileUtils.readFileToString(new File(sslCerPath,"dev003.key"), MySSL.CHARSET);
X509Certificate devCrt = MySSL.getCertficate(devCrtCtx);
PrivateKey devKey = MySSL.getPrivateKey(devKeyCtx);

String rootCACrtPath = "E:\\HOWTO\\emqtt-ssl\\self1\\java\\rootCA0.crt";
String rootCAKeyPath = "E:\\HOWTO\\emqtt-ssl\\self1\\java\\ca0.key"; CertificateFactory cAf = CertificateFactory.getInstance("X.509");
FileInputStream caIn = new FileInputStream(rootCACrtPath);
X509Certificate ca = (X509Certificate) cAf.generateCertificate(caIn);
PrivateKey caKey = (PrivateKey) MySSL.getPrivateKey(new File(rootCAKeyPath)); SSLSocketFactory factory = MySSL.getSSLSocketFactory(ca, devCrt, devKey, "shihuc");
options.setSocketFactory(factory); // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(false);
// 设置连接的用户名
options.setUserName(userName);
// 设置连接的密码
options.setPassword(passWord.toCharArray());
// 设置超时时间 单位为秒
options.setConnectionTimeout();
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval();
// 设置重连机制
options.setAutomaticReconnect(false);
// 设置回调
client.setCallback(new BuizCallback());
MqttTopic topic = client.getTopic(TOPIC1);
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
//options.setWill(topic, "close".getBytes(), 2, true);//遗嘱
client.connect(options);
//订阅消息
int[] Qos = {};
String[] topic1 = {TOPIC1};
client.subscribe(topic1, Qos); } catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) throws MqttException {
System.setProperty("javax.net.debug", "ssl,handshake");
JsseConsumer client = new JsseConsumer();
client.start();
}
}

关于其中paho的相关配置,请参考我前面的博客文章,或者有其他详细内容需要探讨的话,可以关注我的博客,和我互动。

MQTT研究之EMQ:【JAVA代码构建X509证书】的更多相关文章

  1. MQTT研究之EMQ:【JAVA代码构建X509证书【续集】】

    openssl创建私钥,获取公钥,创建证书都是比较简单的,就几个指令,很快就可以搞定,之所以说简单,是因为证书里面的基本参数配置不需要我们组装,只需要将命令行里面需要的几个参数配置进去即可.但是呢,用 ...

  2. MQTT研究之EMQ:【SSL证书链验证】

    1. 创建证书链(shell脚本) 客户端证书链关系: rootCA-->chainca1-->chainca2-->chainca3 ca caCert1 caCert2 caCe ...

  3. MQTT研究之EMQ:【CoAP协议应用开发】

    本博文的重点是尝试CoAP协议的应用开发,其中包含CoAP协议中一个重要的开源工具libcoap的安装和遇到的问题调研.当然,为了很好的将EMQ的CoAP协议网关用起来,也调研了下EMQ体系下,CoA ...

  4. MQTT研究之EMQ:【EMQ之HTTP认证/访问控制】

    今天进行验证的逻辑是EMQ的http的Auth以及ACL的逻辑. 首先,参照HTTP插件认证配置的说明文档进行基本的配置, 我的配置内容如下: ##-------------------------- ...

  5. MQTT研究之EMQ:【CoAP协议的ECC证书研究】

    今天研究的内容,是CoAP这个协议在EMQ消息队列的支持,CoAP是一个受限资源的协议,基于UDP实现的多用于物联网环境的通信协议.相关介绍不多说,可以看RFC. CoAP协议下,基于DTLS通信,同 ...

  6. MQTT研究之EMQ:【wireshark抓包分析】

    基于上篇博文[SSL双向验证]的环境基础,进行消息的具体梳理. 环境基础信息: . 单台Linux CentOS7.2系统,安装一个EMQTTD的实例broker. . emq的版本2.3.11. . ...

  7. MQTT研究之EMQ:【eclipse的paho之java客户端使用注意事项】

    这里,简单记录一下自己在最近项目中遇到的paho的心得,这里也涵盖EMQX的问题. 1. cleanSession 这个标识,是确保client和server之间是否持久化状态的一个标志,不管是cli ...

  8. MQTT研究之EMQ:【基础研究】

    EMQ版本V2, emqttd-centos7-v2.3.11-1.el7.centos.x86_64.rpm 下载地址:http://emqtt.com/downloads/2318/centos7 ...

  9. MQTT研究之EMQ:【SSL双向验证】

    EMQ是当前MQTT中,用于物联网领域中比较出色的一个broker,今天我这里要记录和分享的是关于SSL安全通信的配置和注意细节. 环境: 1. 单台Linux CentOS7.2系统,安装一个EMQ ...

随机推荐

  1. 桂林电子科技大学第三届ACM程序设计竞赛 G 路径

    链接:https://ac.nowcoder.com/acm/contest/558/G来源:牛客网 小猫在研究树. 小猫在研究路径. 给定一棵N个点的树,每条边有边权,请你求出最长的一条路径,满足经 ...

  2. L342 Air Pollution Is Doing More Than Just Slowly Killing Us

    Air Pollution Is Doing More Than Just Slowly Killing Us In the future, the authorities might need to ...

  3. python基础14_文件操作

    文件操作,通常是打开,读,写,追加等.主要涉及 编码 的问题. #!/usr/bin/env python # coding:utf-8 ## open实际上是从OS请求,得到文件句柄 f = ope ...

  4. Mysql基础教程-Mysql的字符集查看与修改

    Show variables like “%char%”修改mysql的字符集----数据库级1)临时的修改Set global character-set_server=utf82)永久修改Alte ...

  5. Session 与 Token 的区别

    1. 为什么要有session的出现?答:是由于网络中http协议造成的,因为http本身是无状态协议,这样,无法确定你的本次请求和上次请求是不是你发送的.如果要进行类似论坛登陆相关的操作,就实现不了 ...

  6. sdn学习-1(概念:Underlay网络和Overlay网络)

    随着云计算.大数据.移动互联网等新技术的普及,部署大量虚拟机成为一种必然趋势.解决这些虚拟机迁移问题理想的方案是在传统单层网络(Underlay)基础上叠加(Overlay)一层逻辑网络,将网络分成两 ...

  7. Graphics Class

    System.Drawing 封装一个 GDI+ 绘图图面. 此类不能被继承. https://docs.microsoft.com/zh-cn/dotnet/api/system.drawing.g ...

  8. php中生成透明背景png缩略图程序

    /** *$sourePic:原图路径 * $smallFileName:小图名称 * $width:小图宽 * $heigh:小图高 */function pngthumb($sourePic,$s ...

  9. Vue组件中的问题

    错误信息: 提示信息含义:组件模板中只能包含一个根元素 解决办法:在模板元素内部增加块级元素div将这些元素标签包裹起来,如图所示

  10. PXE自动装机

    PXE自动装机 一.搭建PXE远程安装服务器 PXE自动装机需要桌面模式 假如不是桌面模式安装的PXE需要安装桌面模式软件包 yum groupinstall "Desktop" ...