После раздела электронной книги 4.3.3 " Цифровая подпись для документа PDF " Я пытаюсь создать рабочий пример, где:
- у клиента есть PDF для подписии только общедоступный сертификат
- Внешнее HW (с частным сертификатом) получает хэш и возвращает подписанный хэш
Я пытался сделать это, но подпись внутри PDF показывает мнечто файл был изменен после процесса подписания.
Следующий код берет исходный PDF-файл и общедоступный сертификат, создает временный PDF-файл с пустым знаком и возвращает HASH
Этот хэш отправляется извне в другое удаленное приложение (где есть ответчик)Приватный сертификат) и возвращает подписанный хеш, я прочитал подписанный хеш и добавил его во временный PDF-файл.
Обновлен полный рабочий код:
package com.Marloo;
import org.apache.commons.codec.Charsets;
import org.bouncycastle.util.encoders.Base64;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
public class Test {
public static final String CERT = "src/main/resources/certificate.pem";
public static final String SRC = "src/main/resources/tmp.pdf";
public static final String DEST = "src/main/resources/signed.pdf";
public static void main(String args[]) throws IOException {
getHash(SRC, CERT);
}
public static void getHash(String doc, String cert) throws IOException {
try {
File initialFile = new File(cert);
InputStream is = new FileInputStream(initialFile);
// We get the self-signed certificate from the client
CertificateFactory factory = CertificateFactory.getInstance("X.509");
Certificate[] chain = new Certificate[1];
chain[0] = factory.generateCertificate(is);
// we create a reader and a stamper
PdfReader reader = new PdfReader(doc);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfStamper stamper = PdfStamper.createSignature(reader, baos, '\0');
// we create the signature appearance
PdfSignatureAppearance sap = stamper.getSignatureAppearance();
sap.setReason("TEST REASON");
sap.setLocation("TEST LOCATION");
//sap.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig"); //visible
sap.setVisibleSignature(new Rectangle(36, 748, 36, 748), 1, "sig"); //invisible
sap.setCertificate(chain[0]);
// we create the signature infrastructure
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(sap.getReason());
dic.setLocation(sap.getLocation());
dic.setContact(sap.getContact());
dic.setDate(new PdfDate(sap.getSignDate()));
sap.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
sap.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest() {
public MessageDigest getMessageDigest(String hashAlgorithm)
throws GeneralSecurityException {
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, externalDigest, false);
InputStream data = sap.getRangeStream();
byte hash[] = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
// we get OCSP and CRL for the cert
OCSPVerifier ocspVerifier = new OCSPVerifier(null, null);
OcspClient ocspClient = new OcspClientBouncyCastle(ocspVerifier);
byte[] ocsp = null;
if (chain.length >= 2 && ocspClient != null) {
ocsp = ocspClient.getEncoded((X509Certificate) chain[0], (X509Certificate) chain[1], null);
}
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, null, null, MakeSignature.CryptoStandard.CMS);
InputStream sh_is = new ByteArrayInputStream(sh);
byte[] signedAttributesHash = DigestAlgorithms.digest(sh_is, externalDigest.getMessageDigest("SHA256"));
System.out.println("----------------------------------------------");
System.out.println("Hash to be sign:");
System.out.println( new String(Base64.encode(signedAttributesHash), Charsets.UTF_8));
System.out.println("----------------------------------------------");
System.out.println("Insert b64 signed hash [ENTER]");
System.out.println("----------------------------------------------");
Scanner in = new Scanner(System.in);
String signedHashB64 = in.nextLine();
System.out.println( signedHashB64);
ByteArrayOutputStream os = baos;
byte[] signedHash = org.apache.commons.codec.binary.Base64.decodeBase64(signedHashB64.getBytes());
// we complete the PDF signing process
sgn.setExternalDigest(signedHash, null, "RSA");
Collection<byte[]> crlBytes = null;
TSAClientBouncyCastle tsaClient = new TSAClientBouncyCastle("http://timestamp.gdca.com.cn/tsa", null, null);
byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsaClient, ocsp, crlBytes, MakeSignature.CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSig, 0, paddedSig, 0, encodedSig.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
try {
sap.close(dic2);
} catch (DocumentException e) {
throw new IOException(e);
}
FileOutputStream fos = new FileOutputStream(new File(DEST));
os.writeTo(fos);
System.out.println("pdfsig " + System.getProperty("user.dir") + "/" + DEST);
System.out.println("------------------End Of Life --------------------------");
System.exit(0);
} catch (GeneralSecurityException e) {
throw new IOException(e);
} catch (DocumentException e) {
throw new IOException(e);
}
}
}
Вот некоторые ![screenshot](https://i.stack.imgur.com/5t9Uu.png)
Несколько подсказок: в этом неполном посте автор говорит:
"После долгих отладок мы наконец нашли проблему.
По какой-то таинственной причине метод, который генерирует хеш документа, был выполнен дважды, что делает недействительным первый хеш (который мы используем для отправки в службу).
После рефакторинга кода,оригинальный код работал правильно.
Большое спасибо всем людям, которые мне помогают, особенно mkl. "
, но никакой дополнительной информации предоставлено не было, также пишется Time on Штамп и время от TSA различаются по назначению.Я думаю, что это не будет проблемой.
Некоторые намеки?
Спасибо
Обновление 1
(предыдущий код был обновлен)
внешние службы не принимают на входе всю структуру Sign, а только32-байтовый хэш
![screenshot2](https://i.stack.imgur.com/j9kxw.png)
теперь sh var никогда не используется!
Я беру хеш-байт [], отправляю на него, но снова Adobe Reader сообщает, что файл был изменен.
Может быть, я могу попробовать метод "невидимая подпись".Или «видимый штамп» не имел значения в процессе проверки подписи?
Или, может быть, мне нужно как-то воссоздать структуру ANS.1 с подписанным байтом, а затем подписать документ?
Может быть, время между ца и знаком должно быть одинаковым?
![Screenshot3](https://i.stack.imgur.com/PzEks.png)
Буду признателен за любую помощь.
Спасибо
Обновление 2 - РАБОЧЕЕ РЕШЕНИЕ !!!
Действительно, действительно, спасибо MKL за ответ!
рабочее исправление состояло в том, что нам нужно сгенерировать хэш подписанных / аутентифицированных атрибутов внутри пакета PKCS # 7 !!!См. Оригинальный код в переменной signedAttributesHash
![signed pdf image](https://i.stack.imgur.com/cACeX.jpg)