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()));
}