piątek, 14 stycznia 2011

Lektura na weekend

Przymierzałem się do tego od jakiegoś czasu i skuszony pozytywną recenzją Jacka Laskowskiego zakupiłem dzisiaj książkę (podobno lektura obowiązkowa dla każdego programisty Javy) "Hibernate. Od nowicjusza do profesjonalisty" wydaną nakładem wydawnictwa Power Net (oryg. Apress). Po pobieżnym przejrzeniu mogę powiedzieć - warto było. Jedynym chyba minusem jest fakt, że nie jest to II wydanie tej pozycji, które ukazało się w ubiegłym roku. Niestety jak dowiedziałem się w wydawnictwie nie planują oni upgrade'u. Szkoda, że dzisiaj już nie wezmę się za lekturę. Jutro egzamin na 7 Kup - trzeba jeszcze powtórzyć układy i teorię :)

sobota, 8 stycznia 2011

Cyfrowe podpisywanie plików PDF

Dzisiejszy post trochę odbiega od poprzednich, niemniej jednak uznałem, że temat jest warty uwagi. W tym tygodniu zajmowałem się podpisywaniem plików PDF przy pomocy klucza prywatnego i weryfikacją takiego podpisu w oparciu o klucz publiczny.

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

wtorek, 21 grudnia 2010

Contexts and Dependency Injection – część II

Drugi z serii wpisów na temat CDI poświęcony będzie zdarzeniom. Pozwalają one luźno powiązać ze sobą bean'y na zasadzie Akcja → Reakcja.
Załóżmy, że nasza aplikacja chce odnotować w dowolnym miejscu moment operacji na obiekcie użytkownika. Należy w tym celu wstrzyknąć obiekt javax.enterprise.event.Event i wywołać na nim metodę fire(). Jej argumentem, będzie obiekt, który ma podlegać obserwacji.
@Named
@RequestScoped
public class UserBean{

    private List users;

    @Inject
    Event createUserEvent;


    @PostConstruct
    private void init(){
        
        users = new ArrayList();
        users.add(new User("Nowak"));
        users.add(new User("Lis"));
    }

    public void addUser(){

        User user = new User("Kowalski");
        users.add(user);
        createUserEvent.fire(user);
    }
}

Teraz wystarczy już w dowolnym miejscu aplikacji zaimplementować metodę nasłuchującą, będącą odbiorcą powyższego zdarzenia
@Named
@RequestScoped
public class UserObserver {

    public void onCreateUser(@Observes User user){

        System.out.println(">> User "+ user.getName() + " has been created");
    }
}

A co w przypadku gdybyśmy chcieli obserwować kilka różnych zdarzeń na obiekcie User ? Tutaj znowu z pomocą przychodzą kwalifikatory
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface CreateUser {
}
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface DeleteUser {
}

Zmodyfikowana klasa obserwująca
import javaspotlight.qualifiers.CreateUser;
import javaspotlight.qualifiers.DeleteUser;
import javax.enterprise.event.Observes;
import javax.faces.bean.RequestScoped;
import javax.inject.Named;

@Named
@RequestScoped
public class UserObserver {

    public void onCreateUser(@Observes @CreateUser User user){

        System.out.println(">> User "+ user.getName() + " has been created");
    }

    public void onDeleteUser(@Observes @DeleteUser User user){

        System.out.println(">> User "+ user.getName() + " has been deleted");
    }
}

Sposób inicjacji zdarzeń – oczywiście nie muszą znajdować się one w tej samej klasie
import java.util.ArrayList;
import java.util.List;
import javaspotlight.qualifiers.CreateUser;
import javaspotlight.qualifiers.DeleteUser;
import javax.annotation.PostConstruct;
import javax.enterprise.event.Event;
import javax.faces.bean.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@RequestScoped
public class UserBean{

    private List users;

    @Inject
    @CreateUser
    Event createUserEvent;

    @Inject 
    @DeleteUser
    Event deleteUserEvent;

    @PostConstruct
    private void init(){
        
        users = new ArrayList();
        users.add(new User("Nowak"));
        users.add(new User("Lis"));
    }

    public void addUser(){

        User user = new User("Kowalski");
        users.add(user);
        createUserEvent.fire(user);
    }

    public void removeUser(){

        for(User user : users){
     
            if("Nowak".equals(user.getName())){

                deleteUserEvent.fire(user);
                users.remove(user);
            }
        }
    }