package ee.sk.digidoc.factory; import ee.sk.digidoc.Base64Util; import ee.sk.digidoc.CertValue; import ee.sk.digidoc.DigiDocException; import ee.sk.digidoc.Signature; import ee.sk.digidoc.SignedDoc; import java.util.*; import java.security.*; import java.security.KeyStore.ProtectionParameter; import java.security.cert.*; import iaik.pkcs.pkcs11.*; import iaik.pkcs.pkcs11.objects.*; import ee.sk.utils.ConfigManager; import ee.sk.utils.ConvertUtils; import java.io.IOException; import java.io.ByteArrayInputStream; import java.util.Vector; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import org.apache.log4j.Logger; import ee.sk.digidoc.TokenKeyInfo; /** * PKCS#11 based signature implementation using * Sun pkcs11 API. This module was created in order to test * the signature method used by DSS - * https://joinup.ec.europa.eu/svn/sd-dss/trunk/apps/dss/ * @author Veiko Sinivee * @version 1.0 */ public class SunPkcs11SignatureFactory implements SignatureFactory { /** log4j logger */ private static Logger m_logger = Logger.getLogger(SunPkcs11SignatureFactory.class); private Provider m_provider; private KeyStore m_keyStore; public String m_alias = null; /** * initializes the implementation class */ public void init() throws DigiDocException { //DIGIDOC_SIGN_PKCS11_DRIVER m_provider = null; m_keyStore = null; } /** * initializes the implementation class */ public boolean init(String driver, String passwd, int nSlot) throws DigiDocException { //DIGIDOC_SIGN_PKCS11_DRIVER m_provider = null; m_keyStore = null; boolean bOk = initProvider(driver, passwd, nSlot); if(bOk) bOk = initKeystore(passwd); return bOk; } private boolean initProvider(String driver, String passwd, int nSlot) throws DigiDocException { try { String config = "name=OpenSC\n" + "library=" + driver + "\n" + "slotListIndex=" + nSlot; // + "disabledMechanisms = { CKM_SHA1_RSA_PKCS }\n"; if(m_logger.isDebugEnabled()) m_logger.debug("init driver with config:\n---\n" + config + "\n---\n"); byte[] bcfg = config.getBytes(); ByteArrayInputStream confStream = new ByteArrayInputStream(bcfg); sun.security.pkcs11.SunPKCS11 pkcs11 = new sun.security.pkcs11.SunPKCS11(confStream); m_provider = (Provider)pkcs11; Security.addProvider(m_provider); if(m_logger.isDebugEnabled()) m_logger.debug("Driver inited"); return true; } catch(Exception ex) { m_logger.error("Error init provider: " + ex); } return false; } private boolean initKeystore(String passwd) throws DigiDocException { try { String javaLibPath = System.getProperty("java.library.path"); if(m_logger.isDebugEnabled()) m_logger.debug("init keystore" + " in: " + javaLibPath + " provider: " + ((m_provider != null) ? "OK" : "NULL")); if(m_provider == null) throw new DigiDocException(DigiDocException.ERR_INIT_SIG_FAC, "Provider not initialized!", null); // load keystore m_keyStore = KeyStore.getInstance("PKCS11", m_provider); if(m_logger.isDebugEnabled()) m_logger.debug("Load keystore: " + m_provider.getName() + " - " + m_provider.getInfo()); m_keyStore.load(null, passwd.toCharArray()); // list keystore Enumeration eAliases = m_keyStore.aliases(); while(eAliases.hasMoreElements()) { String al = (String)eAliases.nextElement(); if(m_logger.isDebugEnabled()) m_logger.debug("Alias: " + al); if(m_alias == null) m_alias = al; } /*final String p = passwd; m_keyStore.load(new KeyStore.LoadStoreParameter() { public ProtectionParameter getProtectionParameter() { try { return new KeyStore.CallbackHandlerProtection(new CallbackHandler() { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { Callback c = callbacks[i]; if (c instanceof PasswordCallback) { ((PasswordCallback) c).setPassword(p.toCharArray()); return; } } throw new RuntimeException("No password callback"); } }); } catch (Exception e) { if (e instanceof sun.security.pkcs11.wrapper.PKCS11Exception) { if ("CKR_PIN_INCORRECT".equals(e.getMessage())) { throw new RuntimeException("Invalid PIN"); //throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Invalid PIN", e); } } } return null; } });*/ if(m_logger.isDebugEnabled()) m_logger.debug("Keystore loaded"); return true; } catch(Exception ex) { if (ex instanceof sun.security.pkcs11.wrapper.PKCS11Exception) { if ("CKR_PIN_INCORRECT".equals(ex.getMessage())) { DigiDocException.handleException(ex, DigiDocException.ERR_TOKEN_LOGIN); //throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Invalid PIN", e); } } m_logger.error("Error init keystore: " + ex); } return false; } /** * Reads all useable token keys * @return list of available token/key info * @throws DigiDocException */ public TokenKeyInfo[] getTokenKeys() throws DigiDocException { TokenKeyInfo[] keys = null; try { Enumeration eAliases = m_keyStore.aliases(); Vector vec = new Vector(); while(eAliases.hasMoreElements()) { String sAlias = (String)eAliases.nextElement(); X509Certificate cert = (X509Certificate)m_keyStore.getCertificate(sAlias); TokenKeyInfo tok = new TokenKeyInfo(0, 0, null, sAlias.getBytes(), ConvertUtils.getCommonName(cert.getSubjectDN().getName()), cert); vec.add(tok); } keys = new TokenKeyInfo[vec.size()]; for(int i = 0; i < vec.size(); i++) keys[i] = (TokenKeyInfo)vec.elementAt(i); } catch(Exception ex) { m_logger.error("Error init provider: " + ex); } return keys; } /** * Finds keys of specific type * @param bSign true if searching signature keys * @return array of key infos */ public TokenKeyInfo[] getTokensOfType(boolean bSign) { try { if(m_keyStore != null) { X509Certificate cert = (X509Certificate)m_keyStore.getCertificate(m_alias); TokenKeyInfo tok = new TokenKeyInfo(0, 0, null, m_alias.getBytes(), ConvertUtils.getCommonName(cert.getSubjectDN().getName()), cert); TokenKeyInfo[] at = new TokenKeyInfo[1]; at[0] = tok; return at; } } catch(Exception ex) { m_logger.error("Error init provider: " + ex); } return null; } /** * Method returns an array of strings representing the * list of available token names. * @return an array of available token names. * @throws DigiDocException if reading the token information fails. */ public String[] getAvailableTokenNames() throws DigiDocException { String [] as = new String[1]; as[0] = m_alias; return as; } /** * Method returns a digital signature. It finds the RSA private * key object from the active token and * then signs the given data with this key and RSA mechanism. * @param digest digest of the data to be signed. * @param token token index * @param pin users pin code * @param sig Signature object to provide info about desired signature method * @return an array of bytes containing digital signature. * @throws DigiDocException if signing the data fails. */ public byte[] sign(byte[] digest, int token, String pin, Signature sig) throws DigiDocException { try { ConfigManager cfg = ConfigManager.instance(); if(m_provider == null) initProvider(cfg.getProperty("DIGIDOC_SIGN_PKCS11_DRIVER"), pin, token); if(m_keyStore == null) initKeystore(pin); if(m_keyStore == null) { m_logger.error("Failed to load keystore"); throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Keystore load failed", null); } try { if(m_logger.isDebugEnabled() && digest != null) m_logger.debug("Signing: " + ConvertUtils.bin2hex(digest) + " len: " + digest.length + " with: " + m_alias + " on: " + m_provider.getName()); byte[] ddata = ConvertUtils.addDigestAsn1Prefix(digest); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, m_keyStore.getKey(m_alias, pin.toCharArray())); byte[] sdata = null; if(ddata != null) sdata = cipher.doFinal(ddata); if(m_logger.isDebugEnabled()) m_logger.debug("Signature: " + ConvertUtils.bin2hex(sdata) + " len: " + ((sdata != null) ? sdata.length : 0)); return sdata; } catch (NoSuchPaddingException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (IllegalBlockSizeException e) { throw new RuntimeException(e); } catch (BadPaddingException e) { // More likely bad password throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Invalid PIN", e); } } catch(Exception ex) { m_logger.error("Error init provider: " + ex); ex.printStackTrace(); } return null; } /** * Method returns a X.509 certificate object readed * from the active token and representing an * user public key certificate value. * @return X.509 certificate object. * @throws DigiDocException if getting X.509 public key certificate * fails or the requested certificate type X.509 is not available in * the default provider package */ public X509Certificate getCertificate(int token, String pin) throws DigiDocException { try { ConfigManager cfg = ConfigManager.instance(); if(m_provider == null) initProvider(cfg.getProperty("DIGIDOC_SIGN_PKCS11_DRIVER"), pin, token); if(m_keyStore == null) initKeystore(pin); if(m_keyStore == null) { m_logger.error("Failed to load keystore"); throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Keystore load failed", null); } if(m_logger.isDebugEnabled()) m_logger.debug("Get cert for: " + m_alias + " on: " + m_provider.getName()); X509Certificate cert = (X509Certificate)m_keyStore.getCertificate(m_alias); return cert; } catch(Exception ex) { m_logger.error("Error init provider: " + ex); ex.printStackTrace(); } return null; } /** * Method returns a X.509 certificate object readed * from the active token and representing an * user public key certificate value. * @return X.509 certificate object. * @throws DigiDocException if getting X.509 public key certificate * fails or the requested certificate type X.509 is not available in * the default provider package */ public X509Certificate getAuthCertificate(int token, String pin) throws DigiDocException { X509Certificate cert = null; cert = getCertificate(token, pin); //if(ConvertUtils.is return cert; } /** * Method closes the current session. * @throws DigiDocException if closing the session fails. */ public void closeSession() throws DigiDocException { try { m_provider = null; m_keyStore = null; m_alias = null; } catch(Exception ex) { m_logger.error("Error resetting pkcs11 factory: " + ex); } } /** * Resets the previous session * and other selected values */ public void reset() throws DigiDocException { try { if (m_provider != null) { try { Security.removeProvider(m_provider.getName()); } catch (Exception ex) { ex.printStackTrace(); } } m_provider = null; m_keyStore = null; m_alias = null; } catch(Exception ex) { m_logger.error("Error resetting pkcs11 factory: " + ex); } } /** * Method decrypts the data with the RSA private key * corresponding to this certificate (which was used * to encrypt it). Decryption will be done on the card. * This operation closes the possibly opened previous * session with signature token and opens a new one with * authentication tokne if necessary * @param data data to be decrypted. * @param token index of authentication token * @param pin PIN code * @return decrypted data. * @throws DigiDocException for all decryption errors */ public byte[] decrypt(byte[] data, int token, String pin) throws DigiDocException { try { ConfigManager cfg = ConfigManager.instance(); if(m_provider == null) initProvider(cfg.getProperty("DIGIDOC_SIGN_PKCS11_DRIVER"), pin, token); if(m_keyStore == null) initKeystore(pin); if(m_keyStore == null) { m_logger.error("Failed to load keystore"); throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Keystore load failed", null); } try { if(m_logger.isDebugEnabled()) m_logger.debug("Decrypting: " + ConvertUtils.bin2hex(data) + " len: " + data.length + " with: " + m_alias + " on: " + m_provider.getName()); Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, m_keyStore.getKey(m_alias, pin.toCharArray())); byte[] ddata = cipher.doFinal(data); if(m_logger.isDebugEnabled()) m_logger.debug("Decrypted: " + ConvertUtils.bin2hex(ddata) + " len: " + ddata.length); return ddata; } catch (NoSuchPaddingException e) { throw new RuntimeException(e); } catch (InvalidKeyException e) { throw new RuntimeException(e); } catch (IllegalBlockSizeException e) { throw new RuntimeException(e); } catch (BadPaddingException e) { // More likely bad password throw new DigiDocException(DigiDocException.ERR_TOKEN_LOGIN, "Invalid PIN", e); } } catch(Exception ex) { m_logger.error("Error init provider: " + ex); ex.printStackTrace(); } return null; } /** * Returns signature factory type identifier * @return factory type identifier */ public String getType() { return SIGFAC_TYPE_PKCS11_SUN; } }