|
|
@@ -28,9 +28,28 @@ import org.bouncycastle.cms.CMSProcessableByteArray;
|
|
|
import org.bouncycastle.cms.CMSSignedData;
|
|
|
import org.bouncycastle.cms.SignerInformation;
|
|
|
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
|
|
|
+import org.bouncycastle.asn1.ASN1OctetString;
|
|
|
+import org.bouncycastle.asn1.ASN1Primitive;
|
|
|
+import org.bouncycastle.asn1.x509.AccessDescription;
|
|
|
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
|
|
|
+import org.bouncycastle.asn1.x509.Extension;
|
|
|
+import org.bouncycastle.asn1.x509.GeneralName;
|
|
|
import org.bouncycastle.cert.X509CertificateHolder;
|
|
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
|
|
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
|
|
|
+import org.bouncycastle.cert.ocsp.CertificateID;
|
|
|
+import org.bouncycastle.cert.ocsp.CertificateStatus;
|
|
|
+import org.bouncycastle.cert.ocsp.OCSPReq;
|
|
|
+import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
|
|
|
+import org.bouncycastle.cert.ocsp.OCSPResp;
|
|
|
+import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
|
|
|
+import org.bouncycastle.cert.ocsp.RevokedStatus;
|
|
|
+import org.bouncycastle.cert.ocsp.UnknownStatus;
|
|
|
+import org.bouncycastle.cert.ocsp.jcajce.JcaCertificateID;
|
|
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
|
+import org.bouncycastle.operator.DigestCalculatorProvider;
|
|
|
+import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
|
|
|
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
|
|
|
import org.bouncycastle.util.Store;
|
|
|
import org.apache.commons.io.FileUtils;
|
|
|
import org.ofdrw.core.basicType.ST_Box;
|
|
|
@@ -57,6 +76,10 @@ import org.springframework.web.multipart.MultipartFile;
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
+import java.math.BigInteger;
|
|
|
+import java.net.HttpURLConnection;
|
|
|
+import java.net.URI;
|
|
|
+import java.net.URL;
|
|
|
import java.nio.file.Files;
|
|
|
import java.nio.file.Path;
|
|
|
import java.security.MessageDigest;
|
|
|
@@ -65,6 +88,7 @@ import java.security.PrivateKey;
|
|
|
import java.security.GeneralSecurityException;
|
|
|
import java.security.cert.Certificate;
|
|
|
import java.security.cert.X509Certificate;
|
|
|
+import java.time.Instant;
|
|
|
import java.util.*;
|
|
|
import java.util.zip.ZipException;
|
|
|
|
|
|
@@ -434,6 +458,7 @@ public class SignatureController {
|
|
|
? Collections.emptyList() : signatureList.getSignatures();
|
|
|
List<Map<String, Object>> signatures = new ArrayList<>();
|
|
|
boolean allValid = !signatureRecords.isEmpty();
|
|
|
+ boolean allCertificateChecksPerformed = !signatureRecords.isEmpty();
|
|
|
SESV4ValidateContainer signatureValidator = new SESV4ValidateContainer();
|
|
|
|
|
|
for (int i = 0; i < signatureRecords.size(); i++) {
|
|
|
@@ -447,6 +472,8 @@ public class SignatureController {
|
|
|
var resourceLocator = reader.getResourceLocator();
|
|
|
boolean contentIntegrityValid = false;
|
|
|
boolean cryptographicSignatureValid = false;
|
|
|
+ boolean certificateStatusValid = false;
|
|
|
+ boolean certificateStatusChecked = false;
|
|
|
Boolean sealMatched = null;
|
|
|
try {
|
|
|
ST_Loc signaturesPath = reader.getDefaultDocSignaturesPath();
|
|
|
@@ -523,6 +550,14 @@ public class SignatureController {
|
|
|
certificateBytes));
|
|
|
signatureResult.put("signerName",
|
|
|
certificate.getSubjectX500Principal().getName());
|
|
|
+ CertificateValidation certificateValidation =
|
|
|
+ validateSignerCertificate(certificate);
|
|
|
+ certificateStatusChecked = true;
|
|
|
+ certificateStatusValid = certificateValidation.valid();
|
|
|
+ signatureResult.put("certificateStatusValid", certificateStatusValid);
|
|
|
+ signatureResult.put("certificateStatusMessage",
|
|
|
+ certificateValidation.message());
|
|
|
+ signatureResult.put("certificateStatusChecked", true);
|
|
|
}
|
|
|
} finally {
|
|
|
resourceLocator.restore();
|
|
|
@@ -533,9 +568,12 @@ public class SignatureController {
|
|
|
signatureResult.put("contentIntegrityValid", contentIntegrityValid);
|
|
|
signatureResult.put("cryptographicSignatureValid",
|
|
|
cryptographicSignatureValid);
|
|
|
+ signatureResult.putIfAbsent("certificateStatusValid", certificateStatusValid);
|
|
|
+ signatureResult.put("certificateStatusChecked", certificateStatusChecked);
|
|
|
signatureResult.put("sealMatched", sealMatched);
|
|
|
signatureResult.put("valid", contentIntegrityValid
|
|
|
&& cryptographicSignatureValid
|
|
|
+ && certificateStatusValid
|
|
|
&& Boolean.TRUE.equals(signatureResult.get("structureValid"))
|
|
|
&& (sealMatched == null || sealMatched)
|
|
|
&& (signatureRecord.getType() == null
|
|
|
@@ -543,11 +581,16 @@ public class SignatureController {
|
|
|
== org.ofdrw.core.signatures.SigType.Seal));
|
|
|
if (!contentIntegrityValid) {
|
|
|
signatureResult.put("message", "签章引用的文档内容摘要校验失败");
|
|
|
+ } else if (!certificateStatusValid) {
|
|
|
+ signatureResult.put("message", signatureResult.getOrDefault(
|
|
|
+ "certificateStatusMessage", "签名证书状态校验失败"));
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
signatureResult.put("contentIntegrityValid", contentIntegrityValid);
|
|
|
signatureResult.put("cryptographicSignatureValid",
|
|
|
cryptographicSignatureValid);
|
|
|
+ signatureResult.put("certificateStatusValid", certificateStatusValid);
|
|
|
+ signatureResult.put("certificateStatusChecked", certificateStatusChecked);
|
|
|
signatureResult.put("sealMatched", sealMatched);
|
|
|
signatureResult.putIfAbsent("structureValid", false);
|
|
|
signatureResult.put("valid", false);
|
|
|
@@ -555,6 +598,8 @@ public class SignatureController {
|
|
|
}
|
|
|
|
|
|
boolean signatureValid = Boolean.TRUE.equals(signatureResult.get("valid"));
|
|
|
+ allCertificateChecksPerformed &= Boolean.TRUE.equals(
|
|
|
+ signatureResult.get("certificateStatusChecked"));
|
|
|
signatures.add(signatureResult);
|
|
|
allValid &= signatureValid;
|
|
|
}
|
|
|
@@ -562,10 +607,10 @@ public class SignatureController {
|
|
|
verification.put("valid", allValid);
|
|
|
verification.put("signatureCount", signatures.size());
|
|
|
verification.put("signatures", signatures);
|
|
|
- verification.put("trustChecked", false);
|
|
|
+ verification.put("trustChecked", allCertificateChecksPerformed);
|
|
|
verification.put("message", signatureRecords.isEmpty()
|
|
|
? "OFD文件中没有电子签章"
|
|
|
- : allValid ? "所有OFD电子签章均通过校验" : "存在无效电子签章");
|
|
|
+ : allValid ? "所有OFD电子签章及证书状态均通过校验" : "存在无效电子签章或证书状态异常");
|
|
|
}
|
|
|
return ofdSignatureResult(result, DataResponse.CODE_SUCCESS, "验签完成", verification);
|
|
|
} catch (Exception e) {
|
|
|
@@ -608,7 +653,9 @@ public class SignatureController {
|
|
|
}
|
|
|
}
|
|
|
List<Map<String, Object>> signatures = new ArrayList<>();
|
|
|
- boolean allValid = !signatureFields.isEmpty();
|
|
|
+ boolean allSignaturesValid = !signatureFields.isEmpty();
|
|
|
+ boolean allCertificateChecksPerformed = !signatureFields.isEmpty();
|
|
|
+ long latestSignedRevisionEnd = -1;
|
|
|
BouncyCastleProvider provider = new BouncyCastleProvider();
|
|
|
|
|
|
for (PDSignatureField signatureField : signatureFields) {
|
|
|
@@ -624,6 +671,15 @@ public class SignatureController {
|
|
|
&& byteRange[2] >= byteRange[1]
|
|
|
&& byteRange[3] >= 0
|
|
|
&& (long) byteRange[2] + byteRange[3] == pdfBytes.length;
|
|
|
+ if (byteRange != null
|
|
|
+ && byteRange.length == 4
|
|
|
+ && byteRange[0] == 0
|
|
|
+ && byteRange[1] >= 0
|
|
|
+ && byteRange[2] >= byteRange[1]
|
|
|
+ && byteRange[3] >= 0) {
|
|
|
+ latestSignedRevisionEnd = Math.max(
|
|
|
+ latestSignedRevisionEnd, (long) byteRange[2] + byteRange[3]);
|
|
|
+ }
|
|
|
signatureResult.put("coversWholeDocument", coversWholeDocument);
|
|
|
try {
|
|
|
byte[] signedContent = pdfSignature.getSignedContent(pdfBytes);
|
|
|
@@ -633,12 +689,17 @@ public class SignatureController {
|
|
|
Store<X509CertificateHolder> certificateStore = cmsSignedData.getCertificates();
|
|
|
Collection<SignerInformation> signerInfos = cmsSignedData.getSignerInfos().getSigners();
|
|
|
boolean valid = !signerInfos.isEmpty();
|
|
|
+ boolean certificateStatusValid = !signerInfos.isEmpty();
|
|
|
+ boolean certificateStatusChecked = !signerInfos.isEmpty();
|
|
|
String signerName = pdfSignature.getName();
|
|
|
+ String certificateStatusMessage = "签名证书未能验证";
|
|
|
|
|
|
for (SignerInformation signerInfo : signerInfos) {
|
|
|
Collection<X509CertificateHolder> matchingCertificates =
|
|
|
certificateStore.getMatches(signerInfo.getSID());
|
|
|
boolean signerValid = false;
|
|
|
+ boolean signerCertificateStatusValid = false;
|
|
|
+ boolean signerCertificateStatusChecked = false;
|
|
|
for (X509CertificateHolder certificateHolder : matchingCertificates) {
|
|
|
var certificate = new JcaX509CertificateConverter()
|
|
|
.setProvider(provider)
|
|
|
@@ -648,42 +709,61 @@ public class SignatureController {
|
|
|
.build(certificate))) {
|
|
|
signerValid = true;
|
|
|
signerName = certificate.getSubjectX500Principal().getName();
|
|
|
+ CertificateValidation certificateValidation =
|
|
|
+ validateSignerCertificate(certificate);
|
|
|
+ signerCertificateStatusChecked = true;
|
|
|
+ signerCertificateStatusValid = certificateValidation.valid();
|
|
|
+ certificateStatusMessage = certificateValidation.message();
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
valid &= signerValid;
|
|
|
+ certificateStatusValid &= signerCertificateStatusValid;
|
|
|
+ certificateStatusChecked &= signerCertificateStatusChecked;
|
|
|
}
|
|
|
|
|
|
- signatureResult.put("valid", valid);
|
|
|
+ signatureResult.put("cryptographicSignatureValid", valid);
|
|
|
+ signatureResult.put("certificateStatusValid", certificateStatusValid);
|
|
|
+ signatureResult.put("certificateStatusChecked", certificateStatusChecked);
|
|
|
+ signatureResult.put("certificateStatusMessage", certificateStatusMessage);
|
|
|
+ signatureResult.put("valid", valid && certificateStatusValid);
|
|
|
signatureResult.put("signerName", signerName);
|
|
|
if (pdfSignature.getSignDate() != null) {
|
|
|
signatureResult.put("signingTime", pdfSignature.getSignDate().getTime());
|
|
|
}
|
|
|
if (!valid) {
|
|
|
signatureResult.put("message", "签名摘要或签名值校验失败");
|
|
|
- allValid = false;
|
|
|
+ } else if (!certificateStatusValid) {
|
|
|
+ signatureResult.put("message", certificateStatusMessage);
|
|
|
} else if (!coversWholeDocument) {
|
|
|
- signatureResult.put("message", "签名有效,但未覆盖整个PDF文档,签名后可能有内容变更");
|
|
|
- allValid = false;
|
|
|
+ signatureResult.put("message", "签名有效,签名后文档中追加了后续增量内容");
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
signatureResult.put("valid", false);
|
|
|
+ signatureResult.put("certificateStatusValid", false);
|
|
|
+ signatureResult.put("certificateStatusChecked", false);
|
|
|
signatureResult.put("message", e.getMessage());
|
|
|
- allValid = false;
|
|
|
}
|
|
|
+ allSignaturesValid &= Boolean.TRUE.equals(signatureResult.get("valid"));
|
|
|
+ allCertificateChecksPerformed &= Boolean.TRUE.equals(
|
|
|
+ signatureResult.get("certificateStatusChecked"));
|
|
|
signatures.add(signatureResult);
|
|
|
}
|
|
|
|
|
|
+ boolean finalRevisionSigned = latestSignedRevisionEnd == pdfBytes.length;
|
|
|
+ boolean allValid = allSignaturesValid && finalRevisionSigned;
|
|
|
Map<String, Object> verification = new HashMap<>();
|
|
|
verification.put("valid", allValid);
|
|
|
verification.put("signatureCount", signatures.size());
|
|
|
verification.put("signatures", signatures);
|
|
|
+ verification.put("trustChecked", allCertificateChecksPerformed);
|
|
|
verification.put("message", signatureFields.isEmpty()
|
|
|
? "PDF文件中没有电子签名"
|
|
|
: allValid
|
|
|
- ? "所有PDF签名均通过完整性校验"
|
|
|
- : "存在无效签名或签名后文档内容发生变化");
|
|
|
- verification.put("trustChecked", false);
|
|
|
+ ? "所有PDF签名及证书状态均有效,最终文档版本已签名"
|
|
|
+ : !allSignaturesValid
|
|
|
+ ? "存在无效PDF签名或签名证书状态异常"
|
|
|
+ : "最终签名后文档内容发生变化或未被签名");
|
|
|
return ofdSignatureResult(result, DataResponse.CODE_SUCCESS, "验签完成", verification);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
@@ -710,6 +790,218 @@ public class SignatureController {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+ private CertificateValidation validateSignerCertificate(X509Certificate signerCertificate) {
|
|
|
+ try {
|
|
|
+ Set<String> visitedSerials = new HashSet<>();
|
|
|
+ X509Certificate currentCertificate = signerCertificate;
|
|
|
+ for (int depth = 0; depth < 10; depth++) {
|
|
|
+ String serialNumber = currentCertificate.getSerialNumber().toString(16)
|
|
|
+ .toUpperCase(Locale.ROOT);
|
|
|
+ if (!visitedSerials.add(serialNumber)) {
|
|
|
+ return new CertificateValidation(false, "证书链存在循环");
|
|
|
+ }
|
|
|
+
|
|
|
+ CertificateEntity certificateEntity = findCertificateBySerial(
|
|
|
+ currentCertificate.getSerialNumber());
|
|
|
+ if (certificateEntity == null) {
|
|
|
+ return new CertificateValidation(false, "签名证书链未在本地CA中登记");
|
|
|
+ }
|
|
|
+ X509Certificate registeredCertificate =
|
|
|
+ certificateAssembleService.getCertificate(certificateEntity.getCertSerialNumber());
|
|
|
+ if (!MessageDigest.isEqual(
|
|
|
+ currentCertificate.getEncoded(), registeredCertificate.getEncoded())) {
|
|
|
+ return new CertificateValidation(false, "签名证书与本地登记证书不一致");
|
|
|
+ }
|
|
|
+ String status = certificateEntity.getStatus();
|
|
|
+ if ("2".equals(status)) {
|
|
|
+ return new CertificateValidation(false, "证书已过期");
|
|
|
+ }
|
|
|
+ if ("3".equals(status)) {
|
|
|
+ return new CertificateValidation(false, "证书已冻结");
|
|
|
+ }
|
|
|
+ if ("4".equals(status)) {
|
|
|
+ return new CertificateValidation(false, "证书已吊销");
|
|
|
+ }
|
|
|
+ if (!"1".equals(status)) {
|
|
|
+ return new CertificateValidation(false, "证书状态未知");
|
|
|
+ }
|
|
|
+ currentCertificate.checkValidity();
|
|
|
+
|
|
|
+ if ("1".equals(certificateEntity.getIsRoot())) {
|
|
|
+ if (!currentCertificate.getSubjectX500Principal()
|
|
|
+ .equals(currentCertificate.getIssuerX500Principal())) {
|
|
|
+ return new CertificateValidation(false, "根证书不是自签名证书");
|
|
|
+ }
|
|
|
+ currentCertificate.verify(currentCertificate.getPublicKey());
|
|
|
+ return new CertificateValidation(true, "证书链有效,OCSP状态正常");
|
|
|
+ }
|
|
|
+
|
|
|
+ String issuerSerial = certificateEntity.getParentSerial();
|
|
|
+ if (issuerSerial == null || issuerSerial.isBlank()) {
|
|
|
+ return new CertificateValidation(false, "证书缺少颁发者信息");
|
|
|
+ }
|
|
|
+ CertificateEntity issuerEntity = certificateMapper.selectOne(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CertificateEntity>()
|
|
|
+ .eq(CertificateEntity::getCertSerialNumber, issuerSerial)
|
|
|
+ );
|
|
|
+ if (issuerEntity == null) {
|
|
|
+ return new CertificateValidation(false, "未找到证书颁发者");
|
|
|
+ }
|
|
|
+ X509Certificate issuerCertificate =
|
|
|
+ certificateAssembleService.getCertificate(issuerEntity.getCertSerialNumber());
|
|
|
+ currentCertificate.verify(issuerCertificate.getPublicKey());
|
|
|
+
|
|
|
+ CertificateValidation ocspValidation =
|
|
|
+ checkOcspStatus(currentCertificate, issuerCertificate);
|
|
|
+ if (!ocspValidation.valid()) {
|
|
|
+ return ocspValidation;
|
|
|
+ }
|
|
|
+ currentCertificate = issuerCertificate;
|
|
|
+ }
|
|
|
+ return new CertificateValidation(false, "证书链深度超过允许范围");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("签名证书状态校验失败: {}", e.getMessage());
|
|
|
+ return new CertificateValidation(false, "证书链或OCSP状态校验失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private CertificateEntity findCertificateBySerial(BigInteger serialNumber) {
|
|
|
+ String normalizedSerial = serialNumber.toString(16).toUpperCase(Locale.ROOT);
|
|
|
+ String paddedSerial = String.format("%032X", serialNumber);
|
|
|
+ return certificateMapper.selectOne(
|
|
|
+ new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CertificateEntity>()
|
|
|
+ .in(CertificateEntity::getCertSerialNumber, normalizedSerial, paddedSerial)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private CertificateValidation checkOcspStatus(
|
|
|
+ X509Certificate certificate, X509Certificate issuerCertificate) throws Exception {
|
|
|
+ String responderUrl = getOcspResponderUrl(certificate);
|
|
|
+ if (responderUrl == null) {
|
|
|
+ return new CertificateValidation(false, "签名证书未配置OCSP地址");
|
|
|
+ }
|
|
|
+
|
|
|
+ DigestCalculatorProvider digestProvider =
|
|
|
+ new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
|
|
|
+ X509CertificateHolder issuerHolder = new X509CertificateHolder(issuerCertificate.getEncoded());
|
|
|
+ CertificateID certificateId = new JcaCertificateID(
|
|
|
+ digestProvider.get(CertificateID.HASH_SHA1),
|
|
|
+ issuerCertificate,
|
|
|
+ certificate.getSerialNumber()
|
|
|
+ );
|
|
|
+ OCSPReq request = new OCSPReqBuilder().addRequest(certificateId).build();
|
|
|
+
|
|
|
+ URL url = URI.create(responderUrl).toURL();
|
|
|
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
|
+ connection.setRequestMethod("POST");
|
|
|
+ connection.setConnectTimeout(5000);
|
|
|
+ connection.setReadTimeout(5000);
|
|
|
+ connection.setDoOutput(true);
|
|
|
+ connection.setRequestProperty("Content-Type", "application/ocsp-request");
|
|
|
+ connection.setRequestProperty("Accept", "application/ocsp-response");
|
|
|
+ byte[] requestBytes = request.getEncoded();
|
|
|
+ connection.setFixedLengthStreamingMode(requestBytes.length);
|
|
|
+
|
|
|
+ byte[] responseBytes;
|
|
|
+ try {
|
|
|
+ try (var output = connection.getOutputStream()) {
|
|
|
+ output.write(requestBytes);
|
|
|
+ }
|
|
|
+ int httpStatus = connection.getResponseCode();
|
|
|
+ if (httpStatus != HttpURLConnection.HTTP_OK) {
|
|
|
+ return new CertificateValidation(false, "OCSP服务返回HTTP " + httpStatus);
|
|
|
+ }
|
|
|
+ if (connection.getContentLengthLong() > 1_048_576) {
|
|
|
+ return new CertificateValidation(false, "OCSP响应超过允许大小");
|
|
|
+ }
|
|
|
+ try (InputStream input = connection.getInputStream();
|
|
|
+ ByteArrayOutputStream responseOutput = new ByteArrayOutputStream()) {
|
|
|
+ byte[] buffer = new byte[4096];
|
|
|
+ int read;
|
|
|
+ while ((read = input.read(buffer)) != -1) {
|
|
|
+ responseOutput.write(buffer, 0, read);
|
|
|
+ if (responseOutput.size() > 1_048_576) {
|
|
|
+ return new CertificateValidation(false, "OCSP响应超过允许大小");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ responseBytes = responseOutput.toByteArray();
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ connection.disconnect();
|
|
|
+ }
|
|
|
+
|
|
|
+ OCSPResp response = new OCSPResp(responseBytes);
|
|
|
+ if (response.getStatus() != OCSPRespBuilder.SUCCESSFUL
|
|
|
+ || !(response.getResponseObject() instanceof BasicOCSPResp basicResponse)) {
|
|
|
+ return new CertificateValidation(false, "OCSP响应状态异常");
|
|
|
+ }
|
|
|
+ if (!basicResponse.isSignatureValid(
|
|
|
+ new JcaContentVerifierProviderBuilder()
|
|
|
+ .setProvider("BC")
|
|
|
+ .build(issuerHolder))) {
|
|
|
+ return new CertificateValidation(false, "OCSP响应签名无效");
|
|
|
+ }
|
|
|
+
|
|
|
+ Instant now = Instant.now();
|
|
|
+ for (var singleResponse : basicResponse.getResponses()) {
|
|
|
+ CertificateID responseCertificateId = singleResponse.getCertID();
|
|
|
+ if (!responseCertificateId.getSerialNumber().equals(certificate.getSerialNumber())
|
|
|
+ || !responseCertificateId.matchesIssuer(issuerHolder, digestProvider)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Date thisUpdate = singleResponse.getThisUpdate();
|
|
|
+ Date nextUpdate = singleResponse.getNextUpdate();
|
|
|
+ if (thisUpdate == null || thisUpdate.toInstant().isAfter(now.plusSeconds(300))
|
|
|
+ || nextUpdate == null || nextUpdate.toInstant().isBefore(now)) {
|
|
|
+ return new CertificateValidation(false, "OCSP响应已过期或时间无效");
|
|
|
+ }
|
|
|
+
|
|
|
+ CertificateStatus status = singleResponse.getCertStatus();
|
|
|
+ if (status == CertificateStatus.GOOD) {
|
|
|
+ return new CertificateValidation(true, "OCSP状态正常");
|
|
|
+ }
|
|
|
+ if (status instanceof RevokedStatus revokedStatus) {
|
|
|
+ return new CertificateValidation(false,
|
|
|
+ revokedStatus.hasRevocationReason()
|
|
|
+ ? "OCSP状态为已冻结或已吊销,原因码 " + revokedStatus.getRevocationReason()
|
|
|
+ : "OCSP状态为已冻结或已吊销");
|
|
|
+ }
|
|
|
+ if (status instanceof UnknownStatus) {
|
|
|
+ return new CertificateValidation(false, "OCSP服务无法识别该证书");
|
|
|
+ }
|
|
|
+ return new CertificateValidation(false, "OCSP返回未知证书状态");
|
|
|
+ }
|
|
|
+ return new CertificateValidation(false, "OCSP响应中没有对应证书状态");
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getOcspResponderUrl(X509Certificate certificate) throws Exception {
|
|
|
+ byte[] aiaExtension = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
|
|
|
+ if (aiaExtension == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ byte[] aiaBytes = ASN1OctetString.getInstance(
|
|
|
+ ASN1Primitive.fromByteArray(aiaExtension)).getOctets();
|
|
|
+ AuthorityInformationAccess aia = AuthorityInformationAccess.getInstance(
|
|
|
+ ASN1Primitive.fromByteArray(aiaBytes));
|
|
|
+ for (AccessDescription description : aia.getAccessDescriptions()) {
|
|
|
+ if (AccessDescription.id_ad_ocsp.equals(description.getAccessMethod())
|
|
|
+ && description.getAccessLocation().getTagNo() == GeneralName.uniformResourceIdentifier) {
|
|
|
+ String value = description.getAccessLocation().getName().toString();
|
|
|
+ URI uri = URI.create(value);
|
|
|
+ if ("http".equalsIgnoreCase(uri.getScheme())
|
|
|
+ || "https".equalsIgnoreCase(uri.getScheme())) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private record CertificateValidation(boolean valid, String message) {
|
|
|
+ }
|
|
|
+
|
|
|
private Map<String, Object> ofdSignatureResult(Map<String, Object> result, int code, String message, Object data) {
|
|
|
result.put("code", code);
|
|
|
result.put("message", message);
|
|
|
@@ -737,4 +1029,4 @@ public class SignatureController {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
-}
|
|
|
+}
|