SATSAMIDlet.java

/**
 * Copyright (c) 2012 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia 
Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.satsa;

import com.nokia.mid.ui.orientation.Orientation;
import com.nokia.mid.ui.orientation.OrientationListener;
import java.security.GeneralSecurityException;
import javax.crypto.BadPaddingException;
import javax.crypto.ShortBufferException;
import javax.microedition.lcdui.Display;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

// Main SATSA MIDlet class
// It controls the state of the user interface
// and handles the encryption and decryption process
// The class implements OrientationListener to detect a change in the orientation of the device's display */
public class SATSAMIDlet extends MIDlet implements OrientationListener{

	private Display display;
	private MessageStore messageStore;
	private ListScreen listScreen;
	private EncryptScreen encryptScreen;
	private PasswordScreen passwordScreen;
	private DecryptScreen decryptScreen;
	private InfoScreen infoScreen;
	private NewScreen newScreen;
	private Codec codec;

	public SATSAMIDlet() {
	}

	protected void destroyApp(boolean unconditional)
			throws MIDletStateChangeException {
		exitMIDlet();
	}

	protected void pauseApp() {
	}

	protected void startApp()
			throws MIDletStateChangeException {

		/** Registers the orientation listener. Applications that need information about events related to display orientation changes need to register with Orientation to get notifications of the events.*/
		Orientation.addOrientationListener(this);

		if (display == null) {
			initMIDlet();
		}
	}

	// Initialize all the internal fields
	private void initMIDlet() {
		// Init the user interface
		display = Display.getDisplay(this);
		listScreen = new ListScreen(this);
		encryptScreen = new EncryptScreen(this);
		passwordScreen = new PasswordScreen(this);
		decryptScreen = new DecryptScreen(this);
		infoScreen = new InfoScreen();
		newScreen = new NewScreen(this);

		// Initialize the Cryptographic Codec and the message store
		codec = new Codec();
		messageStore = new MessageStore(this);

		// Startup the UI
		showMessageList();
	}

	// Shows the list of existing messages
	void showMessageList() {
		messageStore.fillList(listScreen);
		display.setCurrent(listScreen);
	}

	// Shows an encrypted message in HEX format
	void showEncryptedMessage(int index) {
		encryptScreen.setIndex(index);
		encryptScreen.setMessage(codec.byteToHex(messageStore.getMessage(index + 1)));
		display.setCurrent(encryptScreen);
	}

	// Displays the password screen
	void showPasswordScreen(int index) {
		passwordScreen.setIndex(index);
		display.setCurrent(passwordScreen);
	}

	// Shows a decrypted messages
	void showDecryptedMessage(int index, String password) {
		// 128bit (16 characters) key is needed for AES
		// just fill with blanks if too short
		while (password.length() < 16) {
			password = password.concat(" ");
		}
		decryptScreen.setIndex(index);
		byte message[] = messageStore.getMessage(index + 1);
		// Do the decryption and validation in a separate thread
		new Thread(new CryptoRunnable(message,
				password.getBytes(),
				false)).start();
	}

	// Display an error message
	void showError(String messageString) {
		infoScreen.showError(messageString, Display.getDisplay(this));
	}

	// Delete a message
	void deleteMessage(int index) {
		messageStore.deleteMessage(index + 1);
		messageStore.fillList(listScreen);
	}

	// Shows the new message
	void showNewMessage() {
		newScreen.createForm();
		display.setCurrent(newScreen);
	}

	// adds a new message encrypting it an storing in the record store
	void addNewMessage(String message, String password) {
		// 128bit (16 characters) key is needed for AES
		// just fill with blanks if too short
		while (password.length() < 16) {
			password = password.concat(" ");
		}
		// Do the encryption in a separate thread
		new Thread(new CryptoRunnable(message.getBytes(),
				password.getBytes(),
				true)).start();
	}

	void exitMIDlet() {
		if (messageStore != null) {
			messageStore.close();
		}
		notifyDestroyed();
	}

	// Internal class to handle the decryption and encryption
	// process in a separate thread as recommended in SATSA-CRYPTO
	private class CryptoRunnable implements Runnable {

		private byte[] message, password;
		private boolean encryption;

		CryptoRunnable(byte[] message, byte[] password, boolean encryption) {
			this.message = message;
			this.password = password;
			this.encryption = encryption;
		}

		public void run() {
			if (encryption) {
				try {
					// Encrypt the message
					byte[] encryptedMessage = codec.encrypt(password, message);
					// Calculate the digest of the encrypted message
					byte[] digest = codec.digest(encryptedMessage);
					// Compose the overall message
					byte[] messageBytes =
							new byte[encryptedMessage.length + digest.length];
					System.arraycopy(digest, 0, messageBytes, 0, digest.length);
					System.arraycopy(encryptedMessage,
							0,
							messageBytes,
							digest.length,
							encryptedMessage.length);
					// Store the encoded message
					messageStore.addMessage(messageBytes);
				} catch (GeneralSecurityException gse) {
					// Use of generic Exception type since mutiple
					// exceptions may be thrown at this point
					showError("General Security Exception while encrypting: "
							+ gse.toString());
				}
				showMessageList();
			} else {
				try {
					// Get the cipher text and the digest
					// SHA-1 digest is 160 bits long
					byte[] digest = new byte[20];
					byte[] cipherText = new byte[message.length - digest.length];
					// Decompose the message
					System.arraycopy(message, 0, digest, 0, digest.length);
					System.arraycopy(message,
							digest.length,
							cipherText,
							0,
							cipherText.length);

					// Verify the cipher's text digest
					if (codec.isDigestValid(cipherText, digest)) {
						// If digest is ok, let's decrypt
						byte[] plainText = codec.decrypt(password, cipherText);

						// Display the message on screen
						decryptScreen.setMessage(new String(plainText));
						display.setCurrent(decryptScreen);
					} else {
						showError("Digest of message is not valid");
					}
				} catch (BadPaddingException bpe) {
					// This is a particular exception when the password is incorrect
					showError("Incorrect password.");
				} catch (ShortBufferException bpe) {
					showError("Incorrect password.");
				} catch (GeneralSecurityException gse) {
					// Handles all other exceptions
					showError("General Security Exception while decrypting: "
							+ gse.toString());
				}
			}
		}
	}

	/**  Called when display's orientation has changed. */
	public void displayOrientationChanged( int newDisplayOrientation ){

		/** Change MIDlet UI orientation */
		Orientation.setAppOrientation(newDisplayOrientation);     
	} 
}