From 182918a7b99c1ef99a1d5dbcba568d3b2859e3ac Mon Sep 17 00:00:00 2001 From: liulu Date: Wed, 25 Dec 2024 14:22:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B8=A6=E7=AD=BE=E5=90=8D=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E4=BF=A1=E5=B0=81=E5=8A=A0=E5=AF=86=E5=92=8C=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chsm/utils/SignedAndEnvelopedData.java | 106 ++++++++++++++++++ .../chsm/param/AsymEnvelopeSealReq.java | 11 +- .../chsm/controller/AsymKeyController.java | 26 +++++ .../sunyard/chsm/service/AsymKeyService.java | 104 ++++++++++++++--- .../src/test/java/api/AsymKeyTest.java | 24 +++- 5 files changed, 249 insertions(+), 22 deletions(-) create mode 100644 chsm-common/src/main/java/com/sunyard/chsm/utils/SignedAndEnvelopedData.java diff --git a/chsm-common/src/main/java/com/sunyard/chsm/utils/SignedAndEnvelopedData.java b/chsm-common/src/main/java/com/sunyard/chsm/utils/SignedAndEnvelopedData.java new file mode 100644 index 0000000..7e4a5b3 --- /dev/null +++ b/chsm-common/src/main/java/com/sunyard/chsm/utils/SignedAndEnvelopedData.java @@ -0,0 +1,106 @@ +package com.sunyard.chsm.utils; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.cms.EncryptedContentInfo; + +import java.util.Enumeration; + +/** + *
+ * SignedAndEnvelopedData ::=SEQUENCE {
+ *             version                      Version,
+ *             recipientInfos               RecipientInfos,
+ *             digestAlgorithms             DigestAlgorithmIdentifiers
+ *             encryptedContentInfo         EncryptedContentInfo,
+ *             certificates    [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
+ *             crls            [1] IMPLICIT CertificateRevocationLists OPTIONAL,
+ *             signerInfos                  SignerInfos
+ *     }
+ * 
+ * + * @author liulu + * @since 2024/12/25 + */ +@Getter +@AllArgsConstructor +public class SignedAndEnvelopedData extends ASN1Object { + + private ASN1Integer version; + private ASN1Set recipientInfos; + private ASN1Set digestAlgorithms; + private EncryptedContentInfo encryptedContentInfo; + private ASN1Set certificates; + private ASN1Set crls; + private ASN1Set signerInfos; + + + public static SignedAndEnvelopedData getInstance( + Object obj) { + if (obj instanceof SignedAndEnvelopedData) { + return (SignedAndEnvelopedData) obj; + } + + if (obj != null) { + return new SignedAndEnvelopedData(ASN1Sequence.getInstance(obj)); + } + + return null; + } + + private SignedAndEnvelopedData(ASN1Sequence seq) { + Enumeration e = seq.getObjects(); + + version = ASN1Integer.getInstance(e.nextElement()); + recipientInfos = ((ASN1Set) e.nextElement()); + digestAlgorithms = ((ASN1Set) e.nextElement()); + encryptedContentInfo = EncryptedContentInfo.getInstance(e.nextElement()); + + ASN1Set sigInfs = null; + while (e.hasMoreElements()) { + ASN1Primitive o = (ASN1Primitive) e.nextElement(); + if (o instanceof ASN1TaggedObject) { + ASN1TaggedObject tagged = (ASN1TaggedObject) o; + switch (tagged.getTagNo()) { + case 0: + certificates = ASN1Set.getInstance(tagged, false); + break; + case 1: + crls = ASN1Set.getInstance(tagged, false); + break; + default: + throw new IllegalArgumentException("unknown tag value " + tagged.getTagNo()); + } + } else { + if (!(o instanceof ASN1Set)) { + throw new IllegalArgumentException("SET expected, not encountered"); + } + sigInfs = (ASN1Set) o; + } + } + + if (sigInfs == null) { + throw new IllegalArgumentException("signerInfos not set"); + } + signerInfos = sigInfs; + } + + + @Override + public ASN1Primitive toASN1Primitive() { + ASN1EncodableVector v = new ASN1EncodableVector(7); + v.add(version); + v.add(recipientInfos); + v.add(digestAlgorithms); + v.add(encryptedContentInfo); + if (certificates != null) { + v.add(new DERTaggedObject(false, 0, certificates)); + } + if (crls != null) { + v.add(new DERTaggedObject(false, 1, crls)); + } + v.add(signerInfos); + return new DLSequence(v); + } +} diff --git a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEnvelopeSealReq.java b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEnvelopeSealReq.java index d1d1762..c06d737 100644 --- a/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEnvelopeSealReq.java +++ b/chsm-params/src/main/java/com/sunyard/chsm/param/AsymEnvelopeSealReq.java @@ -7,11 +7,12 @@ import javax.validation.constraints.NotBlank; @Data public class AsymEnvelopeSealReq { - /** - * 证书主题 - */ - @NotBlank(message = "证书主题不能为空") - private String subject; + // 签名证书主题 + private String signSubject; + + // 接收者加密证书 + @NotBlank(message = "加密证书不能为空") + private String encCert; // 明文,使用Base64编码 @NotBlank(message = "明文不能为空") diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java index 8154074..1e20aed 100644 --- a/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/controller/AsymKeyController.java @@ -156,5 +156,31 @@ public class AsymKeyController { return R.data(resp); } + /** + * P7带签名的数字信封加封 + * + * @param req + * @return + */ + @PostMapping("/signedEnvelope/seal") + @AuthCode(AuthCodeConst.signed_envelope_seal) + public R signedEnvelopeSeal(@Valid @RequestBody AsymEnvelopeSealReq req) { + AsymEnvelopeSealResp resp = asymKeyService.signedEnvelopeSeal(req); + return R.data(resp); + } + + /** + * P7带签名的数字信封解封 + * + * @param req + * @return + */ + @PostMapping("/signedEnvelope/unseal") + @AuthCode(AuthCodeConst.signed_envelope_unseal) + public R signedEnvelopeUnseal(@Valid @RequestBody AsymEnvelopeUnsealReq req) { + AsymEnvelopeUnsealResp resp = asymKeyService.signedEnvelopeUnseal(req); + return R.data(resp); + } + } diff --git a/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java b/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java index b49eb1b..a2bc857 100644 --- a/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java +++ b/chsm-web-server/src/main/java/com/sunyard/chsm/service/AsymKeyService.java @@ -15,6 +15,7 @@ import com.sunyard.chsm.sdf.model.EccCipher; import com.sunyard.chsm.sdf.model.EccSignature; import com.sunyard.chsm.sdf.util.LangUtils; import com.sunyard.chsm.utils.CodecUtils; +import com.sunyard.chsm.utils.SignedAndEnvelopedData; import com.sunyard.chsm.utils.gm.cert.BCSM2CertUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -40,7 +41,9 @@ import org.springframework.util.Assert; import java.io.ByteArrayOutputStream; import java.io.OutputStream; +import java.security.NoSuchProviderException; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.LocalDateTime; import java.util.Collection; @@ -349,21 +352,19 @@ public class AsymKeyService { @SneakyThrows public AsymEnvelopeSealResp envelopeSeal(AsymEnvelopeSealReq req) { byte[] plainData = CodecUtils.decodeBase64(req.getPlainData()); - AppCert appCert = appCertMapper.selectEncBySubject(req.getSubject()); - Assert.notNull(appCert, "证书主题对应的证书不存在"); - Assert.isTrue(Objects.equals(appCert.getApplicationId(), UserContext.getCurrentAppId()), "您无权使用此证书"); - byte[] envelopeData = makeEnvelopeData(plainData, appCert.getCertText()); + EnvelopedData contentInfo = getEnvelopedData(req.getEncCert(), plainData); + byte[] envelopeData = contentInfo.getEncoded("DER"); + AsymEnvelopeSealResp resp = new AsymEnvelopeSealResp(); resp.setEnvelopeData(CodecUtils.encodeBase64(envelopeData)); return resp; } - @SneakyThrows - private byte[] makeEnvelopeData(byte[] srcMsg, String certText) { - X509Certificate cert = BCSM2CertUtils.getX509Cert(certText); - AlgorithmIdentifier symAlg = new AlgorithmIdentifier(GMObjectIdentifiers.sm_scheme.branch("104")); // sm4 - AlgorithmIdentifier asymAlg = new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt, DERNull.INSTANCE); + private EnvelopedData getEnvelopedData(String encCert, byte[] plainData) throws CertificateException, NoSuchProviderException { + X509Certificate cert = BCSM2CertUtils.getX509Cert(encCert); + AlgorithmIdentifier symAlg = new AlgorithmIdentifier(GMObjectIdentifiers.sms4_ecb); + AlgorithmIdentifier asymAlg = new AlgorithmIdentifier(GMObjectIdentifiers.sm2encrypt); byte[] symKey = sdfApiService.generateRandom(16); BCECPublicKey publicKey = (BCECPublicKey) cert.getPublicKey(); @@ -379,18 +380,17 @@ public class AsymKeyService { ); RecipientInfo recipientInfo = new RecipientInfo(keyTransRecipientInfo); - byte[] encContent = sdfApiService.symEncrypt(AlgId.SGD_SM4_ECB, Padding.PCKS7Padding, symKey, null, srcMsg); + byte[] encContent = sdfApiService.symEncrypt(AlgId.SGD_SM4_ECB, Padding.PCKS7Padding, symKey, null, plainData); EncryptedContentInfo encContentInfo = new EncryptedContentInfo( new ASN1ObjectIdentifier("1.2.156.10197.6.1.4.2.1"), symAlg, new DEROctetString(encContent) ); - EnvelopedData contentInfo = new EnvelopedData(null, + return new EnvelopedData(null, new DERSet(recipientInfo), encContentInfo, - (org.bouncycastle.asn1.ASN1Set) null); - return contentInfo.getEncoded("DER"); + (ASN1Set) null); } @SneakyThrows @@ -398,6 +398,13 @@ public class AsymKeyService { byte[] envelopeData = CodecUtils.decodeBase64(req.getEnvelopeData()); // 解密数字信封 EnvelopedData ed = EnvelopedData.getInstance(envelopeData); + byte[] plain = getPlainFromEnvelopedData(ed); + AsymEnvelopeUnsealResp resp = new AsymEnvelopeUnsealResp(); + resp.setPlainData(CodecUtils.encodeBase64(plain)); + return resp; + } + + private byte[] getPlainFromEnvelopedData(EnvelopedData ed) { ASN1Set infos = ed.getRecipientInfos(); RecipientInfo recipientInfo = RecipientInfo.getInstance(infos.getObjectAt(0)); KeyTransRecipientInfo transRecipientInfo = KeyTransRecipientInfo.getInstance(recipientInfo.getInfo()); @@ -406,18 +413,85 @@ public class AsymKeyService { AppCert appCert = appCertMapper.selectBySN(sn); Assert.notNull(appCert, "此信封对应的证书不存在"); Assert.isTrue(Objects.equals(appCert.getApplicationId(), UserContext.getCurrentAppId()), "您无权使用此信封的解密证书"); + + AlgorithmIdentifier algorithm = transRecipientInfo.getKeyEncryptionAlgorithm(); + Assert.isTrue(Objects.equals(algorithm.getAlgorithm().getId(), GMObjectIdentifiers.sm2encrypt.getId()), "密钥加密算法只支持SM2"); + byte[] pri = sdfApiService.decryptByTMK(CodecUtils.decodeHex(appCert.getEncPriKey())); byte[] encryptedKey = transRecipientInfo.getEncryptedKey().getOctets(); byte[] symKey = sdfApiService.externalDecryptECC(pri, encryptedKey); EncryptedContentInfo info = ed.getEncryptedContentInfo(); + Assert.isTrue(Objects.equals(info.getContentEncryptionAlgorithm().getAlgorithm().getId(), GMObjectIdentifiers.sms4_ecb.getId()), "内容加密算法只支持SM4_ECB"); + byte[] octets = info.getEncryptedContent().getOctets(); byte[] plain = sdfApiService.symDecrypt(AlgId.SGD_SM4_ECB, Padding.PCKS7Padding, symKey, null, octets); - AsymEnvelopeUnsealResp resp = new AsymEnvelopeUnsealResp(); - resp.setPlainData(CodecUtils.encodeBase64(plain)); + return plain; + } + + @SneakyThrows + public AsymEnvelopeSealResp signedEnvelopeSeal(AsymEnvelopeSealReq req) { + Assert.hasText(req.getSignSubject(), "签名证书主题不能为空"); + byte[] plainData = CodecUtils.decodeBase64(req.getPlainData()); + AppCert appCert = appCertMapper.selectSignBySubject(req.getSignSubject()); + Assert.notNull(appCert, "证书主题对应的签名证书不存在"); + Assert.isTrue(Objects.equals(appCert.getApplicationId(), UserContext.getCurrentAppId()), "您无权使用此证书"); + byte[] encPri = CodecUtils.decodeHex(appCert.getEncPriKey()); + byte[] pri = sdfApiService.decryptByTMK(encPri); + + byte[] asymSignP7Resp = p7Sign(pri, appCert.getCertText(), plainData, true); + ContentInfo instance = ContentInfo.getInstance(asymSignP7Resp); + SignedData signedData = SignedData.getInstance(instance.getContent()); + + EnvelopedData envelopedData = getEnvelopedData(req.getEncCert(), plainData); + + SignedAndEnvelopedData signedAndEnvelopedData = new SignedAndEnvelopedData( + new ASN1Integer(1), + envelopedData.getRecipientInfos(), + signedData.getDigestAlgorithms(), + envelopedData.getEncryptedContentInfo(), + signedData.getCertificates(), + signedData.getCRLs(), + signedData.getSignerInfos() + ); + AsymEnvelopeSealResp resp = new AsymEnvelopeSealResp(); + resp.setEnvelopeData(CodecUtils.encodeBase64(signedAndEnvelopedData.getEncoded())); return resp; } + @SneakyThrows + public AsymEnvelopeUnsealResp signedEnvelopeUnseal(AsymEnvelopeUnsealReq req) { + byte[] data = CodecUtils.decodeBase64(req.getEnvelopeData()); + SignedAndEnvelopedData signedAndEnvelopedData = SignedAndEnvelopedData.getInstance(data); + + EnvelopedData envelopedData = new EnvelopedData( + null, + signedAndEnvelopedData.getRecipientInfos(), + signedAndEnvelopedData.getEncryptedContentInfo(), + (ASN1Set) null + ); + byte[] plainData = getPlainFromEnvelopedData(envelopedData); + + CMSTypedData cmsData = new CMSProcessableByteArray(plainData); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + cmsData.write(bOut); + bOut.close(); + ContentInfo encInfo = new ContentInfo(cmsData.getContentType(), new BEROctetString(bOut.toByteArray())); + SignedData sd = new SignedData( + signedAndEnvelopedData.getDigestAlgorithms(), + encInfo, + signedAndEnvelopedData.getCertificates(), + signedAndEnvelopedData.getCrls(), + signedAndEnvelopedData.getSignerInfos() + ); + ContentInfo contentInfo = new ContentInfo(CMSObjectIdentifiers.signedData, sd); + p7Verify(contentInfo.getEncoded(), null); + AsymEnvelopeUnsealResp resp = new AsymEnvelopeUnsealResp(); + resp.setPlainData(CodecUtils.encodeBase64(plainData)); + return resp; + } + + private KeyInfo checkKey(Long keyId, KeyUsage usage) { KeyInfo keyInfo = keyInfoMapper.selectById(keyId); diff --git a/chsm-web-server/src/test/java/api/AsymKeyTest.java b/chsm-web-server/src/test/java/api/AsymKeyTest.java index 0c4f71d..f263309 100644 --- a/chsm-web-server/src/test/java/api/AsymKeyTest.java +++ b/chsm-web-server/src/test/java/api/AsymKeyTest.java @@ -18,6 +18,8 @@ public class AsymKeyTest extends BaseTest { private static final Long certKeyId = 1871443220005818369L; private static final String dn = "CN=cert-test,O=SYD,L=HZ,ST=ZJ,C=CN"; + private static final String enc_cert = "MIICdjCCAhqgAwIBAgINLGdqVePOjZMIDvBZqDAMBggqgRzPVQGDdQUAMEMxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKDARCSkNBMQ0wCwYDVQQLDARCSkNBMRYwFAYDVQQDDA1URVNUU00yU1VCX1pYMB4XDTI0MTIyNDA1MzQxMVoXDTI1MTIyNDA2MzQxMVowSTELMAkGA1UEBhMCQ04xCzAJBgNVBAgMAlpKMQswCQYDVQQHDAJIWjEMMAoGA1UECgwDU1lEMRIwEAYDVQQDDAljZXJ0LXRlc3QwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAAQDJI7orEc8QVQMblzomR3SPeEqVJdfM46cxquj/JWJ318TZ0gZC1M9YPN9K5NDyaUwnExvGNpnz3PYxbs5nokXo4HqMIHnMAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgM4MB0GA1UdDgQWBBSstrPACaBcow1prbZb2mkgzFOatzAfBgNVHSMEGDAWgBSqoPASv2D/pNLeAnE6J6XPnJ71KjA9BgNVHSAENjA0MDIGCSqBHIbvMgICBDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3d3dy5iamNhLmNuL0NQUzBLBgNVHR8ERDBCMECgPqA8hjpodHRwczovL2NybC5pc2lnbmV0LmNuL2NybC9URVNUU00yU1VCX1pYL1RFU1RTTTJTVUJfWlguY3JsMAwGCCqBHM9VAYN1BQADSAAwRQIhAIJj1ERuVaKh+1YtDlE4kDwrK5ewMeH1ADnK+/7DBrwMAiAJ7J9HBqJkwul1yblnX52W4aQhPZt9LLDZZqJhjEd7Sg=="; + @Test public void testAttach() { @@ -39,10 +41,10 @@ public class AsymKeyTest extends BaseTest { @Test public void testEnvelopedData() { AsymEnvelopeSealReq sealReq = new AsymEnvelopeSealReq(); - sealReq.setSubject(dn); + sealReq.setEncCert(enc_cert); sealReq.setPlainData(CodecUtils.encodeBase64(plain)); AsymEnvelopeSealResp sealResp = execute("/asym/envelope/seal", sealReq, AsymEnvelopeSealResp.class); - log.info("signP7: {}", sealResp.getEnvelopeData()); + log.info("EnvelopeData: {}", sealResp.getEnvelopeData()); AsymEnvelopeUnsealReq unsealReq = new AsymEnvelopeUnsealReq(); unsealReq.setEnvelopeData(sealResp.getEnvelopeData()); @@ -50,7 +52,25 @@ public class AsymKeyTest extends BaseTest { log.info("verifyResp: {}", unsealResp.getPlainData()); log.info("verifyResp: {}", new String(CodecUtils.decodeBase64(unsealResp.getPlainData()))); + Assertions.assertArrayEquals(plain, CodecUtils.decodeBase64(unsealResp.getPlainData())); + } + @Test + public void testSignedEnvelopedData() { + AsymEnvelopeSealReq sealReq = new AsymEnvelopeSealReq(); + sealReq.setSignSubject(dn); + sealReq.setEncCert(enc_cert); + sealReq.setPlainData(CodecUtils.encodeBase64(plain)); + AsymEnvelopeSealResp sealResp = execute("/asym/signedEnvelope/seal", sealReq, AsymEnvelopeSealResp.class); + log.info("EnvelopeData: {}", sealResp.getEnvelopeData()); + + AsymEnvelopeUnsealReq unsealReq = new AsymEnvelopeUnsealReq(); + unsealReq.setEnvelopeData(sealResp.getEnvelopeData()); + AsymEnvelopeUnsealResp unsealResp = execute("/asym/signedEnvelope/unseal", unsealReq, AsymEnvelopeUnsealResp.class); + log.info("verifyResp: {}", unsealResp.getPlainData()); + log.info("verifyResp: {}", new String(CodecUtils.decodeBase64(unsealResp.getPlainData()))); + + Assertions.assertArrayEquals(plain, CodecUtils.decodeBase64(unsealResp.getPlainData())); }