我正在使用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,", "").replaceAll("data:;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:;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,
pdSignature,
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;
}
使用signingTime分析文件文档时。您在评论中提供的pdf,我发现其中有一个问题。意识到这个问题,我重新检查了您的原始文档-17 21.08.14。pdf,并且也认识到了其中的问题,所以可能是这个问题导致了您要解决的验证问题。因此
您的两个示例文件(文档-1721.08.14.pdf和文档-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.5signingTime
签名属性。这对于PAdES BASELINE配置文件是禁止的。
我试图使用以下工作流程创建PAdES签名: PDF准备签名,哈希在浏览器中计算 哈希发送到后端 后端形成分离的CAdES签名 分离的CAdES被发送回组装PAdES签名的浏览器 我们有一个PDF签名的工作示例,其工作原理如下: 准备好PDF,并在浏览器中计算哈希值 这很好用。 然而,现在我们在后端使用DSS库,而不是BouncyCastle,因为我们正在尝试创建PAdES签名。因此,DSS lib
我有一个问题与数字签名PDF文件已标记为PDF/A-3A兼容。使用PDFBox(最新版本,2.0.24)最终在Adobe Acrobat中获得无效签名,而使用iText7(最新版本)获得有效签名。目标是获得符合PAdES LTV的签名。 我的流程如下(使用PDFBox和iText7): 打开PDF,创建用于签名的散列(要签名的数据) 我呼叫第三方服务以取回数字签名 在服务响应中,我还获得了OCSP
我有一个创建PDF文档Base64摘要的API。现在我想创建另一个API,它采用此摘要和PFX并创建一个ETSI. CAdES. detated签名并获取我想要嵌入到我的PDF中的LTV信息(Certs链、OCSP响应、CRL)以使用第三个API获得PAdES-LTV签名(我的第三个API将获取从此API获得的CAdES签名和LTV信息,并将它们嵌入到我的PDF中)。我不知道如何使用该摘要和带有J
我可以在命令行中为两个签名者签名Apk吗?
为了在我们的JUnit测试中使用H2数据库而不是调用Oracle,我被阻止在H2上创建别名以模拟一些Oracle兼容性: > 我首先为日期到字符的转换声明了to_char的别名:可以
下面是C 17形式的规则([basic.lval]/8),但它在其他标准中看起来很相似(C 98中是“lvalue”而不是“glvalue”): 8如果程序试图通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义: (8.4)-对应于对象动态类型的有符号或无符号类型 这条规则听起来像是“除非你做X,否则你会得到UB”,但这并不意味着如果你做了X,你就不会得到UB,正如人们所期望的那样