如何解决PAdES 签名关卡 - Adobe Acrobat
我正在使用 pdfBox 3.0.0 RC 创建 PADES 签名,我的代码使用示例来创建数字签名。但是,当我使用此工具打开文档时,我无法在 Adobe Acrobat 中看到签名级别,尽管它能够验证我的签名。
我不是在创建 VRI,所以我猜测这可能是一个问题,但如果这是验证我的签名所必需的,我不明白为什么签名显示为有效?
Adobe Acrobat 签名:
/**
* Service for automatically signing a document as part of a workflow. In this instance no user information is
* gathered
*
* @param taskID
* @param processName which will be added to the document
* @param keyID the ID for the key used to sign the PDF document
* @return the signed PDF document as a base 64 Encoded String
*/
@Transactional
public String signPDFService(String processID,String processName,String keyID,ObjectData signatureImage,String creator)
{
try {
ByteArrayOutputStream ostream = new ByteArrayOutputStream();
//Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
/*List<Task> tasks = taskService.createTaskQuery()
.taskId(taskID)
.list();
// Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
List<PDFDocument> documents = tasks.stream()
.map(task -> {
String processID = task.getProcessInstanceId();
Map<String,Object> variables = taskService.getvariables(task.getId());
PDFDocument document;
try {
document = new PDFDocument(
(ArrayList) variables.get("assigneeList"),(String) variables.get("unsignedPDFDocument"),task.getProcessInstanceId(),task.getId(),(String) variables.get("name"),(String) variables.get("description")
);
document.setHistory((ArrayList) variables.get("history"));
return document;
} catch (IOException e) {
// Todo Auto-generated catch block
e.printstacktrace();
return null;
} catch (CMSException e) {
// Todo Auto-generated catch block
e.printstacktrace();
return null;
}
})
.collect(Collectors.toList());*/
//Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(documents.get(0).getProcessID());
Optional<ObjectData> pdfDocumentProcessInstance = userDataRepository.findById(processID);
if(pdfDocumentProcessInstance.isEmpty())
throw new IOException("No process found");
String pdfDocumentBase64String = pdfDocumentProcessInstance.get().getAttributes().get("PDFDocument");
String extractedPDFString = pdfDocumentBase64String.replaceAll("data:application/pdf;base64,","").replaceAll("data:;base64,"").replaceAll("data:application/octet-stream","");
//String extractedPDFString = base64PDF.replaceAll("data:application/pdf;base64,"");
InputStream stream = new ByteArrayInputStream(Base64.getDecoder().decode(extractedPDFString.getBytes()));
//Create the date object to sign the document
Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
//Retrieve certificate chain for the PDF Signer
String certChainPEM = kmsService.getCertChainPEM(keyID);
X509Certificate pdfSignerCertificate = X509Utils.readCertificateChain(certChainPEM).get(0).getCertificate();
//Create the CMS Signing Object
ExternalSignatureCMSSignedDataGenerator cmsGenerator = new ExternalSignatureCMSSignedDataGenerator();
ExternalSignatureSignerInfoGenerator signerGenerator = new ExternalSignatureSignerInfoGenerator(CMSSignedDataGenerator.DIGEST_SHA256,"1.2.840.10045.4.3.2");
signerGenerator.setCertificate(pdfSignerCertificate);
ExternalSigningSupport externalSigningSupport;
PDDocument pdDocument = Loader.loadPDF(stream);
//Create the PDFBox Signature Object
PDSignature pdSignature = new PDSignature();
pdSignature.setFilter(PDSignature.FILTER_AdobE_PPKLITE);
pdSignature.setSubFilter(PDSignature.SUBFILTER_ETSI_CADES_DETACHED);
pdSignature.setLocation("Remote IS Blocks Signer");
pdSignature.setName("IS Blocks Signer");
pdSignature.setReason(processName);
pdDocument.setDocumentId(calendar.getTimeInMillis());
pdSignature.setSignDate(calendar);
// Optional: Certify the first time signature
// can be done only if version is at least 1.5 and if not already set
// doing this on a PDF/A-1b file fails validation by Adobe preflight (PDFBox-3821)
// PDF/A-1b requires PDF version 1.4 max,so don't increase the version on such files.
int accesspermissions = SigUtils.getMDPPermission(pdDocument);
if (pdDocument.getVersion() >= 1.5f && accesspermissions == 0 && processName.contains("Document Certifying Key"))
{
logger.debug("Certifying Document");
SigUtils.setMDPPermission(pdDocument,pdSignature,3);
}
if(signatureImage != null) {
String data = signatureImage.getAttributes().get("data").replaceAll("data:application/pdf;base64,"").replaceAll("data:image/png;base64,"");
int pageNumber = Integer.parseInt(signatureImage.getAttributes().get("page"));
float x = Float.parseFloat(signatureImage.getAttributes().get("x"));
float y = Float.parseFloat(signatureImage.getAttributes().get("y"));
float width = Float.parseFloat(signatureImage.getAttributes().get("width"));
float height = Float.parseFloat(signatureImage.getAttributes().get("height"));
SignatureOptions signatureOptions;
// register signature dictionary and sign interface
signatureOptions = new SignatureOptions();
PDFVisibleSignature pdfVisibleSignature = new PDFVisibleSignature();
signatureOptions.setVisualSignature(pdfVisibleSignature.createVisualSignatureTemplate(
x,y,width,height,pdDocument,pageNumber,Base64.getDecoder().decode(data.getBytes("UTF-8"))));
signatureOptions.setPage(pageNumber);
pdDocument.addSignature(pdSignature,null,signatureOptions);
} else {
pdDocument.addSignature(pdSignature);
}
externalSigningSupport = pdDocument.saveIncrementalForExternalSigning(ostream);
//Create the message digest of the pre-signed PDF
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bytes = org.apache.commons.io.IoUtils.toByteArray(externalSigningSupport.getContent());
byte[] hashBytes = digest.digest(bytes);
//CMS Signature
InputStream isBytes = new ByteArrayInputStream(bytes);
CMSProcessable input = new CMSProcessableInputStream(isBytes);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
MessageDigest messageDigest1 = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest1.digest(bytes);
byte[] bytesToSign = signerGenerator.getBytesToSign(PKCSObjectIdentifiers.data,hash,new Date(),"BC");
String encodedData = Base64.getEncoder().encodetoString(bytesToSign);
logger.debug("Bytes to Sign:" + (Base64.getEncoder().encodetoString(bytesToSign)));
logger.debug("Hash:" + Base64.getEncoder().encodetoString(hash));
//Create the signature using the keyID
//At this time only ECDSAWithSHA256 is supported
Map<String,String> signature = kmsService.sign(keyID,encodedData);
byte[] signedBytes = Base64.getDecoder().decode(signature.get("signature"));
X509Certificate[] chain;
signerGenerator.setCertificate(pdfSignerCertificate);
signerGenerator.setSignedBytes(signedBytes);
cmsGenerator.addSignerInf(signerGenerator);
cmsGenerator.addCertificatesAndCRLs(X509Utils.getCertStore(signature.get("certificateChain")));
CMSSignedData signedData = cmsGenerator.generate(new CMSProcessableByteArray(hash),false);
//Add a RFC3161 Time Stamp
ValidationTimeStamp validation = new ValidationTimeStamp("https://freetsa.org/tsr");
signedData = validation.addSignedTimeStamp(signedData);
ContentSigner nonSigner = new ContentSigner() {
@Override
public byte[] getSignature() {
return signedBytes;
}
@Override
public OutputStream getoutputStream() {
return new ByteArrayOutputStream();
}
@Override
public AlgorithmIdentifier getAlgorithmIdentifier() {
return new DefaultSignatureAlgorithmIdentifierFinder().find( "SHA256WithECDSA" );
}
};
CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
JcaSignerInfoGeneratorBuilder sigb = new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build());
gen.addCertificate(new X509CertificateHolder(pdfSignerCertificate.getEncoded()));
sigb.setDirectSignature( true );
gen.addSignerInfoGenerator(sigb.build(nonSigner,new X509CertificateHolder(pdfSignerCertificate.getEncoded())));
CMSTypedData msg = new CMSProcessableInputStream( new ByteArrayInputStream( "not used".getBytes() ) );
CMSSignedData signedData1 = gen.generate((CMSTypedData)msg,false);
signedData1.getEncoded();
externalSigningSupport.setSignature(signedData.getEncoded());
//documents.get(0).addHistoricEvent("Signed " + processName);
//ArrayList<String> history = documents.get(0).getHistory();
//Post Signature
String signedPDFDocument = Base64.getEncoder().encodetoString(ostream.toByteArray());
PDDocument newPdf1;
newPdf1 = Loader.loadPDF(ostream.toByteArray());
byte[] fileContent = ostream.toByteArray();
List<PDSignature> pdfSignatures;
pdfSignatures = newPdf1.getSignatureDictionaries();
byte[] signatureAsBytes;
signatureAsBytes = newPdf1.getLastSignatureDictionary().getContents( fileContent );
byte[] signedContentAsBytes;
signedContentAsBytes = newPdf1.getLastSignatureDictionary().getSignedContent( fileContent );
// Now we construct a PKCS #7 or CMS.
CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContentAsBytes);
CMSSignedData cmsSignedData;
cmsSignedData = new CMSSignedData(cmsProcessableInputStream,signatureAsBytes);
Store certificatesstore = cmsSignedData.getCertificates();
Collection<Signerinformation> signers = cmsSignedData.getSignerInfos().getSigners();
Signerinformation signerinformation = signers.iterator().next();
Collection matches = certificatesstore.getMatches(signerinformation.getSID());
X509CertificateHolder certificateHolder = (X509CertificateHolder) matches.iterator().next();
ObjectMapper mapper = new ObjectMapper();
ArrayList<String> signatures = null;
//signatures = documents.get(0).getSignatures();
for(int iCount = 0; iCount < pdfSignatures.size(); iCount++) {
PDFSignature pdfSignature = new PDFSignature(
pdfSignatures.get(iCount).getName(),pdfSignatures.get(iCount).getLocation(),pdfSignatures.get(iCount).getSignDate().getdisplayName(Calendar.LONG_FORMAT,java.util.Calendar.LONG,Locale.UK),pdfSignatures.get(iCount).getReason(),certificateHolder.getSubject().toString(),certificateHolder.getIssuer().toString(),Base64.getEncoder().encodetoString(certificateHolder.getEncoded()));
//signatures.add(mapper.writeValueAsstring(pdfSignature));
logger.info("Signature" + mapper.writeValueAsstring(pdfSignature));
}
Map<String,Object> variables = new HashMap<String,Object>();
// variables.put("history",history);
//variables.put("unsignedPDFDocument",signedPDFDocument);
//variables.put("signatures",signatures);
//variables.put("status",value)
Map<String,String> pdfDocumentProcessInstanceAttributes = pdfDocumentProcessInstance.get().getAttributes();
pdfDocumentProcessInstanceAttributes.put("PDFDocument",signedPDFDocument);
ObjectData newpdfProcessInstance = pdfDocumentProcessInstance.get();
newpdfProcessInstance.setAttributes(pdfDocumentProcessInstanceAttributes);
userDataRepository.save(newpdfProcessInstance);
newpdfProcessInstance.getHistory().add(new Date() + "Signed by:" + creator);
System.out.println(newpdfProcessInstance.getId() + " " + newpdfProcessInstance.toString());
newPdf1.close();
ostream.close();
} catch (Exception e){
// Todo Auto-generated catch block
e.printstacktrace();
}
return null;
}
解决方法
在分析您在评论中提供的文件 document-with signingTime.pdf 时,我发现其中存在问题。意识到这个问题后,我重新检查了您原来的 document-17 21.08.14.pdf 并在其中发现了这个问题,所以这个问题可能会导致您在这里要解决的验证问题。因此,...
您的两个示例文件(document-17 21.08.14.pdf 和 document-with signingTime.pdf)实际上都包含两个串联副本相同的多修订版 PDF,具有单个签名 Signature1,只是第二个副本的 ID 条目已更改。添加到其中的是带有签名 Signature2 的增量更新。
%PDF-1.4
...
15 0 obj
<<
/FT /Sig
...
/T (Signature1)
...
>>
endobj
...
/ID [<1952AB9C134E46B58251246E985D5C15> <7F887303DDC0ED7C37AE77403E30DFB0>]
...
%%EOF
%PDF-1.4
...
15 0 obj
<<
/FT /Sig
...
/T (Signature1)
...
>>
endobj
...
/ID [<1952AB9C134E46B58251246E985D5C15> <A57CD5B87222756EC4A096125C7E8A42>]
...
%%EOF
...
35 0 obj
<<
/FT /Sig
...
/T (Signature2)
...
>>
...
%%EOF
这个结构被破坏了,即使它不会引起错误的交叉引用(因为第一个副本和第二个副本除了 ID、交叉引用和 startxref 偏移点之外是相同的到第一个副本中的相应位置)。 Adobe Reader 签名验证对此类问题的反应非常敏感。
因此,您应该删除此处的第二个副本以取得成功。
此外,正如评论中已经提到的,您的 CMS 签名容器的 SignerInfo
包含一个 1.2.840.113549.1.9.5 signingTime
签名属性。 PAdES BASELINE 配置文件禁止这样做。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。