ETDA / PDFSigningAndTimestamp

Simple example project for PDF signing and timstamping
17 stars 16 forks source link

Signature is not LTV enabled #15

Closed saranpol closed 3 years ago

saranpol commented 3 years ago

Hi owner,

I see your code has "For LTV Enable" section but when is sign pdf the signature is not LTV enabled. I use PKCS11 with eToken.

What is possible reason ? image

nathamETDA commented 3 years ago

@saranpol The problem might occurred from eToken does not have certificate chain or application can't connect CDP to get CRL? Could you confirm that eToken have certificate chain and attach test file?

sala-daeng commented 3 years ago

I debug the certificateChain image and the output are ...

certificateChain length 1 certificateChain [Ljava.security.cert.Certificate;@490d6c15

here is and output pdf tsa_signed.pdf

this dialog show when open pdf and take 10-20 seconds and disappear image

why you have this line of code in for loop ? if(i==certificateChain.length-1) {break;}

nathamETDA commented 3 years ago

@sala-daeng from the debug log and output pdf, there is only 1 certificate (signer certificate) in chain. image

Could you confirm how many certificate in the eToken? This code require full certificate chain provided to get complete CRL list. It does not automatically retrieve chain from its certificate information.

The dialog shown because Adobe try to check certificate revocation information due to pdf file does not have any revocation information (CRL).

if(i==certificateChain.length-1) {break;} This line of code is for skipping Root CA revocation information retrieve, Root CA revocation information is not required to check certificate validity. I aware that there is a better way to check this. However due to developer shortage, it might a long time to improve this code.

saranpol commented 3 years ago

My eToken have only 1 certificate, requested from https://www.thaidigitalid.com

so the certificateChain length should have more than one right?

I try this pull request https://github.com/ETDA/PDFSigningAndTimestamp/pull/13

found that it can get about 1700 Certificate Revocation List (CRLs)

and I need to increase signature size *10

but I still can't make LTV enable.

nathamETDA commented 3 years ago

@saranpol Default eToken from CA might have only 1 certificate because there are other ways to get CRL, not from eToken only.

However this code uses certificates from eToken to get CRL. You can add other certificates into it via Safenet client tool.

Yes, the chain length from eToken to be more than 1 in this case.

New PDF file might not have CRL for complete certificate chain.

Could you provide the new sample PDF file?

sala-daeng commented 3 years ago

Here is pdf from https://github.com/ETDA/PDFSigningAndTimestamp/pull/13 tsa_signed.pdf

What can I do to enable LTV ? I have only one eToken and one certificate from thaidigitalid. How can I add other CRL to make LTV enable ?

image

image

nathamETDA commented 3 years ago

@sala-daeng This file has the revocation information for signer certificate, however it is not LTV because there is not revocation information for complete certificate chain. There are 2 options to use this code to sign LTV on Adobe

  1. Import certificate chain into eToken that has signer certificate using Safenet Authentication Client
  2. Modify code to get certificate chain and add them into certificate chain variable before it get revocation information.
sala-daeng commented 3 years ago

@nathamETDA Thank you very much in advance. We really need your help.

"This file has the revocation information for signer certificate" << Do you mean only second tsa_signed.pdf from https://github.com/ETDA/PDFSigningAndTimestamp/pull/13 code has revocation information or both tsa_signed.pdf has revocation information ?

For option 1 : 1.1 Where is certificate chain how can I get it from, is it a file ? 1.2 Which Menu of Safenet Authentication Client to import certificate chain ?

For option 2 : 2.1 Which code to modify do you mean from pull request 13 https://github.com/ETDA/PDFSigningAndTimestamp/pull/13 ? 2.2 If you mean from pull request 13, this is important part of code I modify It already get certificateChain in public static void signWithTSA function before it get crlList in public byte[] sign so what is the cause of no revocation information for complete certificate chain ?

(here is source code of modify from pull request 13) PDFSigningAndTimestamp_LTV_1.zip

    boolean signPdf(File pdfFile, File signedPdfFile) throws IOException {
        PDDocument doc = null;
        try {
            doc = PDDocument.load(pdfFile);
            OutputStream fos = new FileOutputStream(signedPdfFile);
            PDSignature signature = new PDSignature();
            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
            signature.setSignDate(Calendar.getInstance());

            COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject();
            catalogDict.setNeedToBeUpdated(true);

            // For big certificate chain
            SignatureOptions signatureOptions = new SignatureOptions();
//          signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 2);
            signatureOptions.setPreferredSignatureSize(SignatureOptions.DEFAULT_SIGNATURE_SIZE * 10);

            doc.addSignature(signature, this, signatureOptions);
            doc.saveIncremental(fos);

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            if(doc != null){
                doc.close();
            }
        }
    }
    @Override
    public byte[] sign(InputStream is) throws IOException {
        try {
            List<Certificate> certList = new ArrayList<>();
            certList.addAll(Arrays.asList(certificateChain));
            @SuppressWarnings("rawtypes")
            Store certStore = new JcaCertStore(certList);

            ContentSigner sha512Signer = new JcaContentSignerBuilder("SHA256WithRSA").build(privateKey);

            org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate
                    .getInstance(ASN1Primitive.fromByteArray(certificate.getEncoded()));

            ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
            signedAttributes.add(new Attribute(CMSAttributes.contentType, new DERSet(CMSObjectIdentifiers.data)));

            // =========================== For LTV Enable ===========================
            List<CRL> crlList = new DssHelper().readCRLsFromCert((X509Certificate) certificate);
            CertificateList[] certRevList = new CertificateList[crlList.size()];

            System.out.println("crlList ###############################");
            System.out.println(crlList.toString());

            for(int i=0; i<crlList.size(); i++) {
                X509CRL crl = (X509CRL) crlList.get(0);
                X509CRLHolder crlHolder = new X509CRLHolder(crl.getEncoded());
                certRevList[i] = crlHolder.toASN1Structure();
            }

            List<OCSPResponse> ocspList = new ArrayList<OCSPResponse>();
            for (int i=0; i<certificateChain.length; i++) {
                X509Certificate certTemp = (X509Certificate) certificateChain[i];
                if (!certTemp.getIssuerDN().equals(certTemp.getSubjectDN())) {

                    X509Certificate issuerCert = null;
                    if (i+1 < certificateChain.length) {
                        issuerCert = (X509Certificate) certificateChain[i + 1];
                    }
                    if(issuerCert == null) {
                        issuerCert = new GetOcspResp().getIssuerCert(certTemp);
                    }
                    OCSPResp ocspResp = new GetOcspResp().getOcspResp(certTemp, issuerCert);
                    if (ocspResp != null) {
                        ocspList.add(OCSPResponse.getInstance(ocspResp.getEncoded()));
                    }
                }
            }

            OCSPResponse[] ocsps = new OCSPResponse[ocspList.size()];
            for(int i=0; i<ocspList.size(); i++) {
                ocsps[i] = ocspList.get(i);
            }

            RevocationValues revValues = new RevocationValues(certRevList, ocsps, null);

            signedAttributes.add(new Attribute(new ASN1ObjectIdentifier("1.2.840.113583.1.1.8"), new DERSet(revValues)));

            // =========================== For LTV Enable ===========================

            AttributeTable signedAttributesTable = new AttributeTable(signedAttributes);
            signedAttributesTable.toASN1EncodableVector();
            DefaultSignedAttributeTableGenerator signedAttributeGenerator = new DefaultSignedAttributeTableGenerator(
                    signedAttributesTable);

            SignerInfoGeneratorBuilder signerInfoBuilder = new SignerInfoGeneratorBuilder(
                    new JcaDigestCalculatorProviderBuilder().build());
            signerInfoBuilder.setSignedAttributeGenerator(signedAttributeGenerator);

            SignerInfoGenerator signerInfoGen = signerInfoBuilder.build(sha512Signer, new X509CertificateHolder(cert));

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            gen.addSignerInfoGenerator(signerInfoGen);

            gen.addCertificates(certStore);

            CMSProcessableInputStream msg = new CMSProcessableInputStream(is);
            CMSSignedData signedData = gen.generate(msg,false);

            if(tsaClient!= null)
                signedData = signTimeStamps(signedData);

            return signedData.getEncoded();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    public static void signWithTSA(String passwordP12, String inputFileP12, String inputFileName, String outputFile,
            String filePath, String tsaUrl, String keystorePath, String keystorePassword, String keystoreType, String selectedAlias)
            throws IOException, GeneralSecurityException, SignatureException {

        KeyStore keystore = null;
        char[] password = passwordP12.toCharArray();

        if(keystoreType.equals("PKCS12")) {
            keystore = KeyStore.getInstance(keystoreType);
            keystore.load(new FileInputStream(filePath + inputFileP12), password);
        }

        else if(keystoreType.equals("PKCS11")) {
            String configString = "";
            configString = new String(Files.readAllBytes(Paths.get(filePath + inputFileP12)));

            ByteArrayInputStream confStream = new ByteArrayInputStream(configString.getBytes());
            Provider p = new sun.security.pkcs11.SunPKCS11(confStream);
            Security.addProvider(p);

            keystore = KeyStore.getInstance(keystoreType,p);
            keystore.load(null,password);
        }

        if(selectedAlias != "") {
            privateKey = (PrivateKey) keystore.getKey(selectedAlias, password);
            certificate = keystore.getCertificate(selectedAlias);
            certificateChain = keystore.getCertificateChain(selectedAlias);
        }else{
            Enumeration<String> aliases = keystore.aliases();
            while(aliases.hasMoreElements()) {
                String alias = (String)aliases.nextElement();
                System.out.println(alias);
                privateKey = (PrivateKey) keystore.getKey(alias, password);
                certificate = keystore.getCertificate(alias);
                certificateChain = keystore.getCertificateChain(alias);
            }
        }

        System.out.println("certificateChain ###############################");
        System.out.println(certificateChain.toString());

        if(!tsaUrl.isEmpty() && tsaUrl != null){

            MessageDigest digest = MessageDigest.getInstance("SHA-256");
//          tsaClient = new TSAClient(new URL(tsaUrl), filePath+keystorePath,
//                  keystorePassword,"PKCS12", digest);
            tsaClient = new TSAClient(new URL(tsaUrl), "",
                    "", digest);
        }

        File inFile = new File(filePath + inputFileName);
        File outFile = new File(filePath + outputFile);
        new SignAndTimeStamp().signPdf(inFile, outFile);
    }

Thank you very very much again.

nathamETDA commented 3 years ago

1st pdf file does not have any revocation info, 2nd file has revocation info for signer cert. However LTV signature requires revocation info for complete chain so 2nd file still not show LTV.

For option 1 1.1 Certificate chain is a list of certificate files (Root CA cert, intermidiate issuer cert, signer cert), it is the source of trust for signer certificate. This code use it to get complete revocation info. You can get certificate chain from signature properties in Adobe Reader when open pdf file or you can download them from link in signer and issuer certificates or from CA website. 1.2 This manual has how to import cert into eToken. SafeNet Authentication Client_9.0_Users_Guide.pdf

For option 2 Certificate chain was retrieved in signWithTSA(), however the chain is not complete (length = 1) because it get chain from token which does not have complete chain. You have to put intermediate issuer CA certificate into chain at least after chain was retrieved and before revocation check process in sign().

sala-daeng commented 3 years ago

You finally saved my life.

image

image

I export 2 .cer file from pdf (Root CA cert, intermidiate issuer cert) and import into eToken This work with original source code of PDFSigningAndTimestamp (no need https://github.com/ETDA/PDFSigningAndTimestamp/pull/13)

nathamETDA commented 3 years ago

I'm glad to hear that.