非对称签名验签

This commit is contained in:
liulu
2024-12-23 15:38:09 +08:00
parent 7fe6482983
commit 61f94f39cb
10 changed files with 555 additions and 3 deletions
@@ -2,6 +2,8 @@ package com.sunyard.chsm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sunyard.chsm.enums.KeyUsage;
import com.sunyard.chsm.model.Subject;
import com.sunyard.chsm.model.entity.AppCert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.util.Assert;
@@ -28,5 +30,18 @@ public interface AppCertMapper extends BaseMapper<AppCert> {
return certs.iterator().next();
}
default AppCert selectSignBySubject(String dn) {
Assert.hasText(dn, "证书序列号不能为空");
String subject = Subject.fromDN(dn).getDN();
List<AppCert> certs = selectList(new LambdaQueryWrapper<AppCert>()
.eq(AppCert::getSubject, subject)
.eq(AppCert::getCertType, KeyUsage.SIGN_VERIFY.getCode())
);
if (CollectionUtils.isEmpty(certs)) {
return null;
}
return certs.iterator().next();
}
}
@@ -11,6 +11,7 @@ import com.sunyard.chsm.mapper.AppCertMapper;
import com.sunyard.chsm.mapper.ApplicationMapper;
import com.sunyard.chsm.mapper.KeyInfoMapper;
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
import com.sunyard.chsm.model.Subject;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.model.entity.AppCert;
import com.sunyard.chsm.model.entity.Application;
@@ -229,7 +230,8 @@ public class AppCertServiceImpl implements AppCertService {
cert.setCertText(importCert.getCertText());
cert.setVersion(String.valueOf(x509Cert.getVersion()));
cert.setSubject(x509Cert.getSubjectX500Principal().getName());
Subject subject = Subject.fromDN(x509Cert.getSubjectX500Principal().getName());
cert.setSubject(subject.getDN());
cert.setSerialNumber(x509Cert.getSerialNumber().toString());
cert.setIssuerDn(x509Cert.getIssuerX500Principal().getName());
cert.setNotBefore(x509Cert.getNotBefore());
@@ -0,0 +1,283 @@
package com.sunyard.chsm.utils.gm.cert;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
public class SM2X509CertMaker {
private static enum CertLevel {
RootCA,
SubCA,
EndEntity
} // class CertLevel
public static final String SIGN_ALGO_SM3WITHSM2 = "SM3withSM2";
private long certExpire;
private X500Name issuerDN;
// private CertSNAllocator snAllocator;
private KeyPair issuerKeyPair;
/**
* @param issuerKeyPair 证书颁发者的密钥对。
* 其实一般的CA的私钥都是要严格保护的。
* 一般CA的私钥都会放在加密卡/加密机里,证书的签名由加密卡/加密机完成。
* 这里仅是为了演示BC库签发证书的用法,所以暂时不作太多要求。
* @param certExpire 证书有效时间,单位毫秒
* @param issuer 证书颁发者信息
* @param snAllocator 维护/分配证书序列号的实例,证书序列号应该递增且不重复
*/
public SM2X509CertMaker(KeyPair issuerKeyPair, long certExpire, X500Name issuer)
// , CertSNAllocator snAllocator)
{
this.issuerKeyPair = issuerKeyPair;
this.certExpire = certExpire;
this.issuerDN = issuer;
// this.snAllocator = snAllocator;
}
/**
* 生成根CA证书
*
* @param csr CSR
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeRootCACert(byte[] csr)
throws Exception {
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
return makeCertificate(CertLevel.RootCA, null, csr, usage, null);
}
/**
* 生成SubCA证书
*
* @param csr CSR
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeSubCACert(byte[] csr)
throws Exception {
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign);
return makeCertificate(CertLevel.SubCA, 0, csr, usage, null);
}
/**
* 生成SSL用户证书
*
* @param csr CSR
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeSSLEndEntityCert(byte[] csr)
throws Exception {
return makeEndEntityCert(csr,
new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth});
}
/**
* 生成用户证书
*
* @param csr CSR
* @param extendedKeyUsages 扩展指数用途。
* @return 新的证书
* @throws Exception 如果错误发生
*/
public X509Certificate makeEndEntityCert(byte[] csr,
KeyPurposeId[] extendedKeyUsages)
throws Exception {
KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyAgreement
| KeyUsage.dataEncipherment | KeyUsage.keyEncipherment);
return makeCertificate(CertLevel.EndEntity, null, csr, usage, extendedKeyUsages);
}
/**
* @param isCA 是否是颁发给CA的证书
* @param keyUsage 证书用途
* @param csr CSR
* @return
* @throws Exception
*/
private X509Certificate makeCertificate(CertLevel certLevel, Integer pathLenConstrain,
byte[] csr, KeyUsage keyUsage, KeyPurposeId[] extendedKeyUsages)
throws Exception {
if (certLevel == CertLevel.EndEntity) {
if (keyUsage.hasUsages(KeyUsage.keyCertSign)) {
throw new IllegalArgumentException(
"keyusage keyCertSign is not allowed in EndEntity Certificate");
}
}
PKCS10CertificationRequest request = new PKCS10CertificationRequest(csr);
SubjectPublicKeyInfo subPub = request.getSubjectPublicKeyInfo();
PrivateKey issPriv = issuerKeyPair.getPrivate();
PublicKey issPub = issuerKeyPair.getPublic();
X500Name subject = request.getSubject();
String email = null;
String commonName = null;
/*
* RFC 5280 §4.2.1.6 Subject
* Conforming implementations generating new certificates with
* electronic mail addresses MUST use the rfc822Name in the subject
* alternative name extension (Section 4.2.1.6) to describe such
* identities. Simultaneous inclusion of the emailAddress attribute in
* the subject distinguished name to support legacy implementations is
* deprecated but permitted.
*/
RDN[] rdns = subject.getRDNs();
List<RDN> newRdns = new ArrayList<>(rdns.length);
for (int i = 0; i < rdns.length; i++) {
RDN rdn = rdns[i];
AttributeTypeAndValue atv = rdn.getFirst();
ASN1ObjectIdentifier type = atv.getType();
if (BCStyle.EmailAddress.equals(type)) {
email = IETFUtils.valueToString(atv.getValue());
} else {
if (BCStyle.CN.equals(type)) {
commonName = IETFUtils.valueToString(atv.getValue());
}
newRdns.add(rdn);
}
}
List<GeneralName> subjectAltNames = new LinkedList<>();
if (email != null) {
subject = new X500Name(newRdns.toArray(new RDN[0]));
subjectAltNames.add(
new GeneralName(GeneralName.rfc822Name,
new DERIA5String(email, true)));
}
boolean selfSignedEECert = false;
switch (certLevel) {
case RootCA:
if (issuerDN.equals(subject)) {
subject = issuerDN;
} else {
throw new IllegalArgumentException("subject != issuer for certLevel " + CertLevel.RootCA);
}
break;
case SubCA:
if (issuerDN.equals(subject)) {
throw new IllegalArgumentException(
"subject MUST not equals issuer for certLevel " + certLevel);
}
break;
default:
if (issuerDN.equals(subject)) {
selfSignedEECert = true;
subject = issuerDN;
}
}
// BigInteger serialNumber = snAllocator.nextSerialNumber();
BigInteger serialNumber = new BigInteger(IdWorker.getIdStr());
Date notBefore = new Date();
Date notAfter = new Date(notBefore.getTime() + certExpire);
X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
issuerDN, serialNumber,
notBefore, notAfter,
subject, subPub);
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
v3CertGen.addExtension(Extension.subjectKeyIdentifier, false,
extUtils.createSubjectKeyIdentifier(subPub));
if (certLevel != CertLevel.RootCA && !selfSignedEECert) {
v3CertGen.addExtension(Extension.authorityKeyIdentifier, false,
extUtils.createAuthorityKeyIdentifier(SubjectPublicKeyInfo.getInstance(issPub.getEncoded())));
}
// RFC 5280 §4.2.1.9 Basic Constraints:
// Conforming CAs MUST include this extension in all CA certificates
// that contain public keys used to validate digital signatures on
// certificates and MUST mark the extension as critical in such
// certificates.
BasicConstraints basicConstraints;
if (certLevel == CertLevel.EndEntity) {
basicConstraints = new BasicConstraints(false);
} else {
basicConstraints = pathLenConstrain == null
? new BasicConstraints(true) : new BasicConstraints(pathLenConstrain.intValue());
}
v3CertGen.addExtension(Extension.basicConstraints, true, basicConstraints);
// RFC 5280 §4.2.1.3 Key Usage: When present, conforming CAs SHOULD mark this extension as critical.
v3CertGen.addExtension(Extension.keyUsage, true, keyUsage);
if (extendedKeyUsages != null) {
ExtendedKeyUsage xku = new ExtendedKeyUsage(extendedKeyUsages);
v3CertGen.addExtension(Extension.extendedKeyUsage, false, xku);
boolean forSSLServer = false;
for (KeyPurposeId purposeId : extendedKeyUsages) {
if (KeyPurposeId.id_kp_serverAuth.equals(purposeId)) {
forSSLServer = true;
break;
}
}
if (forSSLServer) {
if (commonName == null) {
throw new IllegalArgumentException("commonName must not be null");
}
GeneralName name = new GeneralName(GeneralName.dNSName,
new DERIA5String(commonName, true));
subjectAltNames.add(name);
}
}
if (!subjectAltNames.isEmpty()) {
v3CertGen.addExtension(Extension.subjectAlternativeName, false,
new GeneralNames(subjectAltNames.toArray(new GeneralName[0])));
}
JcaContentSignerBuilder contentSignerBuilder = makeContentSignerBuilder(issPub);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
.getCertificate(v3CertGen.build(contentSignerBuilder.build(issPriv)));
cert.verify(issPub);
return cert;
}
private JcaContentSignerBuilder makeContentSignerBuilder(PublicKey issPub) throws Exception {
if (issPub.getAlgorithm().equals("EC")) {
JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(SIGN_ALGO_SM3WITHSM2);
contentSignerBuilder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
return contentSignerBuilder;
}
throw new Exception("Unsupported PublicKey Algorithm:" + issPub.getAlgorithm());
}
}