新增节点节点类型与证书生成算法

This commit is contained in:
lys 2026-03-17 12:16:06 +08:00
parent dbc06c4357
commit 244ce7832f
7 changed files with 370 additions and 0 deletions

View File

@ -303,6 +303,13 @@
<version>${bouncycastle.version}</version>
</dependency>
<!-- BouncyCastle PKIX证书生成 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>

View File

@ -90,6 +90,12 @@
<artifactId>ruoyi-common-encrypt</artifactId>
</dependency>
<!-- BouncyCastle PKIX 证书生成 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15to18</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-websocket</artifactId>

View File

@ -204,6 +204,11 @@ public class NetNode extends TenantEntity {
*/
private Date reportStatusTime;
/**
* 节点软件支撑
*/
private Integer nodeSoftType;
/**
* 删除标记
*/

View File

@ -161,5 +161,9 @@ public class NetNodeBo extends BaseEntity {
*/
private String serviceName;
/**
* 节点软件支撑
*/
private Integer nodeSoftType;
}

View File

@ -249,5 +249,9 @@ public class NetNodeVo implements Serializable {
*/
private Boolean onlineStatus;
/**
* 节点软件支撑
*/
private Integer nodeSoftType;
}

View File

@ -0,0 +1,321 @@
package org.dromara.net.util;
import lombok.Data;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
/**
* 密钥证书生成工具类
* <p>
* 支持
* - JWT RSA密钥对生成
* - CA ECDSA证书生成
* - 节点ECDSA证书生成由CA签发
* </p>
*
* @author ruoyi
*/
public class KeyCertGeneratorUtil {
private static final String RSA_ALGORITHM = "RSA";
private static final String EC_ALGORITHM = "EC";
private static final String SIGNATURE_ALGORITHM_RSA = "SHA256withRSA";
private static final String SIGNATURE_ALGORITHM_EC = "SHA256withECDSA";
// ECDSA P-256 曲线名称
private static final String EC_CURVE_NAME = "secp256r1";
/**
* 生成完整的密钥证书集合
*
* @param jwtKeySize JWT RSA密钥长度如2048
* @param caCommonName CA证书通用名称
* @param nodeCommonName 节点证书通用名称
* @param certYears 证书有效期
* @return 密钥证书结果
*/
public static KeyCertResult generateAll(int jwtKeySize, String caCommonName, String nodeCommonName, int certYears) throws Exception {
KeyCertResult result = new KeyCertResult();
// 1. 生成JWT RSA密钥对
KeyPair jwtKeyPair = generateRsaKeyPair(jwtKeySize);
result.setJwtPublicKey(exportPublicKeyToPem(jwtKeyPair.getPublic()));
result.setJwtPrivateKey(exportPrivateKeyToPem(jwtKeyPair.getPrivate()));
// 2. 生成CA ECDSA密钥对和自签名证书
KeyPair caKeyPair = generateEcKeyPair();
X509Certificate caCert = generateCaCertificate(caKeyPair, caCommonName, certYears);
result.setCaCertPem(exportCertificateToPem(caCert));
result.setCaPrivateKey(exportPrivateKeyToPem(caKeyPair.getPrivate()));
// 3. 生成节点ECDSA密钥对并由CA签发证书
KeyPair nodeKeyPair = generateEcKeyPair();
X509Certificate nodeCert = generateNodeCertificate(nodeKeyPair, caKeyPair.getPrivate(), caCert, nodeCommonName, certYears);
result.setNodeCertPem(exportCertificateToPem(nodeCert));
result.setNodePrivateKey(exportPrivateKeyToPem(nodeKeyPair.getPrivate()));
return result;
}
/**
* 生成完整的密钥证书集合使用默认参数
*
* @param commonNamePrefix 通用名称前缀
* @return 密钥证书结果
*/
public static KeyCertResult generateAll(String commonNamePrefix) throws Exception {
String uniqueId = generateUniqueId();
String caCommonName = commonNamePrefix + "_" + uniqueId;
String nodeCommonName = commonNamePrefix + "_" + uniqueId;
return generateAll(2048, caCommonName, nodeCommonName, 3);
}
/**
* 生成完整的密钥证书集合使用默认参数和随机前缀
*
* @return 密钥证书结果
*/
public static KeyCertResult generateAll() throws Exception {
return generateAll("node");
}
// ==================== RSA密钥对生成 ====================
/**
* 生成RSA密钥对
*
* @param keySize 密钥长度如2048
* @return RSA密钥对
*/
public static KeyPair generateRsaKeyPair(int keySize) throws NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyGen.initialize(keySize, new SecureRandom());
return keyGen.generateKeyPair();
}
/**
* 生成RSA 2048密钥对
*
* @return RSA密钥对
*/
public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException {
return generateRsaKeyPair(2048);
}
// ==================== ECDSA密钥对生成 ====================
/**
* 生成ECDSA P-256密钥对
*
* @return ECDSA密钥对
*/
public static KeyPair generateEcKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(EC_ALGORITHM);
keyGen.initialize(new java.security.spec.ECGenParameterSpec(EC_CURVE_NAME), new SecureRandom());
return keyGen.generateKeyPair();
}
// ==================== 证书生成 ====================
/**
* 生成CA自签名证书
*
* @param caKeyPair CA密钥对
* @param commonName 通用名称
* @param years 有效期
* @return CA证书
*/
public static X509Certificate generateCaCertificate(KeyPair caKeyPair, String commonName, int years) throws Exception {
Instant now = Instant.now();
// Instant不支持YEARS使用天数计算1年=365天
Instant notAfter = now.plus(365L * years, ChronoUnit.DAYS);
X500Name issuer = new X500Name("CN=" + commonName);
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
issuer,
serial,
Date.from(now),
Date.from(notAfter),
issuer,
caKeyPair.getPublic()
);
// 添加基本约束CA证书
certBuilder.addExtension(
Extension.basicConstraints,
true,
new BasicConstraints(true)
);
// 添加密钥用法
certBuilder.addExtension(
Extension.keyUsage,
true,
new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)
);
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM_EC);
ContentSigner signer = signerBuilder.build(caKeyPair.getPrivate());
X509CertificateHolder certHolder = certBuilder.build(signer);
return new JcaX509CertificateConverter().getCertificate(certHolder);
}
/**
* 生成节点证书由CA签发
*
* @param nodeKeyPair 节点密钥对
* @param caPrivateKey CA私钥
* @param caCertificate CA证书
* @param commonName 节点通用名称
* @param years 有效期
* @return 节点证书
*/
public static X509Certificate generateNodeCertificate(KeyPair nodeKeyPair, PrivateKey caPrivateKey,
X509Certificate caCertificate, String commonName, int years) throws Exception {
Instant now = Instant.now();
// Instant不支持YEARS使用天数计算1年=365天
Instant notAfter = now.plus(365L * years, ChronoUnit.DAYS);
X500Name issuer = X500Name.getInstance(caCertificate.getSubjectX500Principal().getEncoded());
X500Name subject = new X500Name("CN=" + commonName);
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis() + (long)(Math.random() * 10000));
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
issuer,
serial,
Date.from(now),
Date.from(notAfter),
subject,
nodeKeyPair.getPublic()
);
// 添加基本约束非CA证书
certBuilder.addExtension(
Extension.basicConstraints,
true,
new BasicConstraints(false)
);
// 添加密钥用法
certBuilder.addExtension(
Extension.keyUsage,
true,
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)
);
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM_EC);
ContentSigner signer = signerBuilder.build(caPrivateKey);
X509CertificateHolder certHolder = certBuilder.build(signer);
return new JcaX509CertificateConverter().getCertificate(certHolder);
}
// ==================== PEM导出 ====================
/**
* 导出公钥为PEM格式
*
* @param publicKey 公钥
* @return PEM格式字符串
*/
public static String exportPublicKeyToPem(PublicKey publicKey) {
byte[] encoded = publicKey.getEncoded();
String base64 = java.util.Base64.getEncoder().encodeToString(encoded);
return "-----BEGIN PUBLIC KEY-----\n" + base64 + "\n-----END PUBLIC KEY-----";
}
/**
* 导出私钥为PEM格式PKCS#8
*
* @param privateKey 私钥
* @return PEM格式字符串
*/
public static String exportPrivateKeyToPem(PrivateKey privateKey) {
byte[] encoded = privateKey.getEncoded();
String base64 = java.util.Base64.getEncoder().encodeToString(encoded);
return "-----BEGIN PRIVATE KEY-----\n" + base64 + "\n-----END PRIVATE KEY-----";
}
/**
* 导出证书为PEM格式
*
* @param certificate 证书
* @return PEM格式字符串
*/
public static String exportCertificateToPem(X509Certificate certificate) throws Exception {
byte[] encoded = certificate.getEncoded();
String base64 = java.util.Base64.getEncoder().encodeToString(encoded);
return "-----BEGIN CERTIFICATE-----\n" + base64 + "\n-----END CERTIFICATE-----";
}
// ==================== 辅助方法 ====================
/**
* 生成唯一ID
*
* @return 唯一ID字符串
*/
private static String generateUniqueId() {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
StringBuilder sb = new StringBuilder();
SecureRandom random = new SecureRandom();
for (int i = 0; i < 16; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
/**
* 密钥证书结果
*/
@Data
public static class KeyCertResult {
/**
* JWT公钥PEM格式
*/
private String jwtPublicKey;
/**
* JWT私钥PEM格式
*/
private String jwtPrivateKey;
/**
* CA证书PEM格式
*/
private String caCertPem;
/**
* CA私钥PEM格式
*/
private String caPrivateKey;
/**
* 节点证书PEM格式
*/
private String nodeCertPem;
/**
* 节点私钥PEM格式
*/
private String nodePrivateKey;
}
}

View File

@ -0,0 +1,23 @@
import cn.hutool.json.JSONUtil;
import org.dromara.net.util.KeyCertGeneratorUtil;
public class KeyCertGeneratorUtilTest {
public static void main(String[] args) throws Exception {
// 使用默认参数生成
KeyCertGeneratorUtil.KeyCertResult result = KeyCertGeneratorUtil.generateAll();
System.out.println("=== 生成的密钥证书 ===");
System.out.println(JSONUtil.toJsonPrettyStr(result));
// 验证所有字段都不为空
assert result.getJwtPublicKey() != null && !result.getJwtPublicKey().isEmpty();
assert result.getJwtPrivateKey() != null && !result.getJwtPrivateKey().isEmpty();
assert result.getCaCertPem() != null && !result.getCaCertPem().isEmpty();
assert result.getCaPrivateKey() != null && !result.getCaPrivateKey().isEmpty();
assert result.getNodeCertPem() != null && !result.getNodeCertPem().isEmpty();
assert result.getNodePrivateKey() != null && !result.getNodePrivateKey().isEmpty();
System.out.println("\n=== 验证通过 ===");
}
}