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();
ArrayList names = 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());
}
}
}
