package de.unisiegen.gtitool.ui.exchange; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.swing.SwingUtilities; import de.unisiegen.gtitool.core.storage.Element; import de.unisiegen.gtitool.ui.exchange.encryption.AESSecretKeyImpl; import de.unisiegen.gtitool.ui.exchange.encryption.RSAPrivateKeyImpl; import de.unisiegen.gtitool.ui.exchange.encryption.RSAPublicKeyImpl; import de.unisiegen.gtitool.ui.storage.Storage; /** * The {@link Connection} {@link Thread}. * * @author Christian Fehler * @version $Id$ */ public abstract class Connection extends Thread { /** * The RSA key length. */ private static final int RSA_KEY_LENGTH = 1024; /** * The RSA algorithmus. */ private static final String RSA = "RSA"; //$NON-NLS-1$ /** * The AES key length. */ private static final int AES_KEY_LENGTH = 128; /** * The AES algorithmus. */ private static final String AES = "AES"; //$NON-NLS-1$ /** * The encoding. */ private static final String ENCODING = "UTF-8"; //$NON-NLS-1$ /** * The {@link Network}. */ protected Network network; /** * The {@link Exchange}. */ private Exchange exchange; /** * The {@link InputStream}. */ private InputStream input; /** * The {@link OutputStream}. */ private OutputStream output; /** * The {@link Socket}. */ private Socket socket = null; /** * The {@link ServerSocket}. */ private ServerSocket serverSocket = null; /** * The {@link RSAPublicKey}. */ private RSAPublicKey rsaPublicKey = null; /** * The {@link RSAPrivateKey}. */ private RSAPrivateKey rsaPrivateKey = null; /** * The {@link SecretKey}. */ private SecretKey aesSecretKey = null; /** * Allocates a new {@link Connection}. * * @param network The {@link Network}. */ public Connection ( Network network ) { super ( "Connection-Server" ); //$NON-NLS-1$ this.network = network; } /** * Allocates a new {@link Connection}. * * @param network The {@link Network}. * @param exchange The {@link Exchange}. */ public Connection ( Network network, Exchange exchange ) { super ( "Connection-Client" ); //$NON-NLS-1$ this.network = network; // Exchange this.exchange = exchange; } /** * Closes the {@link Connection}. */ protected final void close () { try { if ( this.serverSocket != null ) { this.serverSocket.close (); this.serverSocket = null; } if ( this.socket != null ) { this.socket.close (); this.socket = null; } if ( this.input != null ) { this.input.close (); this.input = null; } if ( this.output != null ) { this.output.close (); this.output = null; } } catch ( IOException exc ) { exc.printStackTrace (); } } /** * Create the streams. */ protected final void createStreams () { try { this.input = this.socket.getInputStream (); this.output = this.socket.getOutputStream (); } catch ( IOException exc ) { exc.printStackTrace (); close (); } } /** * Decryptes a byte array with the {@link PrivateKey}. * * @param cipherText The ciphertext. * @return The decrypted ciphertext. * @throws NoSuchPaddingException Padding wrong. * @throws NoSuchAlgorithmException Algorithm not found. * @throws InvalidKeyException Key wrong. * @throws BadPaddingException Padding wrong. * @throws IllegalBlockSizeException Block size is wrong. */ private final byte [] decryptAES ( byte [] cipherText ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec secretKeySpec = new SecretKeySpec ( this.aesSecretKey .getEncoded (), AES ); Cipher cipher = Cipher.getInstance ( AES ); cipher.init ( Cipher.DECRYPT_MODE, secretKeySpec ); return cipher.doFinal ( cipherText ); } /** * Decryptes a byte array with the {@link PrivateKey}. * * @param cipherText The ciphertext. * @return The decrypted ciphertext. * @throws NoSuchPaddingException Padding wrong. * @throws NoSuchAlgorithmException Algorithm not found. * @throws InvalidKeyException Key wrong. * @throws BadPaddingException Padding wrong. * @throws IllegalBlockSizeException Block size is wrong. */ private final byte [] decryptRSA ( byte [] cipherText ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance ( RSA ); cipher.init ( Cipher.DECRYPT_MODE, this.rsaPrivateKey ); return cipher.doFinal ( cipherText ); } /** * Encryptes a byte array with the {@link PublicKey}. * * @param plainText The plaintext. * @return The encrypted plaintext. * @throws NoSuchPaddingException Padding wrong. * @throws NoSuchAlgorithmException Algorithm not found. * @throws InvalidKeyException Key wrong. * @throws BadPaddingException Padding wrong. * @throws IllegalBlockSizeException Block size is wrong. */ private final byte [] encryptAES ( byte [] plainText ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { SecretKeySpec secretKeySpec = new SecretKeySpec ( this.aesSecretKey .getEncoded (), AES ); Cipher cipher = Cipher.getInstance ( AES ); cipher.init ( Cipher.ENCRYPT_MODE, secretKeySpec ); return cipher.doFinal ( plainText ); } /** * Encryptes a byte array with the {@link PublicKey}. * * @param plainText The plaintext. * @return The encrypted plaintext. * @throws NoSuchPaddingException Padding wrong. * @throws NoSuchAlgorithmException Algorithm not found. * @throws InvalidKeyException Key wrong. * @throws BadPaddingException Padding wrong. * @throws IllegalBlockSizeException Block size is wrong. */ private final byte [] encryptRSA ( byte [] plainText ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance ( RSA ); cipher.init ( Cipher.ENCRYPT_MODE, this.rsaPublicKey ); return cipher.doFinal ( plainText ); } /** * Let the listeners know that the {@link Exchange} is finished. */ protected final void fireExchangeFinished () { SwingUtilities.invokeLater ( new Runnable () { public void run () { Connection.this.network.fireExchangeFinished (); } } ); } /** * Let the listeners know that a {@link Exchange} was received. * * @param newExchange The received {@link Exchange}. */ protected final void fireExchangeReceived ( final Exchange newExchange ) { SwingUtilities.invokeLater ( new Runnable () { public void run () { Connection.this.network.fireExchangeReceived ( newExchange ); } } ); } /** * Let the listeners know that the {@link Network} is connected. */ protected final void fireNetworkConnected () { SwingUtilities.invokeLater ( new Runnable () { public void run () { Connection.this.network.fireNetworkConnected (); } } ); } /** * Returns the byte value. * * @param intValue The input int. * @return The byte value. */ private final byte [] getByteValue ( int intValue ) { byte [] bytes = new byte [ 4 ]; bytes [ 0 ] = ( byte ) ( ( intValue >> 0 ) & 255 ); bytes [ 1 ] = ( byte ) ( ( intValue >> 8 ) & 255 ); bytes [ 2 ] = ( byte ) ( ( intValue >> 16 ) & 255 ); bytes [ 3 ] = ( byte ) ( ( intValue >> 24 ) & 255 ); return bytes; } /** * Returns the int value. * * @param bytes The input bytes. * @return The byte value. */ private final int getIntValue ( byte [] bytes ) { if ( bytes.length != 4 ) { throw new IllegalArgumentException ( "must have length 4" ); //$NON-NLS-1$ } return ( bytes [ 0 ] & 255 ) | ( ( bytes [ 1 ] & 255 ) << 8 ) | ( ( bytes [ 2 ] & 255 ) << 16 ) | ( ( bytes [ 3 ] & 255 ) << 24 ); } /** * Returns the {@link Network}. * * @return The {@link Network}. * @see #network */ protected final Network getNetwork () { return this.network; } /** * Returns the {@link ServerSocket}. * * @return The {@link ServerSocket}. * @see #serverSocket */ protected final ServerSocket getServerSocket () { return this.serverSocket; } /** * Returns the {@link Socket}. * * @return The {@link Socket}. * @see #socket */ protected final Socket getSocket () { return this.socket; } /** * Receives the {@link Exchange}. * * @return The {@link Exchange}. */ protected final Exchange receiveExchange () { try { // Description length byte [] descriptionLengthBytes = new byte [ 4 ]; this.input.read ( descriptionLengthBytes, 0, 4 ); int descriptionLength = getIntValue ( descriptionLengthBytes ); // Description byte [] descriptionBytes = new byte [ descriptionLength ]; this.input.read ( descriptionBytes, 0, descriptionLength ); descriptionBytes = decryptAES ( descriptionBytes ); String description = new String ( descriptionBytes ); // Element length byte [] elementLengthBytes = new byte [ 4 ]; this.input.read ( elementLengthBytes, 0, 4 ); int elementLength = getIntValue ( elementLengthBytes ); // Element byte [] elementBytes = new byte [ elementLength ]; this.input.read ( elementBytes, 0, elementLength ); elementBytes = decryptAES ( elementBytes ); Element element = Storage.getInstance ().load ( new String ( elementBytes ) ); return new Exchange ( element, description ); } catch ( Exception exc ) { exc.printStackTrace (); close (); return null; } } /** * Receives the RSA {@link PublicKey}. */ protected final void receivePublicKeyRSA () { try { byte [] publicKeyLengthBytes = new byte [ 4 ]; this.input.read ( publicKeyLengthBytes, 0, 4 ); int publicKeyLength = getIntValue ( publicKeyLengthBytes ); byte [] publicKeyBytes = new byte [ publicKeyLength ]; Connection.this.input.read ( publicKeyBytes, 0, publicKeyLength ); this.rsaPublicKey = new RSAPublicKeyImpl ( publicKeyBytes ); } catch ( Exception exc ) { exc.printStackTrace (); close (); } } /** * Receives the AES {@link SecretKey}. */ protected final void receiveSecretKeyAES () { try { byte [] secretKeyLengthBytes = new byte [ 4 ]; this.input.read ( secretKeyLengthBytes, 0, 4 ); int secretKeyLength = getIntValue ( secretKeyLengthBytes ); byte [] secretKeyBytes = new byte [ secretKeyLength ]; Connection.this.input.read ( secretKeyBytes, 0, secretKeyLength ); secretKeyBytes = decryptRSA ( secretKeyBytes ); this.aesSecretKey = new AESSecretKeyImpl ( secretKeyBytes ); } catch ( Exception exc ) { exc.printStackTrace (); close (); } } /** * Sends the the {@link Exchange} to the {@link OutputStream}. */ protected final void send () { if ( this.aesSecretKey == null ) { throw new RuntimeException ( "secret aes key not received" ); //$NON-NLS-1$ } try { // Description String description = this.exchange.getDescription (); byte [] descriptionBytes = description.getBytes ( ENCODING ); descriptionBytes = encryptAES ( descriptionBytes ); writeBytes ( descriptionBytes ); // Element String element = this.exchange.getElement ().getStoreString (); byte [] elementBytes = element.getBytes ( ENCODING ); elementBytes = encryptAES ( elementBytes ); writeBytes ( elementBytes ); // Flush this.output.flush (); } catch ( Exception exc ) { exc.printStackTrace (); close (); } } /** * Sends the given RSA {@link PublicKey} to the {@link OutputStream}. */ protected final void sendPublicKeyRSA () { try { // Create keys KeyPair keyPair = null; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance ( RSA ); keyPairGenerator.initialize ( RSA_KEY_LENGTH ); keyPair = keyPairGenerator.genKeyPair (); // Save the private key RSAPrivateKey privateKey = ( RSAPrivateKey ) keyPair.getPrivate (); this.rsaPrivateKey = new RSAPrivateKeyImpl ( privateKey.getModulus (), privateKey.getPrivateExponent () ); } catch ( NoSuchAlgorithmException exc ) { close (); return; } // PublicKey RSAPublicKey publicKey = ( RSAPublicKey ) keyPair.getPublic (); byte [] publicKeyBytes = new RSAPublicKeyImpl ( publicKey.getModulus (), publicKey.getPublicExponent () ).getEncoded (); writeBytes ( publicKeyBytes ); // Flush this.output.flush (); } catch ( Exception exc ) { exc.printStackTrace (); close (); } } /** * Sends the given AES {@link SecretKey} to the {@link OutputStream}. */ protected final void sendSecretKeyAES () { try { // Create keys try { KeyGenerator keyGenerator = KeyGenerator.getInstance ( AES ); keyGenerator.init ( AES_KEY_LENGTH ); this.aesSecretKey = new AESSecretKeyImpl ( keyGenerator.generateKey () .getEncoded () ); } catch ( NoSuchAlgorithmException exc ) { close (); return; } // SecretKey byte [] secretKeyBytes = this.aesSecretKey.getEncoded (); secretKeyBytes = encryptRSA ( secretKeyBytes ); writeBytes ( secretKeyBytes ); // Flush this.output.flush (); } catch ( Exception exc ) { exc.printStackTrace (); close (); } } /** * Sets the {@link ServerSocket}. * * @param serverSocket The {@link ServerSocket} to set. * @see #serverSocket */ protected final void setServerSocket ( ServerSocket serverSocket ) { this.serverSocket = serverSocket; } /** * Sets the {@link Socket}. * * @param socket The {@link Socket} to set. * @see #socket */ protected final void setSocket ( Socket socket ) { this.socket = socket; } /** * Writes the given bytes and the length of the bytes. * * @param bytes The bytes to write. * @throws IOException If an I/O error occurs. */ private final void writeBytes ( byte [] bytes ) throws IOException { this.output.write ( getByteValue ( bytes.length ), 0, 4 ); this.output.write ( bytes, 0, bytes.length ); } }