对接第三方时间戳签署PDF后Adobe提示SigDict/Contents非法数据如何解决
PDF时间戳签名后Adobe提示SigDict /Contents illegal data错误
我尝试通过第三方签名服务商对PDF文档进行签名,流程为先生成空签名,计算文档哈希后发送给服务商,双方约定采用时间戳签名,待服务商返回时间戳令牌后将签名写回PDF。目前获取时间戳、CRL、OCSP的API调用均正常,但生成带签名的PDF后,Adobe提示签名无效,报错信息如下:
签名验证过程中出错。
签名包含不正确、无法识别、损坏或可疑数据。
支持信息:SigDict /Contents illegal data
当前实现代码如下:
signatureprovider.java 核心代码
public ByteArrayOutputStream sign(byte[] src, Rectangle rect, int signPage) throws Exception { log.info("I am in the signing method"); ByteArrayOutputStream dest = new ByteArrayOutputStream(); PdfDocumentHandler handler = new PdfDocumentHandler(new ByteArrayInputStream(src), dest); handler.prepareForSigning(rect, signPage); TrustedTimestampService service = new TrustedTimestampService(); SignResponseWrapper response = new SignResponseWrapper(); response = service.signHash(handler.getEncodedDocumentHash()); String timestampToken = response.getSignResponse().getSignatureObject().getOther().getScSignatureObjects().getScExtendedSignatureObject().getTimestamp().getRFC3161TimeStampToken(); List<String> encodedCrlEntries = new ArrayList<String>(); encodedCrlEntries.add(response.getSignResponse().getOptionalOutputs().getScRevocationInformation().getScCRLs().getScCRL()); List<String> encodedOcspEntries = new ArrayList<String>(); encodedOcspEntries.add(response.getSignResponse().getOptionalOutputs().getScRevocationInformation().getScOCSPs().getScOCSP()); byte[] signed = handler.createSignedPdf(Base64.getDecoder().decode(timestampToken), 15000, encodedCrlEntries, encodedOcspEntries); dest.flush(); dest.write(signed); handler.close(); return dest; }
此处省略服务层代码,该层功能仅为发送哈希并获取对应令牌。
pdfdocumenthandler.java 核心代码
public PdfDocumentHandler(InputStream inputStream, ByteArrayOutputStream outputStream) { this.inputStream = inputStream; this.outputStream = outputStream; } public void prepareForSigning(Rectangle rect, int signPage) throws IOException, GeneralSecurityException { inMemoryStream = new ByteArrayOutputStream(); inMemoryStream.write(StreamUtil.inputStreamToArray(inputStream)); boolean hasSignature = hasDocumentSignature(); pdfReader = new PdfReader(new ByteArrayInputStream(inMemoryStream.toByteArray()), new ReaderProperties()); inMemoryStream.reset(); pdfWriter = new PdfWriter(inMemoryStream, new WriterProperties().addXmpMetadata().setPdfVersion(PdfVersion.PDF_1_0)); StampingProperties stampingProperties = new StampingProperties(); pdfSigner = new PdfDocumentSigner(pdfReader, inMemoryStream, hasSignature ? stampingProperties.useAppendMode() : stampingProperties); pdfSigner.getSignatureAppearance() .setReason("myreason") .setLocation("mylocation") .setContact("mycontact") .setPageRect(rect) .setPageNumber(signPage) .setSignatureCreator("creator"); pdfSigner.setFieldName("digitalSig"); try { imgStorage = new ImageStorage(); ImageData imgData = ImageDataFactory.create(Base64.getDecoder().decode(imgStorage.getimgData().getBytes())); pdfSigner.getSignatureAppearance().setRenderingMode(RenderingMode.GRAPHIC); pdfSigner.getSignatureAppearance().setSignatureGraphic(imgData); } catch(Exception e){ pdfSigner.getSignatureAppearance().setRenderingMode(RenderingMode.DESCRIPTION); } Map<PdfName, PdfObject> signatureDictionary = new HashMap<>(); signatureDictionary.put(PdfName.Filter, PdfName.Adobe_PPKLite); signatureDictionary.put(PdfName.SubFilter, PdfName.ETSI_RFC3161); Calendar signDate = Calendar.getInstance(); pdfSigner.setSignDate(signDate); PdfHashSignatureContainer hashSignatureContainer = new PdfHashSignatureContainer("SHA-512", new PdfDictionary(signatureDictionary)); documentHash = pdfSigner.computeHash(hashSignatureContainer, 15_000); log.info("prepared document - outputstream: " + Base64.getEncoder().encodeToString(outputStream.toByteArray())); log.info("prepared document - inmemorystream: " + Base64.getEncoder().encodeToString(inMemoryStream.toByteArray())); } public byte[] createSignedPdf(byte[] externalSignature, int estimatedSize, List<String> encodedCrlEntries, List<String> encodedOcspEntries) { if (pdfSigner.getCertificationLevel() == PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) { throw new RuntimeException(String.format("Could not apply signature because source file contains a certification that does not allow " + "any changes to the document with id %s")); } if (estimatedSize < externalSignature.length) { throw new RuntimeException(String.format("Not enough space for signature in the document. The estimated size needs to be " + " %d bytes.", externalSignature.length)); } try { pdfSigner.signWithAuthorizedSignature(new PdfSignatureContainer(externalSignature), estimatedSize); if (null != encodedCrlEntries || null!=encodedOcspEntries) { extendDocumentWithCrlOcspMetadata(encodedCrlEntries, encodedOcspEntries); } else { log.info("No CRL and OCSP entries were received to be embedded into the PDF"); outputStream.write(inMemoryStream.toByteArray()); } closeResource(inMemoryStream); closeResource(outputStream); log.info("complete document - outputstream: " + Base64.getEncoder().encodeToString(outputStream.toByteArray())); log.info("complete document - inmemorystream: " + Base64.getEncoder().encodeToString(inMemoryStream.toByteArray())); } catch (IOException | GeneralSecurityException e) { throw new RuntimeException(String.format("Failed to embed the signature in the document"), e); } return outputStream.toByteArray(); } private void extendDocumentWithCrlOcspMetadata(List<String> encodedCrlEntries, List<String> encodedOcspEntries) { if (pdfSigner.getCertificationLevel() == PdfSigner.CERTIFIED_NO_CHANGES_ALLOWED) { throw new RuntimeException(String.format("Could not apply revocation information (LTV) to the DSS Dictionary. Document contains a " + "certification that does not allow any changes")); } log.info("document without crl and ocsp - outputstream: " + Base64.getEncoder().encodeToString(outputStream.toByteArray())); log.info("document without crl and ocsp - inmemorystream: " + Base64.getEncoder().encodeToString(inMemoryStream.toByteArray())); List<byte[]> crl = mapEncodedEntries(encodedCrlEntries, this::mapEncodedCrl); List<byte[]> ocsp = mapEncodedEntries(encodedOcspEntries, this::mapEncodedOcsp); try (InputStream documentStream = new ByteArrayInputStream(inMemoryStream.toByteArray()); PdfReader reader = new PdfReader(documentStream); PdfWriter writer = new PdfWriter(outputStream); PdfDocument pdfDocument = new PdfDocument(reader, writer, new StampingProperties().preserveEncryption().useAppendMode())) { LtvVerification validation = new LtvVerification(pdfDocument); List<String> signatureNames = new SignatureUtil(pdfDocument).getSignatureNames(); String signatureName = signatureNames.get(signatureNames.size() - 1); boolean isSignatureVerificationAdded = validation.addVerification(signatureName, ocsp, crl, null); validation.merge(); logSignatureVerificationInfo(isSignatureVerificationAdded); } catch (Exception e) { throw new RuntimeException(String.format("Failed to embed the signature(s) in the document(s) and close the streams")); } } public String getId() { return id; } public String getEncodedDocumentHash() { return Base64.getEncoder().encodeToString(documentHash); } private void logSignatureVerificationInfo(boolean isSignatureVerificationAdded) { if (isSignatureVerificationAdded) { log.info("Merged LTV validation information to the output stream"); } else { log.warn("Failed to merge LTV validation information to the output stream"); } } private List<byte[]> mapEncodedEntries(List<String> encodedEntries, Function<String, byte[]> mapperFunction) { return Objects.nonNull(encodedEntries) ? encodedEntries.stream().map(mapperFunction).collect(Collectors.toList()) : Collections.emptyList(); } private byte[] mapEncodedCrl(String encodedCrl) { try (InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encodedCrl))) { X509CRL x509crl = (X509CRL) CertificateFactory.getInstance("X.509").generateCRL(inputStream); logCrlInfo(x509crl); return x509crl.getEncoded(); } catch (IOException | CertificateException | CRLException e) { throw new RuntimeException(String.format("Failed to map the received encoded CRL entry"), e); } } private byte[] mapEncodedOcsp(String encodedOcsp) { try (InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(encodedOcsp))) { OCSPResp ocspResp = new OCSPResp(inputStream); BasicOCSPResp basicResp = (BasicOCSPResp) ocspResp.getResponseObject(); logOcspInfo(ocspResp, basicResp); return basicResp.getEncoded(); } catch (IOException | OCSPException e) { throw new RuntimeException(String.format("Failed to map the received encoded OCSP entry"), e); } } private void logCrlInfo(X509CRL x509crl) { int revokedCertificatesNo = Objects.isNull(x509crl.getRevokedCertificates()) ? 0 : x509crl.getRevokedCertificates().size(); String message = "Embedding CRL response... [" + "IssuerDN: " + x509crl.getIssuerX500Principal() + " " + "This update: " + x509crl.getThisUpdate() + " " + "Next update: " + x509crl.getNextUpdate() + " " + "No. of revoked certificates: " + revokedCertificatesNo + "]"; log.info(message); } private void logOcspInfo(OCSPResp ocspResp, BasicOCSPResp basicResp) { SingleResp response = basicResp.getResponses()[0]; BigInteger serialNumber = response.getCertID().getSerialNumber(); X509CertificateHolder firstCertificate = basicResp.getCerts()[0]; String message = "Embedding OCSP response... [" + "Status: " + (ocspResp.getStatus() == 0 ? "OK" : "NOK") + " " + "Produced at: " + basicResp.getProducedAt() + " " + "This update: " + response.getThisUpdate() + " " + "Next update: " + response.getNextUpdate() + " " + "X509 cert issuer: " + firstCertificate.getIssuer() + " " + "X509 cert subject: " + firstCertificate.getSubject() + " " + "Certificate ID: " + serialNumber.toString() + "(" + serialNumber.toString(16).toUpperCase() + ")" + "]"; log.info(message); } private boolean hasDocumentSignature() throws IOException { try (ByteArrayInputStream is = new ByteArrayInputStream(inMemoryStream.toByteArray()); PdfReader reader = new PdfReader(is, new ReaderProperties()); PdfDocument pdfDocument = new PdfDocument(reader)) { SignatureUtil signatureUtil = new SignatureUtil(pdfDocument); return signatureUtil.getSignatureNames().size() > 0; } }
pdfdocumentsigner.java 核心代码
public class PdfDocumentSigner extends PdfSigner { public PdfDocumentSigner(PdfReader reader, OutputStream outputStream, boolean properties) throws IOException { super(reader, outputStream, properties); } public byte[] computeHash(IExternalSignatureContainer externalHashContainer, int estimatedSize) throws GeneralSecurityException, IOException { if (closed) { throw new PdfException(PdfException.ThisInstanceOfPdfSignerAlreadyClosed); } PdfSignature signatureDictionary = new PdfSignature(); PdfSignatureAppearance appearance = getSignatureAppearance(); signatureDictionary.setReason(appearance.getReason()); signatureDictionary.setLocation(appearance.getLocation()); signatureDictionary.setSignatureCreator(appearance.getSignatureCreator()); signatureDictionary.setContact(appearance.getContact()); signatureDictionary.setDate(new PdfDate(getSignDate())); externalHashContainer.modifySigningDictionary(signatureDictionary.getPdfObject()); cryptoDictionary = signatureDictionary; Map<PdfName, Integer> exc = new HashMap<>(); exc.put(PdfName.Contents, estimatedSize * 2 + 2); preClose(exc); InputStream dataRangeStream = getRangeStream(); return externalHashContainer.sign(dataRangeStream); } public void signWithAuthorizedSignature(IExternalSignatureContainer externalSignatureContainer, int estimatedSize) throws GeneralSecurityException, IOException { InputStream dataRangeStream = getRangeStream(); byte[] authorizedSignature = externalSignatureContainer.sign(dataRangeStream); if (estimatedSize < authorizedSignature.length) { throw new IOException(String.format("Not enough space. The estimated signature size [%d bytes] is less than the received authorized " + "signature [%d bytes] which needs to be embedded into the document.", estimatedSize, authorizedSignature.length)); } byte[] paddedSignature = new byte[estimatedSize]; System.arraycopy(authorizedSignature, 0, paddedSignature, 0, authorizedSignature.length); PdfDictionary dic2 = new PdfDictionary(); dic2.put(PdfName.Contents, new PdfString(paddedSignature).setHexWriting(true)); close(dic2); closed = true; } }
排查了很久也没发现遗漏的环节,求帮忙定位问题。
内容的提问来源于stack exchange,提问作者Antonio De Michele
相关产品推荐
相关产品推荐

