【发布时间】:2019-10-25 02:44:33
【问题描述】:
下面的代码有几个问题。
谷歌搜索,阅读 javadoc
import org.apache.pdfbox.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jcajce.util.MessageDigestUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Hex;
import javax.security.cert.CertificateEncodingException;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.*;
import java.text.SimpleDateFormat;
import java.util.*;
import static java.security.AlgorithmParameterGenerator.getInstance;
public class PDFProcess {
public static void main(String[] args) {
System.out.println("Assume customer has signed the prefilled.pdf. Read prefilled.pdf");
PDDocument document = null;
/*
* processes file anacreditForm-signed trusted which has password protection. both owner password 1234 or user password abce will work
*
*/
try {
File signedFile = new File("anacreditForm-signed expired not locked.pdf");
document = PDDocument.load(signedFile, "1234");
System.out.println("Number of pages" + document.getNumberOfPages());
PDDocumentCatalog pdCatalog = document.getDocumentCatalog();
PDAcroForm pdAcroForm = pdCatalog.getAcroForm();
for (PDField pdField : pdAcroForm.getFields()) {
System.out.println("Values found: " + pdField.getValueAsString());
}
System.out.println("Signed? " + pdAcroForm.isSignaturesExist());
if (pdAcroForm.isSignaturesExist()) {
PDSignatureField signatureField = (PDSignatureField) pdAcroForm.getField("signatureField");
System.out.println("Name: " + signatureField.getSignature().getName());
System.out.println("Contact Info: " + signatureField.getSignature().getContactInfo());
Security.addProvider(new BouncyCastleProvider());
List<PDSignature> signatureDictionaries = document.getSignatureDictionaries();
X509Certificate cert;
Collection<X509Certificate> result = new HashSet<X509Certificate>();
// Then we validate signatures one at the time.
for (PDSignature signatureDictionary : signatureDictionaries) {
// NOTE that this code currently supports only "adbe.pkcs7.detached", the most common signature /SubFilter anyway.
byte[] signatureContent = signatureDictionary.getContents(new FileInputStream(signedFile));
byte[] signedContent = signatureDictionary.getSignedContent(new FileInputStream(signedFile));
// Now we construct a PKCS #7 or CMS.
CMSProcessable cmsProcessableInputStream = new CMSProcessableByteArray(signedContent);
try {
CMSSignedData cmsSignedData = new CMSSignedData(cmsProcessableInputStream, signatureContent);
// get certificates
Store<?> certStore = cmsSignedData.getCertificates();
// get signers
SignerInformationStore signers = cmsSignedData.getSignerInfos();
// variable "it" iterates all signers
Iterator<?> it = signers.getSigners().iterator();
while (it.hasNext()) {
SignerInformation signer = (SignerInformation) it.next();
// get all certificates for a signer
Collection<?> certCollection = certStore.getMatches(signer.getSID());
// variable "certIt" iterates all certificates of a signer
Iterator<?> certIt = certCollection.iterator();
while (certIt.hasNext()) {
// print details of each certificate
X509CertificateHolder certificateHolder = (X509CertificateHolder) certIt.next();
System.out.println("Subject: " + certificateHolder.getSubject());
System.out.println("Issuer: " + certificateHolder.getIssuer());
System.out.println("Valid from: " + certificateHolder.getNotBefore());
System.out.println("Valid to: " + certificateHolder.getNotAfter());
//System.out.println("Public key: " + Hex.toHexString(certificateHolder.getSubjectPublicKeyInfo().getPublicKeyData().getOctets()));
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream in = new ByteArrayInputStream(certificateHolder.getEncoded());
X509Certificate cert2 = (X509Certificate) certFactory.generateCertificate(in);
// the validity of the certificate isn't verified, just the fact that one of the certs matches the given signer
SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder()
.build(cert2);
if (signer.verify(signerInformationVerifier)){
System.out.println("PDF signature verification is correct");
} else { System.out.println ("PDF signature verification failed");}
StringBuilder encodedChain = new StringBuilder();
encodedChain.append("-----BEGIN CERTIFICATE-----\n");
encodedChain.append(new String(Base64.getEncoder().encode(cert2.getEncoded())));
encodedChain.append("\n-----END CERTIFICATE-----\n");
System.out.println(encodedChain.toString());
//System.out.println("Public key: " + DatatypeConverter.printHexBinary(certificateHolder.getSubjectPublicKeyInfo().getPublicKeyData().getBytes()));
// SerialNumber isi BigInteger in java and hex value in Windows/Mac/Adobe
System.out.println("SerialNumber: " + certificateHolder.getSerialNumber().toString(16));
//result.add(new JcaX509CertificateConverter().getCertificate(certificateHolder));
CertificateFactory certificateFactory2 = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
InputStream is = new ByteArrayInputStream(certificateHolder.getEncoded());
KeyStore keyStore = PKISetup.createKeyStore();
PKIXParameters parameters = new PKIXParameters(keyStore);
parameters.setRevocationEnabled(false);
ArrayList<X509Certificate> start = new ArrayList<>();
start.add(cert2);
CertificateFactory certFactory3 = CertificateFactory.getInstance("X.509");
CertPath certPath = certFactory3.generateCertPath(start);
//CertPath certPath = certificateFactory.generateCertPath(is, "PKCS7"); // Throws Certificate Exception when a cert path cannot be generated
CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX", new BouncyCastleProvider());
// verifies if certificate is signed by trust anchor available in keystore. For example jsCAexpired.cer was removed as trust anchor - all certificates signed by jsCAexpired.cer will fail the check below
PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters); // This will throw a CertPathValidatorException if validation fails
System.out.println("Val result: " + validatorResult );
System.out.println("Subject was: " + cert2.getSubjectDN().getName());
System.out.println("Issuer was: " + cert2.getIssuerDN().getName());
System.out.println("Trust Anchor CA Name: " + validatorResult.getTrustAnchor().getCAName());
System.out.println("Trust Anchor CA: " + validatorResult.getTrustAnchor().getCA());
System.out.println("Trust Anchor Issuer DN:" + validatorResult.getTrustAnchor().getTrustedCert().getIssuerDN());
System.out.println("Trust Anchor SubjectDN:" + validatorResult.getTrustAnchor().getTrustedCert().getSubjectDN());
System.out.println("Trust Cert Issuer UID: " + validatorResult.getTrustAnchor().getTrustedCert().getIssuerUniqueID());
System.out.println("Trust Cert Subject UID: " + validatorResult.getTrustAnchor().getTrustedCert().getSubjectUniqueID());
System.out.println("Trust Cert SerialNumber: " + validatorResult.getTrustAnchor().getTrustedCert().getSerialNumber().toString(16));
System.out.println("Trust Cert Valid From: " + validatorResult.getTrustAnchor().getTrustedCert().getNotBefore());
System.out.println("Trust Cert Valid After: " + validatorResult.getTrustAnchor().getTrustedCert().getNotAfter());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
} //this.testValidateSignatureValidationTest();
document.close();
} catch (InvalidPasswordException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
代码读取受密码保护的 pdf,其中包含表单字段和签名字段。受信任的(根)证书位于 keystone 中。
问题一:查看附近的代码:
// the validity of the certificate isn't verified, just the fact that one of the certs matches the given signer
为什么要检查呢?这里可能出了什么问题?
问题2:查看附近的代码:
Collection<?> certCollection = certStore.getMatches(signer.getSID());
这会从属于签名者的 pdf 中获取证书。是不是在附近的代码中重复了:
SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder().build(cert2);
问题 3:如果 pdf 在签名后被修改,那么代码仍然会产生消息“PDF 签名验证正确”
我还以为检查失败了!签名后检测pdf被修改的java代码是什么?
问题4:见代码:
PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters);
如果证书路径未指向受信任的证书,则会失败。这不是比问题 1 中提到的检查更好的检查吗?
【问题讨论】:
-
我建议你也看看源代码下载中示例子项目中的 ShowSignature.java。这会进行更多检查,包括证书在签名时是否有效(对于链的其余部分相同),还可以判断签名是否涵盖整个文档。但它也不完美,它不检查链的根是否可信。
-
@TilmanHausherr “这会进行更多检查,包括证书在签名时是否有效” - 此类测试也隐式发生在 OP 的代码中,深入 BouncyCastle 调用中。但是这种隐式测试可能很麻烦。
标签: validation digital-signature pdfbox x509certificate verification