Как подписать PDF-файл со встроенной меткой времени и включенным LTV?

Я пытаюсь подписать PDF-файл с включенной временной меткой и LTV, чтобы он отображался в Adobe Reader следующим образом:

Правильная подпись

На английском это означает, что «подпись включает встроенную метку времени» и «подпись включена LTV». Вот код, который я использую:

PrivateKey pk = // get pk from an encrypting certificate created using encrypting file system
Certificate[] chain = ks.getCertificateChain(alias);

PdfReader reader = new PdfReader(src);
FileOutputStream fout = new FileOutputStream(dest);
PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');

PdfSignatureAppearance sap = stp.getSignatureAppearance();

ExternalSignature signature = new PrivateKeySignature(pk, "SHA-512", "SunMSCAPI");
TSAClient tsc = null;
String url = // TSA URL
tsc = new TSAClientBouncyCastle(url, null, null, 4096, "SHA-512");

List<CrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));

ExternalDigest digest = new BouncyCastleDigest();
MakeSignature.signDetached(sap, digest, signature, chain, crlList, null, tsc, 0, CryptoStandard.CMS);

Основываясь на этом ответе, мне нужен способ получить CRL для сертификата TSA в CrlList, но ... как я могу получить сертификат TSA? Нужно ли мне делать timestamp-query запрос к TSA и читать ответ, а затем добавлять его в CrlList? Обратите внимание, что это уже делается внутри MakeSignature.signDetached, когда он вызывает sgn.getEncodedPKCS7. Обратите внимание, что я использую бесплатный сервер TSA.

Это то, что показано в Adobe Reader с приведенным выше кодом. Детали подписи:  введите описание изображения здесь

Сведения о метке времени:  введите описание изображения здесь

Сведения о сертификате TSA:  введите описание изображения здесь


ОБНОВЛЕНИЕ

Поскольку это был бесплатный сервер TSA, мне просто нужно было добавить сертификат сервера TSA в Adobe Trusted Certificates, и теперь он работает. Но я сделал еще один тест, используя смарт-карту для подписи документа, и вот что у меня есть (я добавил корневой сертификат в доверенные сертификаты в Adobe):

Детали подписи:

введите описание изображения здесь

Реквизиты сертификата подписи:

введите описание изображения здесь

На основании этой ссылки, LTV включен означает, что вся информация, необходимая для проверки файл (без корневых сертификатов) содержится в PDF-файле. Таким образом, PDF-файл поддерживает LTV, если он подписан правильно и содержит все необходимые сертификаты и действительный ответ CRL или OSCP для каждого сертификата, а также, если он включает подписи поверх CRL и OCSP, а не только сертификат подписи. Похоже, я выполнил все эти требования или что-то упустил? Если да, то как я могу узнать, чего не хватает для получения PDF-файла с поддержкой LTV?


person lucasdc    schedule 20.09.2017    source источник
comment
Обратите внимание, что я использую бесплатный сервер TSA. - вы проверили, доверяет ли Adobe этому бесплатному серверу?   -  person mkl    schedule 20.09.2017
comment
Добавление его в список доверенных сертификатов в Adobe сработало. Но .. есть ли способ сделать это с помощью кода?   -  person lucasdc    schedule 20.09.2017
comment
Есть ли способ сделать это с помощью кода? - Не должно быть. Это то, кому доверяет пользователь. По умолчанию пользователь делегирует обслуживание якорей доверия в основном Adobe. Таким образом, только пользователь (путем редактирования параметров безопасности) и Adobe (путем предоставления обновлений безопасности) должны иметь возможность изменять эти параметры.   -  person mkl    schedule 20.09.2017


Ответы (1)


Во-первых, на основе комментария @mkl я добавил сертификат сервера TSA в доверенные сертификаты Adobe, чтобы сообщение

подпись включает встроенную метку времени, но ее не удалось проверить

стал

подпись включает встроенную метку времени

И решить

Подпись не поддерживает LTV и истечет через (...)

Я мог заметить, что используя

List<CrlClient> crlList = new ArrayList<>();
crlList.add(new CrlClientOnline(chain));

некоторые списки отзыва сертификатов (некоторые сертификаты имели более одной точки распространения) не добавлялись, в результате чего LTV в формате pdf не был включен. Чтобы решить эту проблему, я сделал так:

// long term validation (LTV)
List<CrlClient> crlList = new ArrayList<>();

for(Certificate cert : chain) {
    X509Certificate c = (X509Certificate)cert;
    List<String> crls = this.getCrlDistributionPoints(c);
    if(crls != null && !crls.isEmpty()) {
        crlList.add(new CrlClientOnline(crls.toArray(new String[crls.size()])));
    }
}

private List<String> getCrlDistributionPoints(final X509Certificate cert) throws Exception {
    final byte[] crldpExt = cert.getExtensionValue(X509Extension.cRLDistributionPoints.getId());
    if (crldpExt == null) {
        final List<String> emptyList = new ArrayList<String>();
        return emptyList;
    }
    ASN1InputStream oAsnInStream = null;
    ASN1InputStream oAsnInStream2 = null;
    List<String> crlUrls = new ArrayList<String>();

    try { 
        oAsnInStream = new ASN1InputStream(new ByteArrayInputStream(crldpExt));
        final ASN1Object derObjCrlDP = oAsnInStream.readObject();
        final DEROctetString dosCrlDP = (DEROctetString) derObjCrlDP;
        final byte[] crldpExtOctets = dosCrlDP.getOctets();
        oAsnInStream2 = new ASN1InputStream(new ByteArrayInputStream(crldpExtOctets));
        final ASN1Object derObj2 = oAsnInStream2.readObject();
        final CRLDistPoint distPoint = CRLDistPoint.getInstance(derObj2);
        for (final DistributionPoint dp : distPoint.getDistributionPoints()) {
            final DistributionPointName dpn = dp.getDistributionPoint();
            // Look for URIs in fullName
            if (dpn != null) {
                if (dpn.getType() == DistributionPointName.FULL_NAME) {
                    final GeneralName[] genNames = GeneralNames.getInstance(dpn.getName()).getNames();
                    // Look for an URI
                    for (int j = 0; j < genNames.length; j++) {
                        if (genNames[j].getTagNo() == GeneralName.uniformResourceIdentifier) {
                            final String url = DERIA5String.getInstance(genNames[j].getName()).getString();
                            crlUrls.add(url);
                        }
                    }
                }
            }
        }
    } catch(IOException e) {
        throw new Exception(e.getMessage(), e);
    } finally {
        IOUtils.closeQuietly(oAsnInStream);
        IOUtils.closeQuietly(oAsnInStream2);
    }

    return crlUrls;
}
person lucasdc    schedule 20.09.2017