非对称签名验签
This commit is contained in:
parent
7fe6482983
commit
61f94f39cb
@ -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());
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.sunyard.chsm.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class AsymSignP7Req {
|
||||
|
||||
/**
|
||||
* 签名证书的主题
|
||||
*/
|
||||
@NotBlank(message = "签名证书不能为空")
|
||||
private String subject;
|
||||
|
||||
// 原文,使用Base64编码
|
||||
@NotBlank(message = "原文不能为空")
|
||||
private String plainData;
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.sunyard.chsm.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AsymSignP7Resp {
|
||||
|
||||
// 签名值 Base64编码
|
||||
private String signData;
|
||||
}
|
@ -12,7 +12,7 @@ public class AsymSignRawReq {
|
||||
@NotNull(message = "密钥ID不能为空")
|
||||
private Long keyId;
|
||||
|
||||
// 明文,使用Base64编码
|
||||
// 原文,使用Base64编码
|
||||
@NotBlank(message = "明文不能为空")
|
||||
private String plainData;
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.sunyard.chsm.param;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Data
|
||||
public class AsymVerifyP7Req {
|
||||
|
||||
/**
|
||||
* 签名证书的主题
|
||||
*/
|
||||
// @NotBlank(message = "签名证书不能为空")
|
||||
// private String subject;
|
||||
|
||||
// 原文,使用Base64编码
|
||||
@NotBlank(message = "明文不能为空")
|
||||
private String plainData;
|
||||
|
||||
// 签名值, 使用Base64编码
|
||||
private String signData;
|
||||
|
||||
}
|
@ -23,7 +23,7 @@ public class AsymVerifyRawReq {
|
||||
@NotBlank(message = "密文不能为空")
|
||||
private String signData;
|
||||
|
||||
// 明文,使用Base64编码
|
||||
// 原文,使用Base64编码
|
||||
@NotBlank(message = "明文不能为空")
|
||||
private String plainData;
|
||||
|
||||
|
@ -7,8 +7,11 @@ import com.sunyard.chsm.param.AsymDecryptReq;
|
||||
import com.sunyard.chsm.param.AsymDecryptResp;
|
||||
import com.sunyard.chsm.param.AsymEncryptReq;
|
||||
import com.sunyard.chsm.param.AsymEncryptResp;
|
||||
import com.sunyard.chsm.param.AsymSignP7Req;
|
||||
import com.sunyard.chsm.param.AsymSignP7Resp;
|
||||
import com.sunyard.chsm.param.AsymSignRawReq;
|
||||
import com.sunyard.chsm.param.AsymSignRawResp;
|
||||
import com.sunyard.chsm.param.AsymVerifyP7Req;
|
||||
import com.sunyard.chsm.param.AsymVerifyRawReq;
|
||||
import com.sunyard.chsm.param.VerifyResp;
|
||||
import com.sunyard.chsm.service.AsymKeyService;
|
||||
@ -85,5 +88,59 @@ public class AsymKeyController {
|
||||
return R.data(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* P7 Attach签名
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/sign/P7Attach")
|
||||
@AuthCode(AuthCodeConst.sign_P7Attach)
|
||||
public R<AsymSignP7Resp> signP7Attach(@Valid @RequestBody AsymSignP7Req req) {
|
||||
AsymSignP7Resp resp = asymKeyService.signP7Attach(req);
|
||||
return R.data(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* P7 Attach验签
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/verify/P7Attach")
|
||||
@AuthCode(AuthCodeConst.verify_P7Attach)
|
||||
public R<VerifyResp> verifyP7Attach(@Valid @RequestBody AsymVerifyP7Req req) {
|
||||
VerifyResp resp = asymKeyService.verifyP7Attach(req);
|
||||
return R.data(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* P7 Detach签名
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/sign/P7Detach")
|
||||
@AuthCode(AuthCodeConst.sign_P7Detach)
|
||||
public R<AsymSignP7Resp> signP7Detach(@Valid @RequestBody AsymSignP7Req req) {
|
||||
AsymSignP7Resp resp = asymKeyService.signP7Detach(req);
|
||||
return R.data(resp);
|
||||
}
|
||||
|
||||
/**
|
||||
* P7 Detach验签
|
||||
*
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/verify/P7Detach")
|
||||
@AuthCode(AuthCodeConst.verify_P7Detach)
|
||||
public R<VerifyResp> verifyP7Detach(@Valid @RequestBody AsymVerifyP7Req req) {
|
||||
VerifyResp resp = asymKeyService.verifyP7Detach(req);
|
||||
return R.data(resp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -5,37 +5,67 @@ import com.sunyard.chsm.enums.KeyAlg;
|
||||
import com.sunyard.chsm.enums.KeyCategory;
|
||||
import com.sunyard.chsm.enums.KeyStatus;
|
||||
import com.sunyard.chsm.enums.KeyUsage;
|
||||
import com.sunyard.chsm.mapper.AppCertMapper;
|
||||
import com.sunyard.chsm.mapper.KeyInfoMapper;
|
||||
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
|
||||
import com.sunyard.chsm.model.entity.AppCert;
|
||||
import com.sunyard.chsm.model.entity.KeyInfo;
|
||||
import com.sunyard.chsm.model.entity.KeyRecord;
|
||||
import com.sunyard.chsm.param.AsymDecryptReq;
|
||||
import com.sunyard.chsm.param.AsymDecryptResp;
|
||||
import com.sunyard.chsm.param.AsymEncryptReq;
|
||||
import com.sunyard.chsm.param.AsymEncryptResp;
|
||||
import com.sunyard.chsm.param.AsymSignP7Req;
|
||||
import com.sunyard.chsm.param.AsymSignP7Resp;
|
||||
import com.sunyard.chsm.param.AsymSignRawReq;
|
||||
import com.sunyard.chsm.param.AsymSignRawResp;
|
||||
import com.sunyard.chsm.param.AsymVerifyP7Req;
|
||||
import com.sunyard.chsm.param.AsymVerifyRawReq;
|
||||
import com.sunyard.chsm.param.VerifyResp;
|
||||
import com.sunyard.chsm.sdf.SdfApiService;
|
||||
import com.sunyard.chsm.sdf.model.EccCipher;
|
||||
import com.sunyard.chsm.sdf.model.EccSignature;
|
||||
import com.sunyard.chsm.utils.CodecUtils;
|
||||
import com.sunyard.chsm.utils.gm.BCECUtils;
|
||||
import com.sunyard.chsm.utils.gm.cert.BCSM2CertUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaCertStore;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cms.CMSProcessableByteArray;
|
||||
import org.bouncycastle.cms.CMSSignedData;
|
||||
import org.bouncycastle.cms.CMSSignedDataGenerator;
|
||||
import org.bouncycastle.cms.CMSTypedData;
|
||||
import org.bouncycastle.cms.SignerInfoGenerator;
|
||||
import org.bouncycastle.cms.SignerInformation;
|
||||
import org.bouncycastle.cms.SignerInformationStore;
|
||||
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
|
||||
import org.bouncycastle.cms.jcajce.JcaSignerInfoVerifierBuilder;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
||||
import org.bouncycastle.util.Store;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author liulu
|
||||
* @since 2024/12/20
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AsymKeyService {
|
||||
|
||||
private final AppCertMapper appCertMapper;
|
||||
private final KeyInfoMapper keyInfoMapper;
|
||||
private final SpKeyRecordMapper spKeyRecordMapper;
|
||||
private final SdfApiService sdfApiService;
|
||||
@ -147,6 +177,118 @@ public class AsymKeyService {
|
||||
return resp;
|
||||
}
|
||||
|
||||
public AsymSignP7Resp signP7Attach(AsymSignP7Req req) {
|
||||
byte[] plainData = CodecUtils.decodeBase64(req.getPlainData());
|
||||
AppCert appCert = appCertMapper.selectSignBySubject(req.getSubject());
|
||||
Assert.notNull(appCert, "证书主题对应的证书不存在");
|
||||
Assert.isTrue(Objects.equals(appCert.getApplicationId(), UserContext.getCurrentAppId()), "您无权使用此密钥ID");
|
||||
byte[] encPri = CodecUtils.decodeHex(appCert.getEncPriKey());
|
||||
byte[] pri = sdfApiService.decryptByTMK(encPri);
|
||||
|
||||
byte[] asymSignP7Resp = p7Sign(pri, appCert.getCertText(), plainData, true);
|
||||
AsymSignP7Resp resp = new AsymSignP7Resp();
|
||||
resp.setSignData(CodecUtils.encodeBase64(asymSignP7Resp));
|
||||
return resp;
|
||||
}
|
||||
|
||||
public VerifyResp verifyP7Attach(AsymVerifyP7Req req) {
|
||||
byte[] signData = CodecUtils.decodeBase64(req.getSignData());
|
||||
boolean verify;
|
||||
try {
|
||||
verify = p7Verify(signData, null);
|
||||
|
||||
} catch (Exception e) {
|
||||
verify = false;
|
||||
}
|
||||
VerifyResp resp = new VerifyResp();
|
||||
resp.setVerified(verify);
|
||||
return resp;
|
||||
}
|
||||
|
||||
public AsymSignP7Resp signP7Detach(AsymSignP7Req req) {
|
||||
byte[] plainData = CodecUtils.decodeBase64(req.getPlainData());
|
||||
AppCert appCert = appCertMapper.selectSignBySubject(req.getSubject());
|
||||
Assert.notNull(appCert, "证书主题对应的证书不存在");
|
||||
Assert.isTrue(Objects.equals(appCert.getApplicationId(), UserContext.getCurrentAppId()), "您无权使用此密钥ID");
|
||||
byte[] encPri = CodecUtils.decodeHex(appCert.getEncPriKey());
|
||||
byte[] pri = sdfApiService.decryptByTMK(encPri);
|
||||
|
||||
byte[] asymSignP7Resp = p7Sign(pri, appCert.getCertText(), plainData, false);
|
||||
AsymSignP7Resp resp = new AsymSignP7Resp();
|
||||
resp.setSignData(CodecUtils.encodeBase64(asymSignP7Resp));
|
||||
return resp;
|
||||
}
|
||||
|
||||
public VerifyResp verifyP7Detach(AsymVerifyP7Req req) {
|
||||
Assert.hasText(req.getPlainData(), "P7Detach验签原文不能为空");
|
||||
byte[] signData = CodecUtils.decodeBase64(req.getSignData());
|
||||
byte[] plainData = CodecUtils.decodeBase64(req.getPlainData());
|
||||
boolean verify;
|
||||
try {
|
||||
verify = p7Verify(signData, plainData);
|
||||
|
||||
} catch (Exception e) {
|
||||
verify = false;
|
||||
}
|
||||
VerifyResp resp = new VerifyResp();
|
||||
resp.setVerified(verify);
|
||||
return resp;
|
||||
}
|
||||
|
||||
private static byte[] p7Sign(byte[] pri, String cert, byte[] plainData, boolean encapsulate) {
|
||||
try {
|
||||
BCECPrivateKey privateKey = BCECUtils.createPrivateKey(pri);
|
||||
X509Certificate x509Cert = BCSM2CertUtils.getX509Cert(cert);
|
||||
// 构造签名内容
|
||||
CMSTypedData cmsData = new CMSProcessableByteArray(plainData);
|
||||
// 生成签名者信息
|
||||
SignerInfoGenerator signerInfoGenerator = new JcaSignerInfoGeneratorBuilder(
|
||||
new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build()
|
||||
).build(
|
||||
new JcaContentSignerBuilder("SM3withSM2").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey),
|
||||
x509Cert
|
||||
);
|
||||
// 构建 CMS Signed Data
|
||||
CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
|
||||
generator.addSignerInfoGenerator(signerInfoGenerator);
|
||||
generator.addCertificates(new JcaCertStore(Collections.singletonList(x509Cert)));
|
||||
CMSSignedData signedData = generator.generate(cmsData, encapsulate);
|
||||
return signedData.getEncoded();
|
||||
} catch (Exception ex) {
|
||||
log.warn("", ex);
|
||||
throw new IllegalArgumentException("P7Attach 签名异常");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean p7Verify(byte[] signedDataBytes, byte[] originalData) throws Exception {
|
||||
|
||||
CMSSignedData signedData;
|
||||
if (originalData == null || originalData.length == 0) {
|
||||
signedData = new CMSSignedData(signedDataBytes);
|
||||
} else {
|
||||
CMSTypedData originalContent = new CMSProcessableByteArray(originalData);
|
||||
signedData = new CMSSignedData(originalContent, signedDataBytes);
|
||||
}
|
||||
|
||||
Store<X509CertificateHolder> certStore = signedData.getCertificates();
|
||||
SignerInformationStore signers = signedData.getSignerInfos();
|
||||
|
||||
for (SignerInformation signer : signers.getSigners()) {
|
||||
Collection<X509CertificateHolder> matches = certStore.getMatches(signer.getSID());
|
||||
if (matches.isEmpty()) {
|
||||
throw new IllegalArgumentException("No matching certificate found for signer");
|
||||
}
|
||||
X509CertificateHolder certHolder = matches.iterator().next(); // 这里进行类型安全的提取
|
||||
X509Certificate cert = new JcaX509CertificateConverter()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.getCertificate(certHolder);
|
||||
if (signer.verify(new JcaSignerInfoVerifierBuilder(
|
||||
new JcaDigestCalculatorProviderBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build()).build(cert))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private KeyInfo checkKey(Long keyId, KeyUsage usage) {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user