Zacznijmy od wygenerowania repozytorium z parą takich kluczy przy pomocy keytool'a
keytool -genkey -alias kuba -keypass demokeypass -storepass demostorepass -keystore demo-keystore.jks -validity 360 -dname "CN=Jakub Pawlowski, OU=blog, O=javaspotlight.blogspot.com, L=Lodz, S=Lodzkie, C=PL"
Następnie należy wyeksportować klucz publiczny
keytool -export -alias kuba -keypass demokeypass -storepass demostorepass -keystore demo-keystore.jks -file demo-cert.cer
Poniższe polecenia umożliwiają weryfikacje zawartości obu plików
keytool -list -alias kuba -storepass demostorepass -v -keystore ~/tmp/demo-keystore.jks keytool -printcert -v -file ~/tmp/demo-cert.cer
Do podpisania PDF'a użyłem oczywiście biblioteki iText (wymagana jest także biblioteka Bouncy Castle).
Klasa odpowiedzialna za podpisanie PDF'a wygląda następująco
import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfSignatureAppearance; import com.itextpdf.text.pdf.PdfStamper; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableEntryException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.util.logging.Logger; public class PdfSigner { private static final Logger log = Logger.getLogger(PdfSigner.class.getName()); private static final String PDF_PATH = "/home/kuba/tmp/demo-doc.pdf"; private static final String PDF_PATH_SIGNED = "/home/kuba/tmp/demo-doc-signed.pdf"; private static final String KEYSTORE_PATH = "/home/kuba/tmp/demo-keystore.jks"; private static final String KEYSTORE_PASSWORD = "demostorepass"; private static final String KEY_ALIAS = "kuba"; private static final String KEY_PASSWORD = "demokeypass"; private Certificate[] certificates; PrivateKey privateKey; public PdfSigner() { try { KeyStore ks = KeyStore.getInstance("JKS"); FileInputStream fis = new FileInputStream(KEYSTORE_PATH); ks.load(fis, KEYSTORE_PASSWORD.toCharArray()); certificates = ks.getCertificateChain(KEY_ALIAS); privateKey = (PrivateKey)ks.getKey(KEY_ALIAS, KEY_PASSWORD.toCharArray()); } catch (FileNotFoundException ex) { log.severe(ex.getMessage()); } catch (KeyStoreException ex) { log.severe(ex.getMessage()); } catch (IOException ex) { log.severe(ex.getMessage()); } catch (NoSuchAlgorithmException ex) { log.severe(ex.getMessage()); } catch (CertificateException ex) { log.severe(ex.getMessage()); } catch (UnrecoverableEntryException ex) { log.severe(ex.getMessage()); } } public void signPdf(){ try { PdfReader pdfReader = new PdfReader(PDF_PATH); FileOutputStream fos = new FileOutputStream(PDF_PATH_SIGNED); PdfStamper stamper = PdfStamper.createSignature(pdfReader, fos, '\0'); PdfSignatureAppearance appearance = stamper.getSignatureAppearance(); appearance.setReason("Demo Blog"); appearance.setCrypto(privateKey, certificates, null, PdfSignatureAppearance.WINCER_SIGNED); appearance.setCertificationLevel(PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED); stamper.close(); log.info("Document signed"); } catch (IOException ex) { log.severe(ex.getMessage()); } catch (DocumentException ex) { log.severe(ex.getMessage()); } } }
Po zaimportowaniu klucza publicznego do Adobe Reader'a i otwarciu podpisanego dokumentu, powinny pokazać się następujące informacje o certyfikacie
Poprawność podpisu można także sprawdzić programowo
import com.itextpdf.text.pdf.AcroFields; import com.itextpdf.text.pdf.PdfPKCS7; import com.itextpdf.text.pdf.PdfReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Calendar; import java.util.logging.Logger; public class PdfValidator { private static final Logger log = Logger.getLogger(PdfValidator.class.getName()); private static final String PDF_PATH_SIGNED = "/home/kuba/tmp/demo-doc-signed.pdf"; private static final String CERTIFICATE_PATH = "/home/kuba/tmp/demo-cert.cer"; private KeyStore keyStore; public PdfValidator() { try { keyStore = KeyStore.getInstance("JKS"); keyStore.load(null, null); FileInputStream fis = new FileInputStream(CERTIFICATE_PATH); CertificateFactory cf = CertificateFactory.getInstance("X509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(fis); keyStore.setCertificateEntry("cacert", cert); } catch (FileNotFoundException ex) { log.severe(ex.getMessage()); } catch (KeyStoreException ex) { log.severe(ex.getMessage()); } catch (IOException ex) { log.severe(ex.getMessage()); } catch (NoSuchAlgorithmException ex) { log.severe(ex.getMessage()); } catch (CertificateException ex) { log.severe(ex.getMessage()); } } public void validatePdf(){ try { PdfReader reader = new PdfReader(PDF_PATH_SIGNED); AcroFields af = reader.getAcroFields(); ArrayListnames = af.getSignatureNames(); for (String name : names) { PdfPKCS7 pk = af.verifySignature(name); Calendar calendar = pk.getSignDate(); Certificate[] certificates = pk.getCertificates(); log.info("Revision modified: " + !pk.verify()); Object[] fails = PdfPKCS7.verifyCertificates(certificates, keyStore, null, calendar); if (fails == null) { log.info("Certificates verified against the KeyStore"); } else { log.info("Certificate failed: " + fails[1]); } } } catch (SignatureException ex) { log.severe(ex.getMessage()); } catch (IOException ex) { log.severe(ex.getMessage()); } } }