新增节点节点类型与证书生成算法
This commit is contained in:
parent
dbc06c4357
commit
244ce7832f
7
pom.xml
7
pom.xml
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -204,6 +204,11 @@ public class NetNode extends TenantEntity {
|
|||
*/
|
||||
private Date reportStatusTime;
|
||||
|
||||
/**
|
||||
* 节点软件支撑
|
||||
*/
|
||||
private Integer nodeSoftType;
|
||||
|
||||
/**
|
||||
* 删除标记
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -161,5 +161,9 @@ public class NetNodeBo extends BaseEntity {
|
|||
*/
|
||||
private String serviceName;
|
||||
|
||||
/**
|
||||
* 节点软件支撑
|
||||
*/
|
||||
private Integer nodeSoftType;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,5 +249,9 @@ public class NetNodeVo implements Serializable {
|
|||
*/
|
||||
private Boolean onlineStatus;
|
||||
|
||||
/**
|
||||
* 节点软件支撑
|
||||
*/
|
||||
private Integer nodeSoftType;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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=== 验证通过 ===");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue