Java tutorial
/* * Copyright (c) 1990-2012 kopiLeft Development SARL, Bizerte, Tunisia * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id$ */ package org.kopi.ebics.client; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPublicKey; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.xml.crypto.dsig.SignedInfo; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.kopi.ebics.certificate.CertificateManager; import org.kopi.ebics.exception.EbicsException; import org.kopi.ebics.interfaces.EbicsPartner; import org.kopi.ebics.interfaces.EbicsUser; import org.kopi.ebics.interfaces.PasswordCallback; import org.kopi.ebics.interfaces.Savable; import org.kopi.ebics.utils.Utils; import org.kopi.ebics.xml.UserSignature; /** * Simple implementation of an EBICS user. * This object is not serializable, but it should be persisted every time it needs to be saved. * Persistence is achieved via <code>save(ObjectOutputStream)</code> and the matching constructor. * * @author Hachani * */ public class User implements EbicsUser, Savable { /** * First time constructor. Use this constructor, * if you want to prepare the first communication with the bank. For further communications you should recover persisted objects. * All required signature keys will be generated now. * * @param partner customer in whose name we operate. * @param userId UserId as obtained from the bank. * @param name the user name, * @param email the user email * @param country the user country * @param organisation the user organization or company * @param passwordCallback a callback-handler that supplies us with the password. * This parameter can be null, in this case no password is used. * @throws IOException * @throws GeneralSecurityException */ public User(EbicsPartner partner, String userId, String name, String email, String country, String organization, PasswordCallback passwordCallback) throws GeneralSecurityException, IOException { this.partner = partner; this.userId = userId; this.name = name; this.dn = makeDN(name, email, country, organization); this.passwordCallback = passwordCallback; createUserCertificates(); needSave = true; } /** * Reconstructs a persisted EBICS user. * * @param partner the customer in whose name we operate. * @param ois the object stream * @param passwordCallback a callback-handler that supplies us with the password. * @throws IOException * @throws GeneralSecurityException if the supplies password is wrong. * @throws ClassNotFoundException */ public User(EbicsPartner partner, ObjectInputStream ois, PasswordCallback passwordCallback) throws IOException, GeneralSecurityException, ClassNotFoundException { this.partner = partner; this.passwordCallback = passwordCallback; this.userId = ois.readUTF(); this.name = ois.readUTF(); this.dn = ois.readUTF(); this.isInitialized = ois.readBoolean(); this.isInitializedHIA = ois.readBoolean(); this.a005Certificate = (X509Certificate) ois.readObject(); this.e002Certificate = (X509Certificate) ois.readObject(); this.x002Certificate = (X509Certificate) ois.readObject(); this.a005PrivateKey = (PrivateKey) ois.readObject(); this.e002PrivateKey = (PrivateKey) ois.readObject(); this.x002PrivateKey = (PrivateKey) ois.readObject(); ois.close(); } /** * Reconstructs a an EBICS user by loading its certificate * from a given key store. * @param partner the customer in whose name we operate. * @param userId UserID as obtained from the bank. * @param keystorePath the key store path * @param passwordCallback a callback-handler that supplies us with the password. * @throws GeneralSecurityException * @throws IOException */ public User(EbicsPartner partner, String userId, String name, String keystorePath, PasswordCallback passwordCallback) throws GeneralSecurityException, IOException { this.partner = partner; this.userId = userId; this.name = name; this.passwordCallback = passwordCallback; loadCertificates(keystorePath); this.dn = a005Certificate.getSubjectDN().getName(); needSave = true; } /** * Creates new certificates for a user. * @throws GeneralSecurityException * @throws IOException */ private void createUserCertificates() throws GeneralSecurityException, IOException { manager = new CertificateManager(this); manager.create(); } /** * Saves the user certificates in a given path * @param path the certificates path * @throws GeneralSecurityException * @throws IOException */ public void saveUserCertificates(String path) throws GeneralSecurityException, IOException { if (manager == null) { throw new GeneralSecurityException("Cannot save user certificates"); } manager.save(path, passwordCallback); } /** * Loads the user certificates from a key store * @param keyStorePath the key store path * @throws GeneralSecurityException * @throws IOException */ private void loadCertificates(String keyStorePath) throws GeneralSecurityException, IOException { manager = new CertificateManager(this); manager.load(keyStorePath, passwordCallback); } @Override public void save(ObjectOutputStream oos) throws IOException { oos.writeUTF(userId); oos.writeUTF(name); oos.writeUTF(dn); oos.writeBoolean(isInitialized); oos.writeBoolean(isInitializedHIA); oos.writeObject(a005Certificate); oos.writeObject(e002Certificate); oos.writeObject(x002Certificate); oos.writeObject(a005PrivateKey); oos.writeObject(e002PrivateKey); oos.writeObject(x002PrivateKey); oos.flush(); oos.close(); needSave = false; } /** * Has the users signature key been sent to the bank? * @return True if the users signature key been sent to the bank */ public boolean isInitialized() { return isInitialized; } /** * The users signature key has been sent to the bank. * @param isInitialized transfer successful? */ public void setInitialized(boolean isInitialized) { this.isInitialized = isInitialized; needSave = true; } /** * Have the users authentication and encryption keys been sent to the bank? * @return True if the users authentication and encryption keys been sent to the bank. */ public boolean isInitializedHIA() { return isInitializedHIA; } /** * The users authentication and encryption keys have been sent to the bank. * @param isInitializedHIA transfer successful? */ public void setInitializedHIA(boolean isInitializedHIA) { this.isInitializedHIA = isInitializedHIA; needSave = true; } /** * Generates new keys for this user and sends them to the bank. * @param keymgmt the key management instance with the ebics session. * @param passwordCallback the password-callback for the new keys. * @throws EbicsException Exception during server request * @throws IOException Exception during server request */ public void updateKeys(KeyManagement keymgmt, PasswordCallback passwordCallback) throws EbicsException, IOException { needSave = true; } /** * EBICS Specification 2.4.2 - 7.1 Process description: * * <p>In particular, so-called white-space characters? such as spaces, tabs, carriage * returns and line feeds (CR/LF?) are not permitted. * * <p> All white-space characters should be removed from entry buffer {@code buf}. * * @param buf the given byte buffer * @param offset the offset * @param length the length * @return The byte buffer portion corresponding to the given length and offset */ public static byte[] removeOSSpecificChars(byte[] buf) { ByteArrayOutputStream output; output = new ByteArrayOutputStream(); for (int i = 0; i < buf.length; i++) { switch (buf[i]) { case '\r': case '\n': case 0x1A: // CTRL-Z / EOF // ignore this character break; default: output.write(buf[i]); } } return output.toByteArray(); } /** * Makes the Distinguished Names for the user certificates. * @param name the user name * @param email the user email * @param country the user country * @param organization the user organization * @return */ private String makeDN(String name, String email, String country, String organization) { StringBuffer buffer; buffer = new StringBuffer(); buffer.append("CN=" + name); if (country != null) { buffer.append(", " + "C=" + country.toUpperCase()); } if (organization != null) { buffer.append(", " + "O=" + organization); } if (email != null) { buffer.append(", " + "E=" + email); } return buffer.toString(); } /** * Did any persistable attribute change since last load/save operation. * @return True if the object needs to be saved. */ public boolean needsSave() { return needSave; } @Override public byte[] getA005Certificate() throws EbicsException { try { return a005Certificate.getEncoded(); } catch (CertificateEncodingException e) { throw new EbicsException(e.getMessage()); } } @Override public byte[] getE002Certificate() throws EbicsException { try { return e002Certificate.getEncoded(); } catch (CertificateEncodingException e) { throw new EbicsException(e.getMessage()); } } @Override public byte[] getX002Certificate() throws EbicsException { try { return x002Certificate.getEncoded(); } catch (CertificateEncodingException e) { throw new EbicsException(e.getMessage()); } } @Override public void setA005Certificate(X509Certificate a005Certificate) { this.a005Certificate = a005Certificate; needSave = true; } @Override public void setE002Certificate(X509Certificate e002Certificate) { this.e002Certificate = e002Certificate; needSave = true; } @Override public void setX002Certificate(X509Certificate x002Certificate) { this.x002Certificate = x002Certificate; needSave = true; } @Override public RSAPublicKey getA005PublicKey() { return (RSAPublicKey) a005Certificate.getPublicKey(); } @Override public RSAPublicKey getE002PublicKey() { return (RSAPublicKey) e002Certificate.getPublicKey(); } @Override public RSAPublicKey getX002PublicKey() { return (RSAPublicKey) x002Certificate.getPublicKey(); } @Override public void setA005PrivateKey(PrivateKey a005PrivateKey) { this.a005PrivateKey = a005PrivateKey; needSave = true; } @Override public void setX002PrivateKey(PrivateKey x002PrivateKey) { this.x002PrivateKey = x002PrivateKey; needSave = true; } @Override public void setE002PrivateKey(PrivateKey e002PrivateKey) { this.e002PrivateKey = e002PrivateKey; needSave = true; } @Override public String getSecurityMedium() { return "0000"; } @Override public EbicsPartner getPartner() { return partner; } @Override public String getUserId() { return userId; } @Override public String getName() { return name; } @Override public String getDN() { return dn; } @Override public PasswordCallback getPasswordCallback() { return passwordCallback; } @Override public String getSaveName() { return userId + ".cer"; } /** * EBICS Specification 2.4.2 - 11.1.1 Process: * * <p>Identification and authentication signatures are based on the RSA signature process. * The following parameters determine the identification and authentication signature process: * * <ol> * <li> Length of the (secret) RSA key * <li> Hash algorithm * <li> Padding process * <li> Canonisation process. * </ol> * * <p>For the identification and authentication process, EBICS defines the process X002? with * the following parameters: * <ol> * <li>Key length in Kbit >=1Kbit (1024 bit) and lesser than 16Kbit</li> * <li>Hash algorithm SHA-256</li> * <li>Padding process: PKCS#1</li> * <li>Canonisation process: http://www.w3.org/TR/2001/REC-xml-c14n-20010315 * </ol> * * <p>From EBICS 2.4 on, the customer system must use the hash value of the public bank key * X002 in a request. * * <p>Notes: * <ol> * <li> The key length is defined else where. * <li> The padding is performed by the {@link Signature} class. * <li> The digest is already canonized in the {@link SignedInfo#sign(byte[]) sign(byte[])} * </ol> */ @Override public byte[] authenticate(byte[] digest) throws GeneralSecurityException { Signature signature; signature = Signature.getInstance("SHA256WithRSA", BouncyCastleProvider.PROVIDER_NAME); signature.initSign(x002PrivateKey); signature.update(digest); return signature.sign(); } /** * EBICS Specification 2.4.2 - 14.1 Version A005/A006 of the electronic signature: * * <p>For the signature processes A005 an interval of 1536 bit (minimum) * and 4096 bit (maximum) is defined for the key length. * * <p>The digital signature mechanisms A005 is both based on the industry standard * [PKCS1] using the hash algorithm SHA-256. They are both signature mechanisms without * message recovery. * * <p>A hash algorithm maps bit sequences of arbitrary length (input bit sequences) to byte * sequences of a fixed length, determined by the Hash algorithm. The result of the execution of * a Hash algorithm to a bit sequence is defined as hash value. * * <p>The hash algorithm SHA-256 is specified in [FIPS H2]. SHA-256 maps input bit sequences of * arbitrary length to byte sequences of 32 byte length. The padding of input bit sequences to a * length being a multiple of 64 byte is part of the hash algorithm. The padding even is applied if * the input bit sequence already has a length that is a multiple of 64 byte. * * <p>SHA-256 processes the input bit sequences in blocks of 64 byte length. * The hash value of a bit sequence x under the hash algorithm SHA-256 is referred to as * follows: SHA-256(x). * * <p>The digital signature mechanism A005 is identical to EMSA-PKCS1-v1_5 using the hash * algorithm SHA-256. The byte length H of the hash value is 32. * * According [PKCS1] (using the method EMSA-PKCS1-v1_5) the following steps shall be * performed for the computation of a signature for message M with bit length m. * <ol> * <li> The hash value HASH(M) of the byte length H shall be computed. In the case of A005 * SHA-256(M) with a length of 32 bytes.</li> * <li> The DSI for the signature algorithm shall be generated.</li> * <li> A signature shall be computed using the DSI with the standard algorithm for the * signature generation described in section 14.1.3.1 of the EBICS specification (V 2.4.2). * </ol> * * <p>The {@link Signature} is a digital signature scheme with * appendix (SSA) combining the RSA algorithm with the EMSA-PKCS1-v1_5 encoding * method. * * <p> The {@code digest} will be signed with the RSA user signature key using the * {@link Signature} that will be instantiated with the <b>SHA-256</b> * algorithm. This signature is then put in a {@link UserSignature} XML object that * will be sent to the EBICS server. */ @Override public byte[] sign(byte[] digest) throws IOException, GeneralSecurityException { Signature signature = Signature.getInstance("SHA256WithRSA", BouncyCastleProvider.PROVIDER_NAME); signature.initSign(a005PrivateKey); signature.update(removeOSSpecificChars(digest)); return signature.sign(); } /** * EBICS IG CFONB VF 2.1.4 2012 02 24 - 2.1.3.2 Calcul de la signature: * * <p>Il convient dutiliser PKCS1 V1.5 pour chiffrer la cl de chiffrement. * * <p>EBICS Specification 2.4.2 - 15.2 Workflows at the recipients end: * * <p><b>Decryption of the DES key</b> * <p>The leading 256 null bits of the EDEK are removed and the remaining 768 bits are decrypted * with the recipients secret key of the RSA key system. PDEK is then present. The secret DES * key DEK is obtained from the lowest-value 128 bits of PDEK, this is split into the individual * keys DEK<SUB>left</SUB> and DEK<SUB>right</SUB>. */ @Override public byte[] decrypt(byte[] encryptedData, byte[] transactionKey) throws EbicsException, GeneralSecurityException, IOException { Cipher cipher; int blockSize; ByteArrayOutputStream outputStream; cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, e002PrivateKey); blockSize = cipher.getBlockSize(); outputStream = new ByteArrayOutputStream(); for (int j = 0; j * blockSize < transactionKey.length; j++) { outputStream.write(cipher.doFinal(transactionKey, j * blockSize, blockSize)); } return decryptData(encryptedData, outputStream.toByteArray()); } /** * Decrypts the <code>encryptedData</code> using the decoded transaction key. * * <p>EBICS Specification 2.4.2 - 15.2 Workflows at the recipients end: * * <p><b>Decryption of the message</b> * <p>The encrypted original message is decrypted in CBC mode in accordance with the 2-key * triple DES process via the secret DES key (comprising DEK<SUB>left</SUB> and DEK<SUP>right<SUB>). * In doing this, the following initialization value ICV is again used. * * <p><b>Removal of the padding information</b> * <p>The method Padding with Octets? according to ANSI X9.23 is used to remove the padding * information from the decrypted message. The original message is then available in decrypted * form. * * @param input The encrypted data * @param key The secret key. * @return The decrypted data sent from the EBICS bank. * @throws GeneralSecurityException * @throws IOException */ private byte[] decryptData(byte[] input, byte[] key) throws EbicsException { return Utils.decrypt(input, new SecretKeySpec(key, "EAS")); } // -------------------------------------------------------------------- // DATA MEMBERS // -------------------------------------------------------------------- private EbicsPartner partner; private String userId; private String name; private String dn; private boolean isInitializedHIA; private boolean isInitialized; private PasswordCallback passwordCallback; private transient boolean needSave; private CertificateManager manager; private PrivateKey a005PrivateKey; private PrivateKey e002PrivateKey; private PrivateKey x002PrivateKey; private X509Certificate a005Certificate; private X509Certificate e002Certificate; private X509Certificate x002Certificate; }