我创建了带附件的PDF / A文档。我已经签了两次(第一次使用PdfSigner.CERTIFIED_FORM_FILLING
,第二次使用PdfSigner.NOT_CERTIFIED
)。然后我验证了签名。我使用的所有代码都来自iText7提供的示例。验证按预期进行,但是当我在Adobe中打开文档时,它在第一次签名时给了我一个错误,说文档已经更改(签名验证期间出现错误。意外的字节范围值定义了签名数据的范围& #39)。
我用来创建PDF的代码:
public class PdfA3WitAttachment {
public static final String DATA = "resources/data/united_states.csv";
public static final String FONT = "resources/fonts/FreeSans.ttf";
public static final String BOLD_FONT = "resources/fonts/FreeSansBold.ttf";
public static final String INTENT = "resources/color/sRGB_CS_profile.icm";
/** An image resource. */
public static final String FOX = "resources/images/fox.bmp";
/** An image resource. */
public static final String DOG = "resources/images/dog.bmp";
public static final String DEST = "results/withAttachment.pdf";
public static void main(String args[]) throws IOException {
File file = new File(DEST);
file.getParentFile().mkdirs();
new PdfA3WitAttachment().createPdf(DEST);
}
public void createPdf(String dest) throws IOException {
PdfADocument pdf = new PdfADocument(new PdfWriter(dest),
PdfAConformanceLevel.PDF_A_3A,
new PdfOutputIntent("Custom", "", "http://www.color.org",
"sRGB IEC61966-2.1", new FileInputStream(INTENT)));
Document document = new Document(pdf, PageSize.A4.rotate());
document.setMargins(20, 20, 20, 20);
//Setting some required parameters
pdf.setTagged();
pdf.getCatalog().setLang(new PdfString("en-US"));
pdf.getCatalog().setViewerPreferences(
new PdfViewerPreferences().setDisplayDocTitle(true));
PdfDocumentInfo info = pdf.getDocumentInfo();
info.setTitle("iText7 PDF/A-3 example");
//Add attachment
PdfDictionary parameters = new PdfDictionary();
parameters.put(PdfName.ModDate, new PdfDate().getPdfObject());
PdfFileSpec fileSpec = PdfFileSpec.createEmbeddedFileSpec(
pdf, Files.readAllBytes(Paths.get(DATA)), "united_states.csv",
"united_states.csv", new PdfName("text/csv"), parameters,
PdfName.Data, false);
fileSpec.put(new PdfName("AFRelationship"), new PdfName("Data"));
pdf.addFileAttachment("united_states.csv", fileSpec);
PdfArray array = new PdfArray();
array.add(fileSpec.getPdfObject().getIndirectReference());
pdf.getCatalog().put(new PdfName("AF"), array);
//Embed fonts
PdfFont font = PdfFontFactory.createFont(FONT, true);
// Create content
Image fox = new Image(ImageDataFactory.create(FOX));
fox.getAccessibilityProperties().setAlternateDescription("fox");
Image dog = new Image(ImageDataFactory.create(DOG));
dog.getAccessibilityProperties().setAlternateDescription("dog");
document.add(
new Paragraph()
.setFont(font)
.setFontSize(20)
.add(new Text("The quick brown "))
.add(fox)
.add(new Text(" jumps over the lazy "))
.add(dog));
// step 4
document.close();
//Close document
document.close();
}
public void process(Table table, String line, PdfFont font, boolean isHeader) {
StringTokenizer tokenizer = new StringTokenizer(line, ";");
while (tokenizer.hasMoreTokens()) {
if (isHeader) {
table.addHeaderCell(new Cell().setHorizontalAlignment(HorizontalAlignment.CENTER).add(new Paragraph(tokenizer.nextToken()).setHorizontalAlignment(HorizontalAlignment.CENTER).setFont(font)));
} else {
table.addCell(new Cell().setHorizontalAlignment(HorizontalAlignment.CENTER).add(new Paragraph(tokenizer.nextToken()).setHorizontalAlignment(HorizontalAlignment.CENTER).setFont(font)));
}
}
}
}
用于签名和验证的准则:
public class SignWithCertificate {
/*public static final String SRC = "./results/zugferd/pdfa/quickbrownfox4.pdf";
public static final String DEST = "./results/zugferd/pdfa/quickbrownfox4_cacert.pdf";
public static final String DEST1 = "./results/zugferd/pdfa/quickbrownfox4_cacert_sign.pdf";*/
public static final String SRC = "./results/withAttachment.pdf";
public static final String DEST = "./results/withAttachment_cacert.pdf";
public static final String DEST1 = "./results/withAttachment_cacert_sign.pdf";
public void sign(String src, String dest, Certificate[] chain, PrivateKey pk, String digestAlgorithm, String provider, PdfSigner.CryptoStandard subfilter,
String reason, String location, IOcspClient ocspClient, ITSAClient tsaClient, int estimatedSize)
throws GeneralSecurityException, IOException {
boolean appendMode = false;
System.out.println("_____________CERTIFICATE SIGN________________");
System.out.println(appendMode);
System.out.println("_____________________________________________");
// Creating the reader and the signer
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), appendMode);
signer.setCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING);
// Creating the appearance
PdfSignatureAppearance appearance = signer.getSignatureAppearance().setReason(reason).setLocation(location).setReuseAppearance(false);
Rectangle rect = new Rectangle(36, 648, 200, 100);
appearance.setPageRect(rect).setPageNumber(1);
signer.setFieldName("cert");
// Creating the signature
IExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
IExternalDigest digest = new BouncyCastleDigest();
Collection<ICrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));
signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, estimatedSize, subfilter);
}
public void signAgain(String src, String dest, Certificate[] chain, PrivateKey pk, String digestAlgorithm, String provider,
PdfSigner.CryptoStandard subfilter, String reason, String location, IOcspClient ocspClient, ITSAClient tsaClient,
int estimatedSize) throws GeneralSecurityException, IOException {
boolean appendMode = true;
System.out.println("_______________________ SIGN________________");
System.out.println(appendMode);
System.out.println("_____________________________________________");
// Creating the reader and the signer
PdfReader reader = new PdfReader(src);
PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), appendMode);
signer.setCertificationLevel(PdfSigner.NOT_CERTIFIED);
// Creating the appearance
PdfSignatureAppearance appearance = signer.getSignatureAppearance().setReason(reason).setLocation(location).setReuseAppearance(false);
Rectangle rect = new Rectangle(246, 648, 200, 100);
appearance.setPageRect(rect).setPageNumber(1);
signer.setFieldName("sig");
// Creating the signature
IExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm, provider);
IExternalDigest digest = new BouncyCastleDigest();
Collection<ICrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));
signer.signDetached(digest, pks, chain, crlList, ocspClient, tsaClient, estimatedSize, subfilter);
}
public void verifySignatures(String path) throws IOException, GeneralSecurityException {
System.out.println(path);
PdfReader reader = new PdfReader(path);
PdfDocument pdfDoc = new PdfDocument(reader);
SignatureUtil util = new SignatureUtil(pdfDoc);
List<String> names = util.getSignatureNames();
for (String name : names) {
System.out.println("===== " + name + " =====");
verifySignature(util, name);
}
System.out.println();
}
public PdfPKCS7 verifySignature(SignatureUtil util, String name) throws GeneralSecurityException, IOException {
System.out.println("Signature covers whole document: " + util.signatureCoversWholeDocument(name));
System.out.println("Document revision: " + util.getRevision(name) + " of " + util.getTotalRevisions());
PdfPKCS7 pkcs7 = util.verifySignature(name);
System.out.println("Integrity check OK? " + pkcs7.verify());
return pkcs7;
}
public void inspectSignatures(String path) throws IOException, GeneralSecurityException {
System.out.println(path);
PdfReader reader = new PdfReader(path);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
Map<String, PdfFormField> fields = form.getFormFields();
SignatureUtil util = new SignatureUtil(pdfDoc);
List<String> names = util.getSignatureNames();
SignaturePermissions perms = null;
for (String name : names) {
System.out.println("===== " + name + " =====");
perms = inspectSignature(fields.get(name), name, perms, util);
}
System.out.println();
}
public SignaturePermissions inspectSignature(PdfFormField field, String name, SignaturePermissions perms, SignatureUtil util) throws GeneralSecurityException, IOException {
PdfArray position = field.getWidgets().get(0).getRectangle();
if (position != null && position.size() > 0) {
float width = (float) (position.getAsNumber(2).getValue() - position.getAsNumber(0).getValue());
float height = (float) (position.getAsNumber(3).getValue() - position.getAsNumber(1).getValue());
if (width == 0 || height == 0) {
System.out.println("Invisible signature");
} else {
PdfPage page = field.getWidgets().get(0).getPage();
System.out.println(String.format("Field on page %s; llx: %s, lly: %s, urx: %s; ury: %s", page, position.getAsNumber(0).getValue(), position.getAsNumber(1).getValue(),
position.getAsNumber(2).getValue(), position.getAsNumber(3).getValue()));
}
}
PdfPKCS7 pkcs7 = verifySignature(util, name);
System.out.println("Digest algorithm: " + pkcs7.getHashAlgorithm());
System.out.println("Encryption algorithm: " + pkcs7.getEncryptionAlgorithm());
System.out.println("Filter subtype: " + pkcs7.getFilterSubtype());
X509Certificate cert = (X509Certificate) pkcs7.getSigningCertificate();
System.out.println("Name of the signer: " + CertificateInfo.getSubjectFields(cert).getField("CN"));
if (pkcs7.getSignName() != null)
System.out.println("Alternative name of the signer: " + pkcs7.getSignName());
SimpleDateFormat date_format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
System.out.println("Signed on: " + date_format.format(pkcs7.getSignDate().getTime()));
if (pkcs7.getTimeStampDate() != null) {
System.out.println("TimeStamp: " + date_format.format(pkcs7.getTimeStampDate().getTime()));
TimeStampToken ts = pkcs7.getTimeStampToken();
System.out.println("TimeStamp service: " + ts.getTimeStampInfo().getTsa());
System.out.println("TimeStamp verified? " + pkcs7.verifyTimestampImprint());
}
System.out.println("Location: " + pkcs7.getLocation());
System.out.println("Reason: " + pkcs7.getReason());
PdfDictionary sigDict = util.getSignatureDictionary(name);
PdfString contact = sigDict.getAsString(PdfName.ContactInfo);
if (contact != null)
System.out.println("Contact info: " + contact);
perms = new SignaturePermissions(sigDict, perms);
System.out.println("Signature type: " + (perms.isCertification() ? "certification" : "approval"));
System.out.println("Filling out fields allowed: " + perms.isFillInAllowed());
System.out.println("Adding annotations allowed: " + perms.isAnnotationsAllowed());
for (FieldLock lock : perms.getFieldLocks()) {
System.out.println("Lock: " + lock.toString());
}
return perms;
}
public static void main(String[] args) throws IOException, GeneralSecurityException {
String path = "./src/test/SupplierX.p12";
String astrasKSpath = "./src/test/ASTRAS_APP.p12";
char[] pass = "password".toCharArray();
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
KeyStore ks = KeyStore.getInstance("pkcs12", provider.getName());
ks.load(new FileInputStream(path), pass);
KeyStore astrasKs = KeyStore.getInstance("pkcs12", provider.getName());
astrasKs.load(new FileInputStream(astrasKSpath), pass);
SignWithCertificate app = new SignWithCertificate();
String alias = "ASTRAS_APP";
PrivateKey pk = (PrivateKey) astrasKs.getKey(alias, pass);
Certificate[] chain = astrasKs.getCertificateChain(alias);
app.sign(SRC, DEST, chain, pk, DigestAlgorithms.SHA256, provider.getName(), PdfSigner.CryptoStandard.CMS, "Test Sign Certificate", "München", null, null, 0);
alias = "SupplierX";
pk = (PrivateKey) ks.getKey(alias, pass);
chain = ks.getCertificateChain(alias);
app.signAgain(DEST, DEST1, chain, pk, DigestAlgorithms.SHA256, provider.getName(), PdfSigner.CryptoStandard.CMS, "Test Sign", "München", null, null, 0);
app.verifySignatures(DEST);
app.verifySignatures(DEST1);
}
}
我想知道iText如何验证而Adobe没有验证。任何帮助非常感谢(生成的文件在这里:https://drive.google.com/drive/folders/1e8gILlclqftqQRcRb7ZurRzqU-6h1oN-)。