工作中需要用到CA数字证书,经过搜索相关资料,做以下共享,若有不足之处,还望读者海涵。在保护我们的在线交流和数据安全方面,CA数字认证发挥着关键的作。
(注意)仅是能否实现CA数字证书的test代码。实际生产最好不要使用。有其它非常简单的方式!
(一)做法以及流程:
* 添加Bouncy Castle安全提供程序: 开始时,通过Security.addProvider(new BouncyCastleProvider())将Bouncy Castle作为Java的安全提供程序添加到代码中,
* 以便支持使用Bouncy Castle进行加密和签名操作。
* 生成根证书和私钥: 使用RSA算法生成一个2048位的密钥对,其中包括根证书的密钥对rootKeyPair。
* 然后,使用generateRootCertificate方法生成自签名的根证书rootCertificate,包含了根证书的基本信息和公钥。
* 生成的根证书会被保存到文件root-ca-cert.pem,私钥会被保存到文件root-ca-key.pem。
* 生成实体证书请求(CSR)和私钥: 类似地,使用RSA算法生成一个2048位的密钥对,其中包括实体的密钥对entityKeyPair。
* 然后,使用generateEntityCertificate方法生成实体的证书请求(CSR)entityCertificate,并将该请求提交到CA。
* 如果请求成功(HTTP响应码为200到299之间),则实体的证书会被保存到文件entity-cert.pem,私钥会被保存到文件entity-key.pem。
* 数字证书验证: 在verificationSignatureCertificate方法中,服务端验证客户端传来的数字证书的有效性。
* 首先,加载根证书,创建TrustAnchor表示可信任的根证书,然后将其添加到PKIXParameters中,用于配置和初始化PKI验证。
* 接着,使用CertPathValidator进行证书路径验证,并检查证书的有效期、主体和颁发者是否匹配。
* 数字证书签名和封装: 在getSignatureCertificatePKCS7方法中,服务端使用私钥对要签名的数据进行签名,
* 生成数字签名,然后将签名证书和签名结果打包为PKCS#7格式的数字证书。使用CMSSignedDataGenerator类生成PKCS#7格式的数字证书,并将其转换为字节数组返回。
* RSA公钥加密: 在RSAEncryptCertificate方法中,服务端使用客户端传来的RSA公钥对数字证书进行加密,
* 再将加密后的数字证书和使用AES密钥对数字证书进行加密后的结果封装为EncryptedDataAndAESKey对象返回。
* 发送CSR到CA: 在sendCSRToCA方法中,服务端使用HTTP POST请求将实体的证书请求(CSR)发送到CA。
* 首先构建请求参数和请求体,然后使用HttpsURLConnection建立安全的HTTPS连接,将请求体写入输出流,获取响应结果(响应码和响应体),并返回响应码。
* CA签署CSR后,将服务器数字证书发送回服务器。(可以使用定时器等接收CA发回的数字证书)
* 服务器接收到由CA签发的服务器数字证书后,将其与服务器的私钥一起保存在服务器上。私钥仍然必须保持机密。
* 在服务器上配置和使用数字证书:
* 服务器在其网络服务(如HTTPS服务器)中配置并使用服务器数字证书。客户端连接服务器时,可以使用服务器数字证书来验证服务器的身份,并启用安全通信。
1:需要导入的依赖包
import cn.ueclzsz.im.common.utils.Assert; import cn.ueclzsz.im.service.rsa.entity.EncryptedDataAndAESKey; import cn.ueclzsz.im.service.rsa.utlis.Common.CommonUtils; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; import javax.crypto.SecretKey; import javax.net.ssl.HttpsURLConnection; import java.io.*; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.*; import java.security.cert.*; import java.util.*;
2:生成CA证书并提交请求
/** * 生成CA证书并提交请求 */ public void CACreation() { //将 Bouncy Castle 作为 Java 的安全提供程序添加到代码中 Security.addProvider(new BouncyCastleProvider()); try { // 步骤1:生成根证书和私钥,根证书的密钥对用于签发其他证书 //创建一个 KeyPairGenerator对象, keyPairGenerator 以生成密钥对 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); //初始化 keyPairGenerator.initialize(2048); //生成根 CA 的密钥对 rootKeyPair KeyPair rootKeyPair = keyPairGenerator.generateKeyPair(); // 创建自签名根证书 X509Certificate rootCertificate = generateRootCertificate(rootKeyPair); // TODO 保存根证书和私钥到 "root-ca-cert.pem" "root-ca-key.pem"文件中 saveCertificate(rootCertificate, "root-ca-cert.pem"); savePrivateKey(rootKeyPair.getPrivate(), "root-ca-key.pem"); // 步骤2:生成实体证书请求 和私钥 KeyPairGenerator entityKeyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); entityKeyPairGenerator.initialize(2048); //生成用于实体证书请求的的密钥对 entityKeyPair KeyPair entityKeyPair = entityKeyPairGenerator.generateKeyPair(); //生成实体的证书请求(CSR)。其中包括实体的公钥和CSR,以及根CA的证书和私钥用于签发实体证书。 X509Certificate entityCertificate = generateEntityCertificate(entityKeyPair, rootCertificate, rootKeyPair.getPrivate()); //提交CSR到CA int responseCode = sendCSRToCA(entityCertificate); //判断是否成功发送了请求 if (responseCode >= 200 && responseCode < 300) { // TODO 保存实体证书和私钥 saveCertificate(entityCertificate, "entity-cert.pem"); savePrivateKey(entityKeyPair.getPrivate(), "entity-key.pem"); } else { Assert.isTrue(false,"提交CSR申请失败"); } } catch (Exception e) { e.printStackTrace(); } }
3:生成自签名的根证书的方法
/** * 生成自签名的根证书的方法 * 该证书可以用于签发其他实体证书 * 仅测试环境使用 */ public static X509Certificate generateRootCertificate(KeyPair keyPair) throws Exception { //将 Bouncy Castle 作为 Java 的安全提供程序添加到代码中 Security.addProvider(new BouncyCastleProvider()); //用于指定实体的主题 // 创建一个 X500NameBuilder 对象 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); // 添加标准的主题信息(例如:CN、OU、O 等) nameBuilder.addRDN(BCStyle.CN, "example.com"); // 服务器的域名或IP地址 nameBuilder.addRDN(BCStyle.OU, "IT Department"); // 组织单位 nameBuilder.addRDN(BCStyle.O, "Example Organization"); // 组织名称 // 创建 X500Name 对象 X500Name subject = nameBuilder.build(); //创建一个X509v3CertificateBuilder对象,用于生成X.509证书。这个构造器用于构建证书的各种属性,包括版本、序列号、有效期、主题、公钥等 X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( subject,//指定证书的主题,即证书的持有者 BigInteger.valueOf(new Random().nextLong()),//生成证书的序列号,这里使用随机生成的长整数作为序列号。 new Date(System.currentTimeMillis() - 10000),//证书的生效日期,这里表示当前时间减去10000毫秒。 new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000),//证书的过期日期,这里表示当前时间加上365天的毫秒数。 subject,//再次指定主题。 keyPair.getPublic()//用于签署证书的公钥。 ); // 添加基本约束扩展。这表示该证书可以用作CA(证书颁发机构),因此它可以用于签发其他证书。 certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); //将certBuilder构建的证书签名,然后将其转换为X509Certificate对象。 X509Certificate rootCertificate = new JcaX509CertificateConverter().getCertificate (certBuilder.build(new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC").build(keyPair.getPrivate()))); //进行自我验证。这行代码确保生成的根证书是有效的,即可以被根据根证书签发的其他证书信任 rootCertificate.verify(rootCertificate.getPublicKey()); return rootCertificate; }
4:生成实体的证书请求(CSR)
/** * 生成实体的证书请求(CSR) * * @param entityKeyPair 实体的密钥对(RSA密钥) * @param caCertificate 颁发实体证书的证书颁发机构的证书 * @param caPrivateKey 证书颁发机构的私钥 */ public static X509Certificate generateEntityCertificate(KeyPair entityKeyPair, X509Certificate caCertificate, PrivateKey caPrivateKey) throws Exception { // 创建一个 X500NameBuilder 对象 X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE); // 添加标准的主题信息(例如:CN、OU、O 等) nameBuilder.addRDN(BCStyle.CN, "example.com"); // 服务器的域名或IP地址 nameBuilder.addRDN(BCStyle.OU, "IT Department"); // 组织单位 nameBuilder.addRDN(BCStyle.O, "Example Organization"); // 组织名称 // 创建 X500Name 对象 X500Name subject = nameBuilder.build(); //用于构建X.509实体证书 X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( new X500Name(caCertificate.getSubjectX500Principal().getName()),//颁发实体证书的CA证书的主题作为颁发者(Issuer)。 BigInteger.valueOf(new Random().nextLong()),//证书的序列号,这里使用了一个随机生成的序列号。 new Date(System.currentTimeMillis() - 10000),//证书的有效期起始日期,即当前时间往前推10,000毫秒。 new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000),//证书的有效期结束日期,即当前时间往后推365天。 subject,//前面创建的主题,即实体证书的主题。 entityKeyPair.getPublic()//实体的公钥。 ); // 添加基本约束扩展,将basicConstraints扩展设置为true,并指定BasicConstraints对象,其中false表示这是一个终端实体证书(End Entity Certificate)。 certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); //创建一个X.509实体证书 X509Certificate entityCertificate = 通过JcaContentSignerBuilder创建一个内容签名器(Content Signer),指定签名算法为"SHA256WithRSA"。 new JcaX509CertificateConverter().getCertificate(certBuilder.build( //通过JcaContentSignerBuilder创建一个内容签名器(Content Signer),指定签名算法为"SHA256WithRSA"。 new JcaContentSignerBuilder("SHA256WithRSA").setProvider("BC") .build(caPrivateKey)));//使用CA的私钥(caPrivateKey)对证书进行签名 //使用CA的公钥验证生成的实体证书的签名是否有效。这一步用于确保实体证书由CA签发。 entityCertificate.verify(caCertificate.getPublicKey()); //返回生成的实体证书 return entityCertificate; }
5:保存证书以及保存私钥
/** * 保存证书 */ private static void saveCertificate(X509Certificate certificate, String fileName) throws Exception { FileOutputStream fos = new FileOutputStream(fileName); fos.write(certificate.getEncoded()); fos.close(); } /** * 保存私钥 */ private static void savePrivateKey(PrivateKey privateKey, String fileName) throws Exception { FileOutputStream fos = new FileOutputStream(fileName); fos.write(privateKey.getEncoded()); fos.close(); }
6:将数字证书导入密钥库
/** * 将数字证书导入密钥库 * TODO 修改为存储到数据库中 */ public void createKeyStore(String keystorePassword) throws Exception { //这一行创建一个新的 Java 密钥库对象,并使用 "JKS"(Java KeyStore)作为密钥库类型 KeyStore keyStore = KeyStore.getInstance("JKS"); //这一行初始化密钥库。keystorePassword 是一个字符串,代表密钥库的密码。在这里,null 表示我们还没有加载任何内容到密钥库,因此只是用密码初始化它。 keyStore.load(null, keystorePassword.toCharArray()); //创建一个文件输入流以读取数字证书文件。your_certificate.cer 应该是一个 X.509 数字证书文件的路径。 FileInputStream certificateFile = new FileInputStream("your_certificate.cer"); //使用 Java 的 CertificateFactory 类来读取并解析数字证书文件,然后将其转换为 X509Certificate 对象。X.509 是证书的格式。 X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificateFile); //将解析的数字证书添加到密钥库中,同时指定了一个别名(alias)来标识这个证书。密钥库可以包含多个证书和密钥对,每一个都有一个唯一的别名 keyStore.setCertificateEntry("alias", certificate); //创建一个文件输出流以将密钥库保存到磁盘。your_keystore.jks 是保存密钥库的文件路径 FileOutputStream keystoreFile = new FileOutputStream("your_keystore.jks"); //将初始化后的密钥库保存到文件。密钥库被写入到磁盘,并用密码保护,以确保安全性 keyStore.store(keystoreFile, keystorePassword.toCharArray()); }
7:获取密钥库中的证书
/** * 获取密钥库中的证书 */ public X509Certificate getCertificate(String keystorePassword, String keystorePath) throws Exception { FileInputStream keystoreFile = new FileInputStream(keystorePath); KeyStore keyStore = KeyStore.getInstance("JKS"); //打开密钥库 keyStore.load(keystoreFile, keystorePassword.toCharArray()); // 你在创建密钥库时指定的证书别名,此处假设为 "alias" String certificateAlias = "alias"; return (X509Certificate) keyStore.getCertificate(certificateAlias); }
8:访问密钥库
/** * 访问密钥库 * * @param keystorePassword:用于解锁密钥库的密码。 * @param keystorePath:密钥库的文件路径,即你在创建密钥库时指定的 your_keystore.jks。 */ public KeyStore openKeyStore(String keystorePassword, String keystorePath) throws Exception { FileInputStream keystoreFile = new FileInputStream(keystorePath); KeyStore keyStore = KeyStore.getInstance("JKS"); //打开密钥库 keyStore.load(keystoreFile, keystorePassword.toCharArray()); return keyStore; }
9:签名数据
/** * 签名数据 * 这段代码中所描述的操作是用于签名数据,而不是签名数字证书。具体来说, * 它用服务器的私钥对一段数据进行签名,然后将签名后的数据与服务器的数字证书一起发送给客户端 */ public byte[] getSignatureCertificatePKCS7(String keystorePassword, String keystorePath) throws Exception { //访问密钥库 KeyStore keyStore = openKeyStore(keystorePassword, keystorePath); Path path = Paths.get(keystorePath); // 重新加载密钥库以确保它包含最新的数据 try (InputStream inputStream = Files.newInputStream(path)) { keyStore.load(inputStream, keystorePassword.toCharArray()); } //获取密钥库中的证书 X509Certificate certificate = getCertificate(keystorePassword, keystorePath); //验证数字证书 Boolean verifyCertificate = verificationSignatureCertificate(certificate); Assert.isTrue(verifyCertificate, "数字证书验证不通过"); //获取私钥.在创建密钥库时指定的证书别名,此处假设为 "alias" PrivateKey privateKey = (PrivateKey) keyStore.getKey("alias", keystorePassword.toCharArray()); // 数据要签名的数据 例 String plainText = "Hello, World!"; byte[] plaintextBytes = plainText.getBytes(); //创建 Signature 对象并初始化为使用私钥进行签名: Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); // 更新要签名的数据: signature.update(plaintextBytes); // 进行签名: byte[] signatureBytes = signature.sign(); //将签名证书和签名结果打包为 PKCS#7 格式的数字证书 //类将要签名的原始数据 `plaintextBytes` 封装为可处理的数据类型 `msg`。 CMSTypedData msg = new CMSProcessableByteArray(plaintextBytes); //创建一个空的证书列表 `certList`,并将签名证书 `certificate` 添加到列表中。 List<X509Certificate> certList = new ArrayList<>(); certList.add(certificate); // 类创建一个证书存储对象 `certs`,并将证书列表 `certList` 添加到存储中 JcaCertStore certs = new JcaCertStore(certList); //创建一个 `CMSSignedDataGenerator` 对象 `gen`,用于生成 PKCS#7 格式的数字证书。 CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); //使用 `JcaContentSignerBuilder` 类创建一个 `ContentSigner` 对象 `sha256Signer`,指定签名算法为 "SHA256withRSA",并使用私钥 `privateKey` 进行签名。 ContentSigner sha256Signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey); //使用 `JcaSignerInfoGeneratorBuilder` 类创建一个 `SignerInfoGenerator` 对象,用于生成签名者信息。 gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder( // 使用 `JcaDigestCalculatorProviderBuilder` 类构建一个摘要计算器提供者,并将其与 `sha256Signer` 和 `certificate` 绑定。 new JcaDigestCalculatorProviderBuilder().build()).build(sha256Signer, new JcaX509CertificateHolder(certificate))); //将生成的 `SignerInfoGenerator` 对象和证书存储对象 `certs` 添加到 `gen` 中。 gen.addCertificates(certs); //调用 `gen.generate(msg, true)` 方法生成 PKCS#7 格式的数字证书,并将结果存储在 `sigData` 中。 CMSSignedData sigData = gen.generate(msg, true); // 使用 `sigData.getEncoded()` 获取数字证书的字节数组,并将其作为方法的返回值。 return sigData.getEncoded(); }
10:验证数字签名证书
/** * 验证数字签名证书, * 验证前需要解密该数字证书,由于此处设计的数字证书是从数据库中得到的,因此可不解密。 * 若从前端得到数字证书,前端需加密之后,后端解密 * 这段代码用于验证服务器上的数字证书。具体而言, * 它验证了给定的 X.509 数字证书(x509Certificate)是否有效和受信任。这是数字证书验证的一部分,通常在建立安全的通信连接时使用,以确保服务器的身份和通信的机密性。 * * @param x509Certificate 需要验证的证书 */ public Boolean verificationSignatureCertificate(X509Certificate x509Certificate) { // 注册 Bouncy Castle 安全提供程序 try { // 注册 Bouncy Castle 安全提供程序 Security.addProvider(new BouncyCastleProvider()); // 加载根证书(CA证书),打开名为 "root_certificate.crt" 的文件。这个文件应该包含根证书 FileInputStream rootCertFile = new FileInputStream("root_certificate.crt"); //使用 CertificateFactory.getInstance("X.509") 指定了证书的类型,这里是 X.509 证书。 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); //从文件中读取并解析 X.509 数字证书,然后将其转换为 X509Certificate 对象。这个对象现在包含了根证书的信息,可以用于后续的证书验证。 X509Certificate rootCert = (X509Certificate) certificateFactory.generateCertificate(rootCertFile); // 创建一个 TrustAnchor,表示可信任的根证书 TrustAnchor trustAnchor = new TrustAnchor(rootCert, null); // 创建一个 Set 集合来保存可信任的 TrustAnchor 对象。这是因为在实际的证书验证中,可能有多个根证书,每个根证书都需要一个 TrustAnchor 对象。 Set<TrustAnchor> trustAnchors = new HashSet<>(); //将 TrustAnchor 对象添加到 trustAnchors 集合中 trustAnchors.add(trustAnchor); //创建 PKIXParameters 对象,该对象用于配置和初始化 PKI(Public Key Infrastructure)验证。 // 这里将 trustAnchors 集合传递给 PKIXParameters,以告诉验证系统哪些根证书是可信任的。 PKIXParameters params = new PKIXParameters(trustAnchors); // 可以选择启用或禁用证书吊销检查 params.setRevocationEnabled(true); //这是 Java 中的类,用于验证数字证书的路径或链。路径验证是确保从根证书到目标证书的一系列数字证书是完整和有效的过程。 //getInstance("PKIX"):通过 getInstance 方法获取 CertPathValidator 的实例,并指定验证类型为 "PKIX"。 // "PKIX" 表示 Public Key Infrastructure for X.509,是一种常见的数字证书验证标准。这种验证方法用于确保数字证书链的合法性和完整性。 CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX"); //这是 Java 中的接口,用于表示一组数字证书的路径。这是 CertificateFactory 类的一个实例,用于解析和处理数字证书。 //使用 generateCertPath 方法创建证书路径,这个方法接受一个 Set,其中包含要验证的数字证书 x509Certificate List<X509Certificate> certList = new ArrayList<>(); certList.add(x509Certificate); CertPath certPath = certificateFactory.generateCertPath(certList); // 验证证书链 CertPathValidatorResult result = certPathValidator.validate(certPath, params); // 公钥验证证书签名 x509Certificate.verify(rootCert.getPublicKey()); // 验证证书有效期 Date currentDate = new Date(); x509Certificate.checkValidity(currentDate); //证书吊销检查 params.setRevocationEnabled(true); // 验证主体和颁发者匹配 Assert.isTrue(x509Certificate.getSubjectX500Principal().equals(x509Certificate.getIssuerX500Principal()), "主体和颁发者不匹配"); // 如果验证通过,返回 true,若存在异常会自动抛出 return true; } catch (Exception e) { // 验证失败,返回 false return false; } }
11:RSA公钥加密签名过的数字证书,将其AES加密后返回给前端
/** * 验证数字签名证书, * 验证前需要解密该数字证书,由于此处设计的数字证书是从数据库中得到的,因此可不解密。 * 若从前端得到数字证书,前端需加密之后,后端解密 * 这段代码用于验证服务器上的数字证书。具体而言, * 它验证了给定的 X.509 数字证书(x509Certificate)是否有效和受信任。这是数字证书验证的一部分,通常在建立安全的通信连接时使用,以确保服务器的身份和通信的机密性。 * * @param x509Certificate 需要验证的证书 */ public Boolean verificationSignatureCertificate(X509Certificate x509Certificate) { // 注册 Bouncy Castle 安全提供程序 try { // 注册 Bouncy Castle 安全提供程序 Security.addProvider(new BouncyCastleProvider()); // 加载根证书(CA证书),打开名为 "root_certificate.crt" 的文件。这个文件应该包含根证书 FileInputStream rootCertFile = new FileInputStream("root_certificate.crt"); //使用 CertificateFactory.getInstance("X.509") 指定了证书的类型,这里是 X.509 证书。 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); //从文件中读取并解析 X.509 数字证书,然后将其转换为 X509Certificate 对象。这个对象现在包含了根证书的信息,可以用于后续的证书验证。 X509Certificate rootCert = (X509Certificate) certificateFactory.generateCertificate(rootCertFile); // 创建一个 TrustAnchor,表示可信任的根证书 TrustAnchor trustAnchor = new TrustAnchor(rootCert, null); // 创建一个 Set 集合来保存可信任的 TrustAnchor 对象。这是因为在实际的证书验证中,可能有多个根证书,每个根证书都需要一个 TrustAnchor 对象。 Set<TrustAnchor> trustAnchors = new HashSet<>(); //将 TrustAnchor 对象添加到 trustAnchors 集合中 trustAnchors.add(trustAnchor); //创建 PKIXParameters 对象,该对象用于配置和初始化 PKI(Public Key Infrastructure)验证。 // 这里将 trustAnchors 集合传递给 PKIXParameters,以告诉验证系统哪些根证书是可信任的。 PKIXParameters params = new PKIXParameters(trustAnchors); // 可以选择启用或禁用证书吊销检查 params.setRevocationEnabled(true); //这是 Java 中的类,用于验证数字证书的路径或链。路径验证是确保从根证书到目标证书的一系列数字证书是完整和有效的过程。 //getInstance("PKIX"):通过 getInstance 方法获取 CertPathValidator 的实例,并指定验证类型为 "PKIX"。 // "PKIX" 表示 Public Key Infrastructure for X.509,是一种常见的数字证书验证标准。这种验证方法用于确保数字证书链的合法性和完整性。 CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX"); //这是 Java 中的接口,用于表示一组数字证书的路径。这是 CertificateFactory 类的一个实例,用于解析和处理数字证书。 //使用 generateCertPath 方法创建证书路径,这个方法接受一个 Set,其中包含要验证的数字证书 x509Certificate List<X509Certificate> certList = new ArrayList<>(); certList.add(x509Certificate); CertPath certPath = certificateFactory.generateCertPath(certList); // 验证证书链 CertPathValidatorResult result = certPathValidator.validate(certPath, params); // 公钥验证证书签名 x509Certificate.verify(rootCert.getPublicKey()); // 验证证书有效期 Date currentDate = new Date(); x509Certificate.checkValidity(currentDate); //证书吊销检查 params.setRevocationEnabled(true); // 验证主体和颁发者匹配 Assert.isTrue(x509Certificate.getSubjectX500Principal().equals(x509Certificate.getIssuerX500Principal()), "主体和颁发者不匹配"); // 如果验证通过,返回 true,若存在异常会自动抛出 return true; } catch (Exception e) { // 验证失败,返回 false return false; } }
12:发送CSR到CA
/** * 发送CSR到CA */ public static int sendCSRToCA(X509Certificate csr) throws IOException { // 构建请求参数 String requestUrl = "https://www.webnic.cc/zh_cn/"; // 替换为CA提供的URL String requestBody = "csr=" + urlEncode(csr.toString()); // 构建请求体 // 发送POST请求 URL url = new URL(requestUrl); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setDoOutput(true); // 将请求体写入输出流 try (OutputStream outputStream = connection.getOutputStream()) { byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8); outputStream.write(requestBodyBytes, 0, requestBodyBytes.length); } // 获取响应结果 String responseMessage = connection.getResponseMessage();//方法获取响应消息。 //获取响应体的输入流,并将其传递给readResponseToString方法以获取响应体的字符串表示形式 String responseBody = readResponseToString(connection.getInputStream()); //方法获取HTTP响应状态码 return connection.getResponseCode(); }
13:从输入流中逐行读取响应体的内容,将其存储在StringBuilder中,并返回字符串表示形式
/** * 从输入流中逐行读取响应体的内容,将其存储在StringBuilder中,并返回字符串表示形式 */ private static String readResponseToString(InputStream inputStream) throws IOException { StringBuilder response = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { String line; while ((line = reader.readLine()) != null) { response.append(line).append("\n"); } } return response.toString(); }
14:对给定的值进行URL编码,使用URLEncoder类的encode方法实现
/** * 对给定的值进行URL编码,使用URLEncoder类的encode方法实现 */ private static String urlEncode(String value) throws UnsupportedEncodingException { return java.net.URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); } public static URL createURL(String urlString) throws MalformedURLException, MalformedURLException { return new URL(urlString); } private URLHandler urlHandler; public CACertificateTest(URLHandler urlHandler) { this.urlHandler = urlHandler; }
二 测试代码
1:依赖包
import cn.ueclzsz.im.service.ServiceApplication; import cn.ueclzsz.im.service.rsa.ca.URLHandler; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.junit.Assert.assertNotNull; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.security.KeyPairGenerator; import java.security.KeyPair; @SpringBootTest(classes = ServiceApplication.class) @RunWith(SpringRunner.class) @PrepareForTest({CACertificateTest.class, java.net.URL.class}) public class TestCA { private KeyPair rootKeyPair; private KeyPair entityKeyPair; private X509Certificate rootCertificate; private PrivateKey rootPrivateKey; @Test public void testGenerateRootCertificate() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); rootKeyPair = keyPairGenerator.generateKeyPair(); // 赋值给类的成员变量 X509Certificate rootCertificate = generateRootCertificate(rootKeyPair); assertNotNull(rootCertificate); } @Test public void testGenerateEntityCertificate() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair entityKeyPair = keyPairGenerator.generateKeyPair(); // 生成根密钥对 KeyPair rootKeyPair = keyPairGenerator.generateKeyPair(); // Mock rootCertificate and rootKeyPair for testing X509Certificate rootCertificate = generateRootCertificate(rootKeyPair); // 直接使用类的成员变量 PrivateKey rootPrivateKey = rootKeyPair.getPrivate(); // 直接使用类的成员变量 X509Certificate entityCertificate = generateEntityCertificate(entityKeyPair, rootCertificate, rootPrivateKey); assertNotNull(entityCertificate); // Add assertions to validate the generated entityCertificate, e.g., subject, basic constraints, etc. } @Test public void testSendCSRToCA() throws Exception { URLHandler mockURLHandler = Mockito.mock(URLHandler.class); HttpURLConnection mockConnection = Mockito.mock(HttpURLConnection.class); Mockito.when(mockURLHandler.openConnection()).thenReturn(mockConnection); Mockito.when(mockConnection.getResponseCode()).thenReturn(200); Mockito.when(mockConnection.getInputStream()).thenReturn(new ByteArrayInputStream("ResponseData".getBytes())); CACertificateTest caCertificate = new CACertificateTest(mockURLHandler); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair entityKeyPair = keyPairGenerator.generateKeyPair(); // 生成根密钥对 KeyPair rootKeyPair = keyPairGenerator.generateKeyPair(); // Mock rootCertificate and rootKeyPair for testing X509Certificate rootCertificate = generateRootCertificate(rootKeyPair); // 直接使用类的成员变量 PrivateKey rootPrivateKey = rootKeyPair.getPrivate(); // 直接使用类的成员变量 // Test sendCSRToCA method assertNotNull(entityKeyPair); assertNotNull(rootCertificate); assertNotNull(rootPrivateKey); X509Certificate entityCertificate = generateEntityCertificate(entityKeyPair, rootCertificate, rootPrivateKey); int responseCode = 0; try { // 进行网络请求 responseCode = caCertificate.sendCSRToCA(entityCertificate); } catch (IOException e) { // 处理 IOException e.printStackTrace(); // 可以记录错误或提供用户友好的错误消息 } // Assert the response code assertEquals(200, responseCode); } }