在使用Java

时间:2019-06-10 17:18:08

标签: validation digital-signature pdfbox x509certificate verification

下面的代码有几个问题。

googled,阅读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,其中包含表单字段和签名字段。受信任的(根)证书是基石。

问题1:查看附近的代码:

// 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中提到的支票好吗?

1 个答案:

答案 0 :(得分:2)

首先,您向我们展示一些未知来​​源的代码,并提出有关问题。由于我们不了解其上下文,因此答案可能有点含糊或似乎与实际上下文不符。

问题1:

  

查看附近的代码:

// the validity of the certificate isn't verified, just the fact that one of the certs matches the given signer
     

为什么要检查一下?这里可能出什么问题了?

(通过“靠近……的代码”,您指的是确切的代码?因为尚不清楚,我尝试将注释放在上下文中...)

这时发生的一切是,对于当前的SignerInfo对象,其中的SignerIdentifier对象已用于将签名容器中包含的证书之一标识为声明的签名者证书(是的,实际上存在多个可能的匹配的循环,但常见的情况是找到一个完全匹配的匹配,其他所有都应视为可疑)。

因此,代码尚未真正验证证书,但已确定稍后要验证哪个证书(并用于验证签名)。

所以...

  • “为什么要检查那个?” -尚未检查。
  • “这里可能出什么问题了?” -可能在签名容器的证书中找不到声明的签名者证书,或者找到了多个候选者。 您的代码没有为前一种情况提供策略,甚至没有打印警告或错误。在后一种情况下,它将测试每个候选人。通常情况下,最多可以使用一张候选证书进行验证。

问题2:

  

查看附近的代码:

Collection certCollection = certStore.getMatches(signer.getSID());
     

这将从属于签名者的pdf中获取证书。不是在附近的代码中重复吗:

SignerInformationVerifier signerInformationVerifier = new JcaSimpleSignerInfoVerifierBuilder().build(cert2);

(通过“靠近...的代码”,您指的是确切的代码?因为尚不清楚,我认为您的意思是所引用的代码行)

“这将从属于签名者的pdf中获取证书。” -好吧,严格来说,它从与SignerIdentifier匹配的PDF中存储的签名容器中存储的证书中检索签名者证书的候选人

“代码中没有重复吗……”-否,那里的代码构造了BouncyCastle SignerInformationVerifier,可以有效地将多个验证程序实用程序对象捆绑在一起用于签名的不同方面。使用在先前代码中检索的候选签名者证书初始化此对象。因此,没有重复。

问题3:

  

如果在签名后对pdf进行了修改,则代码仍会生成消息“ PDF签名验证正确”,我会认为检查失败!检测签名后修改了pdf的Java代码是什么?

这取决于如何 修改pdf !有两种选择,一种是通过增量更新来应用更改(在这种情况下,原始签名的PDF字节被复制而没有更改,并且此后附加了更改),或者在其他情况下(在原始签名的PDF字节不被添加的情况下) 构成更改后的PDF的开头。

在后一种情况下,原始签名的字节会更改,您的代码将显示“ PDF签名验证失败”。

但是,在前一种情况下,签名的字节不变,并且您的代码将显示“ PDF签名验证正确”。为了进行这种更改,您还必须检查已签名的PDF字节是否是整个PDF(除了为CMS签名容器保留的位置),还是没有其他字节。

有关某些详细信息,请阅读this answer,有关被认为允许的更改,请参见this answer

问题4:

  

查看代码:

PKIXCertPathValidatorResult validatorResult = (PKIXCertPathValidatorResult) certPathValidator.validate(certPath, parameters);
     

如果证书路径未生成可信证书,则此操作将失败。这比问题1中提到的支票好吗?

如上所述,导致问题1的代码根本不是 check ,它是关于确定最终应进行检查的证书。不过,这里的代码实际上是使用先前确定的证书并实际上是检查

戒备

问题1、2和4本质上是关于了解验证CMS签名容器时应采取的步骤。特别是您必须

  • 确定一个签名者证书(您的代码是根据SignerIdentifier值来确定的;由于此值本身不是经过签名的,因此,如今人们认为仅凭此标准是不够的,并另外使用签名的属性(ESSCertIDESSCertIDv2);
  • 验证证书候选者可以用来验证密码签名值(在您的情况下,在signer.verify(signerInformationVerifier)期间)
  • 验证签名文档范围的哈希值是否与messageDigest签名属性的值匹配(在您的情况下,在signer.verify(signerInformationVerifier)期间也是如此)
  • 验证签名者证书是否可以信任(在您的情况下,certPathValidator.validate)。

问题3本质上是关于了解验证集成在PDF中的CMS签名容器时要采取的其他步骤。特别是您必须

  • 检查已签名的字节范围是否包含除签名容器剩余的占位符之外的所有PDF(不是由您的代码完成)。