initial commit adding otr to beem, it's based on http://bitbucket.org/romanzadov/beem, with a better beem integration
--- a/default.properties Thu Aug 26 09:58:50 2010 +0200
+++ b/default.properties Sun Dec 05 18:43:51 2010 +0100
@@ -7,5 +7,7 @@
# "build.properties", and override values to adapt the script to your
# project structure.
+# Indicates whether an apk should be generated for each density.
+split.density=false
# Project target.
-target=android-7
+target=android-8
Binary file libs/junit-4.6.jar has changed
Binary file libs/lcrypto-jdk16-143.jar has changed
--- a/res/layout/chat.xml Thu Aug 26 09:58:50 2010 +0200
+++ b/res/layout/chat.xml Sun Dec 05 18:43:51 2010 +0100
@@ -27,6 +27,10 @@
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textStyle="italic" android:textSize="12sp"
android:focusable="true"/>
+ <TextView android:id="@+id/chat_contact_otr_state"
+ android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:textStyle="italic" android:textSize="12sp"
+ android:focusable="true"/>
</LinearLayout>
</LinearLayout>
<View android:layout_width="fill_parent" android:layout_height="2dp"
--- a/res/menu/chat.xml Thu Aug 26 09:58:50 2010 +0200
+++ b/res/menu/chat.xml Sun Dec 05 18:43:51 2010 +0100
@@ -7,4 +7,8 @@
</group>
<item android:id="@+id/chat_menu_close_chat" android:visible="true"
android:title="@string/chat_menu_close_chat" android:icon="@drawable/ic_menu_end_conversation" />
+ <item android:id="@+id/chat_menu_start_otr_session" android:visible="true"
+ android:title="@string/chat_menu_start_otr_session" />
+ <item android:id="@+id/chat_menu_stop_otr_session" android:visible="true"
+ android:title="@string/chat_menu_stop_otr_session" />
</menu>
--- a/res/values/strings.xml Thu Aug 26 09:58:50 2010 +0200
+++ b/res/values/strings.xml Sun Dec 05 18:43:51 2010 +0100
@@ -249,6 +249,8 @@
<string name="chat_send_message">Send</string>
<string name="chat_menu_contacts_list">Contacts list</string>
<string name="chat_menu_change_chat">Switch chat</string>
+ <string name="chat_menu_start_otr_session">Start OTR session</string>
+ <string name="chat_menu_stop_otr_session">Stop OTR session</string>
<string name="chat_dialog_change_chat_title">Opened chats</string>
<string name="chat_menu_close_chat">Close this chat</string>
<string name="chat_no_more_chats">No more active chats</string>
--- a/src/com/beem/project/beem/service/BeemChatManager.java Thu Aug 26 09:58:50 2010 +0200
+++ b/src/com/beem/project/beem/service/BeemChatManager.java Sun Dec 05 18:43:51 2010 +0100
@@ -306,5 +306,9 @@
@Override
public void stateChanged(final IChat chat) { }
+
+ @Override
+ public void otrStateChanged(String otrState) throws RemoteException {
+ }
}
}
--- a/src/com/beem/project/beem/service/ChatAdapter.java Thu Aug 26 09:58:50 2010 +0200
+++ b/src/com/beem/project/beem/service/ChatAdapter.java Sun Dec 05 18:43:51 2010 +0100
@@ -49,8 +49,8 @@
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.ChatState;
import org.jivesoftware.smackx.ChatStateListener;
-import org.jivesoftware.smackx.ChatState;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -58,6 +58,7 @@
import com.beem.project.beem.service.aidl.IChat;
import com.beem.project.beem.service.aidl.IMessageListener;
+import com.zadov.beem.BeemOtrManager;
/**
* An adapter for smack's Chat class.
@@ -74,6 +75,7 @@
private final List<Message> mMessages;
private final RemoteCallbackList<IMessageListener> mRemoteListeners = new RemoteCallbackList<IMessageListener>();
private final MsgListener mMsgListener = new MsgListener();
+ private BeemOtrManager mOtrManager;
/**
* Constructor.
@@ -84,6 +86,8 @@
mParticipant = new Contact(chat.getParticipant());
mMessages = new LinkedList<Message>();
mAdaptee.addMessageListener(mMsgListener);
+ mOtrManager = new BeemOtrManager(this, mParticipant.getJIDWithRes(), mParticipant.getJID());
+ Log.d(TAG, "new chat, with otr " + mOtrManager.status());
}
/**
@@ -100,9 +104,15 @@
@Override
public void sendMessage(com.beem.project.beem.service.Message message) throws RemoteException {
org.jivesoftware.smack.packet.Message send = new org.jivesoftware.smack.packet.Message();
+ String msgBody = message.getBody();
send.setTo(message.getTo());
Log.w(TAG, "message to " + message.getTo());
- send.setBody(message.getBody());
+ if (mOtrManager != null) {
+ Log.d(TAG, "msg out, with otr " + mOtrManager.status());
+ msgBody = mOtrManager.sendMessage(msgBody);
+ }
+
+ send.setBody(msgBody);
send.setThread(message.getThread());
send.setSubject(message.getSubject());
send.setType(org.jivesoftware.smack.packet.Message.Type.chat);
@@ -116,6 +126,16 @@
e.printStackTrace();
}
}
+
+ public void sendMessage(String msg) {
+ Message msgToSend = new Message(mParticipant.getJIDWithRes(), Message.MSG_TYPE_CHAT);
+ msgToSend.setBody(msg);
+ try {
+ sendMessage(msgToSend);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
/**
* {@inheritDoc}
@@ -206,6 +226,12 @@
@Override
public void processMessage(Chat chat, org.jivesoftware.smack.packet.Message message) {
Message msg = new Message(message);
+ Log.d(TAG, "new msg " + msg.getBody());
+ if (mOtrManager != null) {
+ Log.d(TAG, "msg in, with otr " + mOtrManager.status());
+ msg.setBody(mOtrManager.recieveMessage(msg.getBody()));
+ }
+
//TODO add que les message pas de type errors
ChatAdapter.this.addMessage(msg);
final int n = mRemoteListeners.beginBroadcast();
@@ -240,5 +266,16 @@
mRemoteListeners.finishBroadcast();
}
}
+
+ @Override
+ public void startOtrSession() throws RemoteException {
+ mOtrManager.startSession();
+ Log.d(TAG, "start otr " + mOtrManager.status());
+ }
+
+ @Override
+ public void endOtrSession() throws RemoteException {
+ mOtrManager.endSession();
+ }
}
--- a/src/com/beem/project/beem/service/aidl/IChat.aidl Thu Aug 26 09:58:50 2010 +0200
+++ b/src/com/beem/project/beem/service/aidl/IChat.aidl Sun Dec 05 18:43:51 2010 +0100
@@ -85,5 +85,15 @@
void setState(in String state);
List<Message> getMessages();
+
+ /**
+ * Try to start an OTR session for the current chat.
+ */
+ void startOtrSession();
+
+ /**
+ * Stop the OTR session of the current chat.
+ */
+ void endOtrSession();
}
--- a/src/com/beem/project/beem/service/aidl/IMessageListener.aidl Thu Aug 26 09:58:50 2010 +0200
+++ b/src/com/beem/project/beem/service/aidl/IMessageListener.aidl Sun Dec 05 18:43:51 2010 +0100
@@ -61,4 +61,11 @@
* @param chat the chat changed.
*/
void stateChanged(in IChat chat);
+
+ /**
+ * This method is executed when the otr session status change.
+ * @param otrState the new state of otr session.
+ */
+ void otrStateChanged(in String otrState);
+
}
--- a/src/com/beem/project/beem/ui/Chat.java Thu Aug 26 09:58:50 2010 +0200
+++ b/src/com/beem/project/beem/ui/Chat.java Sun Dec 05 18:43:51 2010 +0100
@@ -117,6 +117,7 @@
private TextView mContactNameTextView;
private TextView mContactStatusMsgTextView;
private TextView mContactChatState;
+ private TextView mContactOtrState;
private ImageView mContactStatusIcon;
private ListView mMessagesListView;
private EditText mInputField;
@@ -157,6 +158,7 @@
mContactNameTextView = (TextView) findViewById(R.id.chat_contact_name);
mContactStatusMsgTextView = (TextView) findViewById(R.id.chat_contact_status_msg);
mContactChatState = (TextView) findViewById(R.id.chat_contact_chat_state);
+ mContactOtrState = (TextView) findViewById(R.id.chat_contact_otr_state);
mContactStatusIcon = (ImageView) findViewById(R.id.chat_contact_status_icon);
mMessagesListView = (ListView) findViewById(R.id.chat_messages);
mMessagesListView.setAdapter(mMessagesListAdapter);
@@ -286,6 +288,36 @@
}
this.finish();
break;
+ case R.id.chat_menu_start_otr_session:
+ try {
+ Log.d(TAG, "opened chats = " + mChat + " for "+mContact);
+ if (mChat == null) {
+ mChat = mChatManager.createChat(mContact, mMessageListener);
+ if (mChat != null) {
+ mChat.setOpen(true);
+ }
+
+ }
+ mChat.startOtrSession();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ break;
+ case R.id.chat_menu_stop_otr_session:
+ try {
+ Log.d(TAG, "opened chats = " + mChat + " for "+mContact);
+ if (mChat == null) {
+ mChat = mChatManager.createChat(mContact, mMessageListener);
+ if (mChat != null) {
+ mChat.setOpen(true);
+ }
+
+ }
+ mChat.endOtrSession();
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ break;
default:
return false;
}
@@ -537,6 +569,25 @@
});
}
+
+ @Override
+ public void otrStateChanged(final String otrState) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ String text = null;
+ if ("PLAINTEXT".equals(otrState)) {
+ text = Chat.this.getString(R.string.chat_state_active);
+ } else if ("ENCRYPTED".equals(otrState)) {
+ text = Chat.this.getString(R.string.chat_state_composing);
+ } else if ("FINISHED".equals(otrState)) {
+ text = Chat.this.getString(R.string.chat_state_active);
+ }
+ mContactOtrState.setText(text);
+ }
+ });
+
+ }
}
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/zadov/beem/BeemOtrManager.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,62 @@
+package com.zadov.beem;
+
+import net.java.otr4j.OtrEngineHostImpl;
+import net.java.otr4j.OtrEngineImpl;
+import net.java.otr4j.OtrPolicy;
+import net.java.otr4j.OtrPolicyImpl;
+import net.java.otr4j.session.SessionID;
+import android.util.Log;
+
+import com.beem.project.beem.service.ChatAdapter;
+
+public class BeemOtrManager {
+
+ private static final String TAG = "BeemOtrManager";
+ protected ChatAdapter mChat;
+ protected OtrEngineHostImpl myHost;
+ private OtrEngineImpl usAlice;
+ private SessionID aliceSessionID;
+
+ public BeemOtrManager(ChatAdapter chat, String jidres, String jid){
+ mChat = chat;
+ myHost = new OtrEngineHostImpl(chat,
+ new OtrPolicyImpl(OtrPolicy.ALLOW_V2
+ | OtrPolicy.ERROR_START_AKE));
+ aliceSessionID = new SessionID(jidres,jid, "XMMP");
+ usAlice = new OtrEngineImpl(myHost);
+ }
+
+ public void startSession() {
+ usAlice.startSession(aliceSessionID);
+ }
+
+ public void endSession() {
+ usAlice.endSession(aliceSessionID);
+ }
+
+ public String status() {
+ return usAlice.getSessionStatus(aliceSessionID).toString();
+ }
+
+ public String recieveMessage(String msg) {
+ Log.d(TAG, "in: "+msg);
+ String plain = null;
+ //mChat.otrStatusDisplay.setText("otr status: "+usAlice.getSessionStatus(aliceSessionID).toString());
+ if(usAlice != null){
+ plain = usAlice.transformReceiving(aliceSessionID, msg);
+ }
+ Log.d(TAG,"in: "+plain);
+ return plain;
+ }
+
+ public String sendMessage(String msg) {
+ Log.d(TAG, "out: "+msg);
+ //mChat.otrStatusDisplay.setText("otr status: "+usAlice.getSessionStatus(aliceSessionID).toString());
+ if(usAlice != null){
+ msg = usAlice.transformSending(aliceSessionID, msg);
+ }
+ Log.d(TAG, "out: "+msg);
+ return msg;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrEngine.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,79 @@
+package net.java.otr4j;
+
+import java.security.PublicKey;
+
+import net.java.otr4j.session.SessionID;
+import net.java.otr4j.session.SessionStatus;
+
+/**
+ *
+ * @author George Politis
+ *
+ */
+public interface OtrEngine {
+
+ /**
+ *
+ * @param sessionID
+ * The session identifier.
+ * @param content
+ * The message content to be transformed.
+ * @return The transformed message content.
+ */
+ public abstract String transformReceiving(SessionID sessionID,
+ String content);
+
+ /**
+ *
+ * @param sessionID
+ * The session identifier.
+ * @param content
+ * The message content to be transformed.
+ * @return The transformed message content.
+ */
+ public abstract String transformSending(SessionID sessionID, String content);
+
+ /**
+ * Starts an Off-the-Record session, if there is no active one.
+ *
+ * @param sessionID
+ * The session identifier.
+ */
+ public abstract void startSession(SessionID sessionID);
+
+ /**
+ * Ends the Off-the-Record session, if exists.
+ *
+ * @param sessionID
+ * The session identifier.
+ */
+ public abstract void endSession(SessionID sessionID);
+
+ /**
+ * Stops/Starts the Off-the-Record session.
+ *
+ * @param sessionID
+ * The session identifier.
+ */
+ public abstract void refreshSession(SessionID sessionID);
+
+ /**
+ *
+ * @param sessionID
+ * The session identifier.
+ * @return The status of an Off-the-Record session.
+ */
+ public abstract SessionStatus getSessionStatus(SessionID sessionID);
+
+ /**
+ *
+ * @param sessionID
+ * The session identifier.
+ * @return The remote public key.
+ */
+ public abstract PublicKey getRemotePublicKey(SessionID sessionID);
+
+ public abstract void addOtrEngineListener(OtrEngineListener l);
+
+ public abstract void removeOtrEngineListener(OtrEngineListener l);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrEngineHost.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,31 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j;
+
+import java.security.KeyPair;
+
+import net.java.otr4j.session.SessionID;
+
+/**
+ *
+ * This interface should be implemented by the host application. It is required
+ * for otr4j to work properly.
+ *
+ * @author George Politis
+ *
+ */
+public abstract interface OtrEngineHost {
+ public abstract void injectMessage(SessionID sessionID, String msg);
+
+ public abstract void showWarning(SessionID sessionID, String warning);
+
+ public abstract void showError(SessionID sessionID, String error);
+
+ public abstract OtrPolicy getSessionPolicy(SessionID sessionID);
+
+ public abstract KeyPair getKeyPair(SessionID sessionID);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrEngineHostImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,68 @@
+package net.java.otr4j;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+
+import net.java.otr4j.crypto.KeyAndSession;
+import net.java.otr4j.session.SessionID;
+
+import com.beem.project.beem.service.ChatAdapter;
+
+
+public class OtrEngineHostImpl implements OtrEngineHost{
+
+ private ChatAdapter mChat;
+ private OtrPolicy policy;
+ public String lastInjectedMessage;
+ private ArrayList<KeyAndSession> keyring = new ArrayList<KeyAndSession>();
+
+ public OtrEngineHostImpl(ChatAdapter chat, OtrPolicy policy){
+ mChat = chat;
+ this.policy = policy;
+ }
+
+ @Override
+ public KeyPair getKeyPair(SessionID sessionID) {
+
+ for(int i = 0; i<keyring.size(); i++){
+ if(sessionID.equals(keyring.get(i).getSessionID())){
+ return keyring.get(i).getKeyPair();
+ }
+ }
+
+ KeyPairGenerator kg;
+ try {
+ kg = KeyPairGenerator.getInstance("DSA");
+
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ }
+ KeyAndSession kp = new KeyAndSession(kg.genKeyPair(), sessionID);
+ keyring.add(kp);
+ return kp.getKeyPair();
+ }
+
+ @Override
+ public OtrPolicy getSessionPolicy(SessionID sessionID) {
+ return this.policy;
+ }
+
+ @Override
+ public void injectMessage(SessionID sessionID, String msg) {
+ mChat.sendMessage(msg);
+ }
+
+ @Override
+ public void showError(SessionID sessionID, String error) {
+ // mChat.otrStatusDisplay.setText(""+error);
+ }
+
+ @Override
+ public void showWarning(SessionID sessionID, String warning) {
+ //mChat.otrStatusDisplay.setText(""+warning);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrEngineImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,136 @@
+/*
+ * otr4j, the open source java otr librar
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+package net.java.otr4j;
+
+import java.security.PublicKey;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import net.java.otr4j.session.Session;
+import net.java.otr4j.session.SessionID;
+import net.java.otr4j.session.SessionImpl;
+import net.java.otr4j.session.SessionStatus;
+
+/**
+ *
+ * @author George Politis
+ *
+ */
+public class OtrEngineImpl implements OtrEngine {
+
+ public OtrEngineImpl(OtrEngineHost listener) {
+ if (listener == null)
+ throw new IllegalArgumentException("OtrEgineHost is required.");
+
+ this.setListener(listener);
+ }
+
+ private OtrEngineHost listener;
+ private Map<SessionID, Session> sessions;
+
+ private Session getSession(SessionID sessionID) {
+
+ if (sessionID == null || sessionID.equals(SessionID.Empty))
+ throw new IllegalArgumentException();
+
+ if (sessions == null)
+ sessions = new Hashtable<SessionID, Session>();
+
+ if (!sessions.containsKey(sessionID)) {
+ Session session = new SessionImpl(sessionID, getListener());
+ sessions.put(sessionID, session);
+
+ session.addOtrEngineListener(new OtrEngineListener() {
+
+ public void sessionStatusChanged(SessionID sessionID) {
+ for (OtrEngineListener l : listeners)
+ l.sessionStatusChanged(sessionID);
+ }
+ });
+
+ }
+
+ return sessions.get(sessionID);
+ }
+
+ public SessionStatus getSessionStatus(SessionID sessionID) {
+ return this.getSession(sessionID).getSessionStatus();
+ }
+
+ public String transformReceiving(SessionID sessionID, String msgText) {
+ try {
+ return this.getSession(sessionID).transformReceiving(msgText);
+ } catch (OtrException e) {
+ listener.showError(sessionID, e.getMessage());
+ return null;
+ }
+ }
+
+ public String transformSending(SessionID sessionID, String msgText) {
+ try {
+ return this.getSession(sessionID).transformSending(msgText, null);
+ } catch (OtrException e) {
+ listener.showError(sessionID, e.getMessage());
+ return null;
+ }
+ }
+
+ public void endSession(SessionID sessionID) {
+ try {
+ this.getSession(sessionID).endSession();
+ } catch (OtrException e) {
+ listener.showError(sessionID, e.getMessage());
+ }
+ }
+
+ public void startSession(SessionID sessionID) {
+ try {
+ this.getSession(sessionID).startSession();
+ } catch (OtrException e) {
+ listener.showError(sessionID, e.getMessage());
+ }
+ }
+
+ private void setListener(OtrEngineHost listener) {
+ this.listener = listener;
+ }
+
+ private OtrEngineHost getListener() {
+ return listener;
+ }
+
+ public void refreshSession(SessionID sessionID) {
+ try {
+ this.getSession(sessionID).refreshSession();
+ } catch (OtrException e) {
+ listener.showError(sessionID, e.getMessage());
+ }
+ }
+
+ public PublicKey getRemotePublicKey(SessionID sessionID) {
+ return this.getSession(sessionID).getRemotePublicKey();
+ }
+
+ private List<OtrEngineListener> listeners = new Vector<OtrEngineListener>();
+
+ public void addOtrEngineListener(OtrEngineListener l) {
+ synchronized (listeners) {
+ if (!listeners.contains(l))
+ listeners.add(l);
+ }
+
+ }
+
+ public void removeOtrEngineListener(OtrEngineListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrEngineListener.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,14 @@
+package net.java.otr4j;
+
+import net.java.otr4j.session.SessionID;
+
+/**
+ * This interface should be implemented by the host application. It notifies
+ * about session status changes.
+ *
+ * @author George Politis
+ *
+ */
+public interface OtrEngineListener {
+ public abstract void sessionStatusChanged(SessionID sessionID);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrException.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,8 @@
+package net.java.otr4j;
+
+@SuppressWarnings("serial")
+public class OtrException extends Exception {
+ public OtrException(Exception e){
+ super(e);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrKeyManager.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,31 @@
+package net.java.otr4j;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+
+import net.java.otr4j.session.SessionID;
+
+public abstract interface OtrKeyManager {
+
+ public abstract void addListener(OtrKeyManagerListener l);
+
+ public abstract void removeListener(OtrKeyManagerListener l);
+
+ public abstract void verify(SessionID sessionID);
+
+ public abstract void unverify(SessionID sessionID);
+
+ public abstract boolean isVerified(SessionID sessionID);
+
+ public abstract String getRemoteFingerprint(SessionID sessionID);
+
+ public abstract String getLocalFingerprint(SessionID sessionID);
+
+ public abstract void savePublicKey(SessionID sessionID, PublicKey pubKey);
+
+ public abstract PublicKey loadRemotePublicKey(SessionID sessionID);
+
+ public abstract KeyPair loadLocalKeyPair(SessionID sessionID);
+
+ public abstract void generateLocalKeyPair(SessionID sessionID);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrKeyManagerImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,302 @@
+package net.java.otr4j;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.List;
+import java.util.Properties;
+import java.util.Vector;
+
+import org.bouncycastle.util.encoders.Base64;
+
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.crypto.OtrCryptoException;
+import net.java.otr4j.session.SessionID;
+
+public class OtrKeyManagerImpl implements OtrKeyManager {
+
+ private OtrKeyManagerStore store;
+
+ public OtrKeyManagerImpl(OtrKeyManagerStore store) {
+ this.store = store;
+ }
+
+ class DefaultPropertiesStore implements OtrKeyManagerStore {
+ private final Properties properties = new Properties();
+ private String filepath;
+
+ public DefaultPropertiesStore(String filepath) throws IOException {
+ if (filepath == null || filepath.length() < 1)
+ throw new IllegalArgumentException();
+ this.filepath = filepath;
+ properties.clear();
+
+ InputStream in = new BufferedInputStream(new FileInputStream(
+ getConfigurationFile()));
+ try {
+ properties.load(in);
+ } finally {
+ in.close();
+ }
+ }
+
+ private File getConfigurationFile() throws IOException {
+ File configFile = new File(filepath);
+ if (!configFile.exists())
+ configFile.createNewFile();
+ return configFile;
+ }
+
+ public void setProperty(String id, boolean value) {
+ properties.setProperty(id, "true");
+ try {
+ this.store();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void store() throws FileNotFoundException, IOException {
+ OutputStream out = new FileOutputStream(getConfigurationFile());
+ properties.store(out, null);
+ out.close();
+ }
+
+ public void setProperty(String id, byte[] value) {
+ properties.setProperty(id, new String(Base64.encode(value)));
+ try {
+ this.store();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void removeProperty(String id) {
+ properties.remove(id);
+
+ }
+
+ public byte[] getPropertyBytes(String id) {
+ String value = properties.getProperty(id);
+ return Base64.decode(value);
+ }
+
+ public boolean getPropertyBoolean(String id, boolean defaultValue) {
+ try {
+ return Boolean.valueOf(properties.get(id).toString());
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+ }
+
+ public OtrKeyManagerImpl(String filepath) throws IOException {
+ this.store = new DefaultPropertiesStore(filepath);
+ }
+
+ private List<OtrKeyManagerListener> listeners = new Vector<OtrKeyManagerListener>();
+
+ public void addListener(OtrKeyManagerListener l) {
+ synchronized (listeners) {
+ if (!listeners.contains(l))
+ listeners.add(l);
+ }
+ }
+
+ public void removeListener(OtrKeyManagerListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ public void generateLocalKeyPair(SessionID sessionID) {
+ if (sessionID == null)
+ return;
+
+ String accountID = sessionID.getAccountID();
+ KeyPair keyPair;
+ try {
+ keyPair = KeyPairGenerator.getInstance("DSA").genKeyPair();
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ // Store Public Key.
+ PublicKey pubKey = keyPair.getPublic();
+ X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey
+ .getEncoded());
+
+ this.store.setProperty(accountID + ".publicKey", x509EncodedKeySpec
+ .getEncoded());
+
+ // Store Private Key.
+ PrivateKey privKey = keyPair.getPrivate();
+ PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
+ privKey.getEncoded());
+
+ this.store.setProperty(accountID + ".privateKey", pkcs8EncodedKeySpec
+ .getEncoded());
+ }
+
+ public String getLocalFingerprint(SessionID sessionID) {
+ KeyPair keyPair = loadLocalKeyPair(sessionID);
+
+ if (keyPair == null)
+ return null;
+
+ PublicKey pubKey = keyPair.getPublic();
+
+ try {
+ return new OtrCryptoEngineImpl().getFingerprint(pubKey);
+ } catch (OtrCryptoException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public String getRemoteFingerprint(SessionID sessionID) {
+ PublicKey remotePublicKey = loadRemotePublicKey(sessionID);
+ if (remotePublicKey == null)
+ return null;
+ try {
+ return new OtrCryptoEngineImpl().getFingerprint(remotePublicKey);
+ } catch (OtrCryptoException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public boolean isVerified(SessionID sessionID) {
+ if (sessionID == null)
+ return false;
+
+ return this.store.getPropertyBoolean(sessionID.getUserID()
+ + ".publicKey.verified", false);
+ }
+
+ public KeyPair loadLocalKeyPair(SessionID sessionID) {
+ if (sessionID == null)
+ return null;
+
+ String accountID = sessionID.getAccountID();
+ // Load Private Key.
+ byte[] b64PrivKey = this.store.getPropertyBytes(accountID
+ + ".privateKey");
+ if (b64PrivKey == null)
+ return null;
+
+ PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(b64PrivKey);
+
+ // Load Public Key.
+ byte[] b64PubKey = this.store
+ .getPropertyBytes(accountID + ".publicKey");
+ if (b64PubKey == null)
+ return null;
+
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey);
+
+ PublicKey publicKey;
+ PrivateKey privateKey;
+
+ // Generate KeyPair.
+ KeyFactory keyFactory;
+ try {
+ keyFactory = KeyFactory.getInstance("DSA");
+ publicKey = keyFactory.generatePublic(publicKeySpec);
+ privateKey = keyFactory.generatePrivate(privateKeySpec);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ } catch (InvalidKeySpecException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ return new KeyPair(publicKey, privateKey);
+ }
+
+ public PublicKey loadRemotePublicKey(SessionID sessionID) {
+ if (sessionID == null)
+ return null;
+
+ String userID = sessionID.getUserID();
+
+ byte[] b64PubKey = this.store.getPropertyBytes(userID + ".publicKey");
+ if (b64PubKey == null)
+ return null;
+
+ X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(b64PubKey);
+
+ // Generate KeyPair.
+ KeyFactory keyFactory;
+ try {
+ keyFactory = KeyFactory.getInstance("DSA");
+ return keyFactory.generatePublic(publicKeySpec);
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ return null;
+ } catch (InvalidKeySpecException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void savePublicKey(SessionID sessionID, PublicKey pubKey) {
+ if (sessionID == null)
+ return;
+
+ X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pubKey
+ .getEncoded());
+
+ String userID = sessionID.getUserID();
+ this.store.setProperty(userID + ".publicKey", x509EncodedKeySpec
+ .getEncoded());
+
+ this.store.removeProperty(userID + ".publicKey.verified");
+ }
+
+ public void unverify(SessionID sessionID) {
+ if (sessionID == null)
+ return;
+
+ if (!isVerified(sessionID))
+ return;
+
+ this.store
+ .removeProperty(sessionID.getUserID() + ".publicKey.verified");
+
+ for (OtrKeyManagerListener l : listeners)
+ l.verificationStatusChanged(sessionID);
+
+ }
+
+ public void verify(SessionID sessionID) {
+ if (sessionID == null)
+ return;
+
+ if (this.isVerified(sessionID))
+ return;
+
+ this.store.setProperty(sessionID.getUserID() + ".publicKey.verified",
+ true);
+
+ for (OtrKeyManagerListener l : listeners)
+ l.verificationStatusChanged(sessionID);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrKeyManagerListener.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,7 @@
+package net.java.otr4j;
+
+import net.java.otr4j.session.SessionID;
+
+public interface OtrKeyManagerListener {
+ public abstract void verificationStatusChanged(SessionID session);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrKeyManagerStore.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,13 @@
+package net.java.otr4j;
+
+public interface OtrKeyManagerStore {
+ public abstract byte[] getPropertyBytes(String id);
+
+ public abstract boolean getPropertyBoolean(String id, boolean defaultValue);
+
+ public abstract void setProperty(String id, byte[] value);
+
+ public abstract void setProperty(String id, boolean value);
+
+ public abstract void removeProperty(String id);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrPolicy.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,68 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j;
+
+/**
+ *
+ * @author George Politis
+ *
+ */
+public interface OtrPolicy {
+
+ public static final int ALLOW_V1 = 0x01;
+ public static final int ALLOW_V2 = 0x02;
+ public static final int REQUIRE_ENCRYPTION = 0x04;
+ public static final int SEND_WHITESPACE_TAG = 0x08;
+ public static final int WHITESPACE_START_AKE = 0x10;
+ public static final int ERROR_START_AKE = 0x20;
+ public static final int VERSION_MASK = (ALLOW_V1 | ALLOW_V2);
+
+ // The four old version 1 policies correspond to the following combinations
+ // of flags (adding an allowance for version 2 of the protocol):
+
+ public static final int NEVER = 0x00;
+ public static final int OPPORTUNISTIC = (ALLOW_V1 | ALLOW_V2
+ | SEND_WHITESPACE_TAG | WHITESPACE_START_AKE | ERROR_START_AKE);
+ public static final int OTRL_POLICY_MANUAL = (ALLOW_V1 | ALLOW_V2);
+ public static final int OTRL_POLICY_ALWAYS = (ALLOW_V1 | ALLOW_V2
+ | REQUIRE_ENCRYPTION | WHITESPACE_START_AKE | ERROR_START_AKE);
+ public static final int OTRL_POLICY_DEFAULT = OPPORTUNISTIC;
+
+ public abstract boolean getAllowV1();
+
+ public abstract boolean getAllowV2();
+
+ public abstract boolean getRequireEncryption();
+
+ public abstract boolean getSendWhitespaceTag();
+
+ public abstract boolean getWhitespaceStartAKE();
+
+ public abstract boolean getErrorStartAKE();
+
+ public abstract int getPolicy();
+
+ public abstract void setAllowV1(boolean value);
+
+ public abstract void setAllowV2(boolean value);
+
+ public abstract void setRequireEncryption(boolean value);
+
+ public abstract void setSendWhitespaceTag(boolean value);
+
+ public abstract void setWhitespaceStartAKE(boolean value);
+
+ public abstract void setErrorStartAKE(boolean value);
+
+ public abstract void setEnableAlways(boolean value);
+
+ public abstract boolean getEnableAlways();
+
+ public abstract void setEnableManual(boolean value);
+
+ public abstract boolean getEnableManual();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/OtrPolicyImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,128 @@
+package net.java.otr4j;
+
+public class OtrPolicyImpl implements OtrPolicy {
+
+ public OtrPolicyImpl() {
+ this.setPolicy(NEVER);
+ }
+
+ public OtrPolicyImpl(int policy) {
+ this.setPolicy(policy);
+ }
+
+ private int policy;
+
+ public int getPolicy() {
+ return policy;
+ }
+
+ private void setPolicy(int policy) {
+ this.policy = policy;
+ }
+
+ public boolean getAllowV1() {
+ return (policy & OtrPolicy.ALLOW_V1) != 0;
+ }
+
+ public boolean getAllowV2() {
+ return (policy & OtrPolicy.ALLOW_V2) != 0;
+ }
+
+ public boolean getErrorStartAKE() {
+ return (policy & OtrPolicy.ERROR_START_AKE) != 0;
+ }
+
+ public boolean getRequireEncryption() {
+ return getEnableManual()
+ && (policy & OtrPolicy.REQUIRE_ENCRYPTION) != 0;
+ }
+
+ public boolean getSendWhitespaceTag() {
+ return (policy & OtrPolicy.SEND_WHITESPACE_TAG) != 0;
+ }
+
+ public boolean getWhitespaceStartAKE() {
+ return (policy & OtrPolicy.WHITESPACE_START_AKE) != 0;
+ }
+
+ public void setAllowV1(boolean value) {
+ if (value)
+ policy |= ALLOW_V1;
+ else
+ policy &= ~ALLOW_V1;
+ }
+
+ public void setAllowV2(boolean value) {
+ if (value)
+ policy |= ALLOW_V2;
+ else
+ policy &= ~ALLOW_V2;
+ }
+
+ public void setErrorStartAKE(boolean value) {
+ if (value)
+ policy |= ERROR_START_AKE;
+ else
+ policy &= ~ERROR_START_AKE;
+ }
+
+ public void setRequireEncryption(boolean value) {
+ if (value)
+ policy |= REQUIRE_ENCRYPTION;
+ else
+ policy &= ~REQUIRE_ENCRYPTION;
+ }
+
+ public void setSendWhitespaceTag(boolean value) {
+ if (value)
+ policy |= SEND_WHITESPACE_TAG;
+ else
+ policy &= ~SEND_WHITESPACE_TAG;
+ }
+
+ public void setWhitespaceStartAKE(boolean value) {
+ if (value)
+ policy |= WHITESPACE_START_AKE;
+ else
+ policy &= ~WHITESPACE_START_AKE;
+ }
+
+ public boolean getEnableAlways() {
+ return getEnableManual() && getErrorStartAKE()
+ && getSendWhitespaceTag() && getWhitespaceStartAKE();
+ }
+
+ public void setEnableAlways(boolean value) {
+ if (value)
+ setEnableManual(true);
+
+ setErrorStartAKE(value);
+ setSendWhitespaceTag(value);
+ setWhitespaceStartAKE(value);
+
+ }
+
+ public boolean getEnableManual() {
+ return getAllowV1() && getAllowV2();
+ }
+
+ public void setEnableManual(boolean value) {
+ setAllowV1(value);
+ setAllowV2(value);
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj == null || obj.getClass() != this.getClass())
+ return false;
+
+ OtrPolicy policy = (OtrPolicy) obj;
+
+ return policy.getPolicy() == this.getPolicy();
+ }
+
+ public int hashCode() {
+ return this.getPolicy();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/crypto/KeyAndSession.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,23 @@
+package net.java.otr4j.crypto;
+
+import java.security.KeyPair;
+
+import net.java.otr4j.session.SessionID;
+
+public class KeyAndSession {
+
+ private KeyPair myKeyPair;
+ private SessionID mySessionID;
+
+ public KeyAndSession(KeyPair kp, SessionID si){
+ myKeyPair = kp;
+ mySessionID = si;
+ }
+
+ public KeyPair getKeyPair(){
+ return myKeyPair;
+ }
+ public SessionID getSessionID(){
+ return mySessionID;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/crypto/OtrCryptoEngine.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,83 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+package net.java.otr4j.crypto;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+/**
+ *
+ * @author George Politis
+ *
+ */
+public interface OtrCryptoEngine {
+
+ public static final String MODULUS_TEXT = "00FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
+ public static final BigInteger MODULUS = new BigInteger(MODULUS_TEXT, 16);
+ public static final BigInteger BIGINTEGER_TWO = BigInteger.valueOf(2);
+ public static final BigInteger MODULUS_MINUS_TWO = MODULUS
+ .subtract(BIGINTEGER_TWO);
+
+ public static String GENERATOR_TEXT = "2";
+ public static BigInteger GENERATOR = new BigInteger(GENERATOR_TEXT, 10);
+
+ public static final int AES_KEY_BYTE_LENGTH = 16;
+ public static final int SHA256_HMAC_KEY_BYTE_LENGTH = 32;
+ public static final int DH_PRIVATE_KEY_MINIMUM_BIT_LENGTH = 320;
+ public static final byte[] ZERO_CTR = new byte[] { 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00 };
+
+ public static final int DSA_PUB_TYPE = 0;
+
+ public abstract KeyPair generateDHKeyPair() throws OtrCryptoException;
+
+ public abstract DHPublicKey getDHPublicKey(byte[] mpiBytes)
+ throws OtrCryptoException;
+
+ public abstract DHPublicKey getDHPublicKey(BigInteger mpi)
+ throws OtrCryptoException;
+
+ public abstract byte[] sha256Hmac(byte[] b, byte[] key)
+ throws OtrCryptoException;
+
+ public abstract byte[] sha256Hmac(byte[] b, byte[] key, int length)
+ throws OtrCryptoException;
+
+ public abstract byte[] sha1Hmac(byte[] b, byte[] key, int length)
+ throws OtrCryptoException;
+
+ public abstract byte[] sha256Hmac160(byte[] b, byte[] key)
+ throws OtrCryptoException;
+
+ public abstract byte[] sha256Hash(byte[] b) throws OtrCryptoException;
+
+ public abstract byte[] sha1Hash(byte[] b) throws OtrCryptoException;
+
+ public abstract byte[] aesDecrypt(byte[] key, byte[] ctr, byte[] b)
+ throws OtrCryptoException;
+
+ public abstract byte[] aesEncrypt(byte[] key, byte[] ctr, byte[] b)
+ throws OtrCryptoException;
+
+ public abstract BigInteger generateSecret(PrivateKey privKey,
+ PublicKey pubKey) throws OtrCryptoException;
+
+ public abstract byte[] sign(byte[] b, PrivateKey privatekey)
+ throws OtrCryptoException;
+
+ public abstract boolean verify(byte[] b, PublicKey pubKey, byte[] rs)
+ throws OtrCryptoException;
+
+ public abstract String getFingerprint(PublicKey pubKey)
+ throws OtrCryptoException;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,393 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.crypto;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.DSAPublicKey;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHPrivateKeySpec;
+import javax.crypto.spec.DHPublicKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import net.java.otr4j.io.SerializationUtils;
+
+import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.engines.AESFastEngine;
+import org.bouncycastle.crypto.generators.DHKeyPairGenerator;
+import org.bouncycastle.crypto.modes.SICBlockCipher;
+import org.bouncycastle.crypto.params.DHKeyGenerationParameters;
+import org.bouncycastle.crypto.params.DHParameters;
+import org.bouncycastle.crypto.params.DHPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DHPublicKeyParameters;
+import org.bouncycastle.crypto.params.DSAParameters;
+import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
+import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.signers.DSASigner;
+import org.bouncycastle.util.BigIntegers;
+
+/**
+ *
+ * @author George Politis
+ *
+ */
+public class OtrCryptoEngineImpl implements OtrCryptoEngine {
+
+ public KeyPair generateDHKeyPair() throws OtrCryptoException {
+
+ // Generate a AsymmetricCipherKeyPair using BC.
+ DHParameters dhParams = new DHParameters(MODULUS, GENERATOR, null,
+ DH_PRIVATE_KEY_MINIMUM_BIT_LENGTH);
+ DHKeyGenerationParameters params = new DHKeyGenerationParameters(
+ new SecureRandom(), dhParams);
+ DHKeyPairGenerator kpGen = new DHKeyPairGenerator();
+
+ kpGen.init(params);
+ AsymmetricCipherKeyPair pair = kpGen.generateKeyPair();
+
+ // Convert this AsymmetricCipherKeyPair to a standard JCE KeyPair.
+ DHPublicKeyParameters pub = (DHPublicKeyParameters) pair.getPublic();
+ DHPrivateKeyParameters priv = (DHPrivateKeyParameters) pair
+ .getPrivate();
+
+ try {
+ KeyFactory keyFac = KeyFactory.getInstance("DH");
+
+ DHPublicKeySpec pubKeySpecs = new DHPublicKeySpec(pub.getY(),
+ MODULUS, GENERATOR);
+ DHPublicKey pubKey = (DHPublicKey) keyFac
+ .generatePublic(pubKeySpecs);
+
+ DHParameters dhParameters = priv.getParameters();
+ DHPrivateKeySpec privKeySpecs = new DHPrivateKeySpec(priv.getX(),
+ dhParameters.getP(), dhParameters.getG());
+ DHPrivateKey privKey = (DHPrivateKey) keyFac
+ .generatePrivate(privKeySpecs);
+
+ return new KeyPair(pubKey, privKey);
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ }
+
+ public DHPublicKey getDHPublicKey(byte[] mpiBytes)
+ throws OtrCryptoException {
+ return getDHPublicKey(new BigInteger(mpiBytes));
+ }
+
+ public DHPublicKey getDHPublicKey(BigInteger mpi) throws OtrCryptoException {
+ DHPublicKeySpec pubKeySpecs = new DHPublicKeySpec(mpi, MODULUS,
+ GENERATOR);
+ try {
+ KeyFactory keyFac = KeyFactory.getInstance("DH");
+ return (DHPublicKey) keyFac.generatePublic(pubKeySpecs);
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ }
+
+ public byte[] sha256Hmac(byte[] b, byte[] key) throws OtrCryptoException {
+ return this.sha256Hmac(b, key, 0);
+ }
+
+ public byte[] sha256Hmac(byte[] b, byte[] key, int length)
+ throws OtrCryptoException {
+
+ SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA256");
+ javax.crypto.Mac mac;
+ try {
+ mac = javax.crypto.Mac.getInstance("HmacSHA256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new OtrCryptoException(e);
+ }
+ try {
+ mac.init(keyspec);
+ } catch (InvalidKeyException e) {
+ throw new OtrCryptoException(e);
+ }
+
+ byte[] macBytes = mac.doFinal(b);
+
+ if (length > 0) {
+ byte[] bytes = new byte[length];
+ ByteBuffer buff = ByteBuffer.wrap(macBytes);
+ buff.get(bytes);
+ return bytes;
+ } else {
+ return macBytes;
+ }
+ }
+
+ public byte[] sha1Hmac(byte[] b, byte[] key, int length)
+ throws OtrCryptoException {
+
+ try {
+ SecretKeySpec keyspec = new SecretKeySpec(key, "HmacSHA1");
+ javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA1");
+ mac.init(keyspec);
+
+ byte[] macBytes = mac.doFinal(b);
+
+ if (length > 0) {
+ byte[] bytes = new byte[length];
+ ByteBuffer buff = ByteBuffer.wrap(macBytes);
+ buff.get(bytes);
+ return bytes;
+ } else {
+ return macBytes;
+ }
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ }
+
+ public byte[] sha256Hmac160(byte[] b, byte[] key) throws OtrCryptoException {
+ return sha256Hmac(b, key, 20);
+ }
+
+ public byte[] sha256Hash(byte[] b) throws OtrCryptoException {
+ try {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
+ sha256.update(b, 0, b.length);
+ return sha256.digest();
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ }
+
+ public byte[] sha1Hash(byte[] b) throws OtrCryptoException {
+ try {
+ MessageDigest sha256 = MessageDigest.getInstance("SHA-1");
+ sha256.update(b, 0, b.length);
+ return sha256.digest();
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ }
+
+ public byte[] aesDecrypt(byte[] key, byte[] ctr, byte[] b)
+ throws OtrCryptoException {
+
+ AESFastEngine aesDec = new AESFastEngine();
+ SICBlockCipher sicAesDec = new SICBlockCipher(aesDec);
+ BufferedBlockCipher bufSicAesDec = new BufferedBlockCipher(sicAesDec);
+
+ // Create initial counter value 0.
+ if (ctr == null)
+ ctr = ZERO_CTR;
+ bufSicAesDec.init(false, new ParametersWithIV(new KeyParameter(key),
+ ctr));
+ byte[] aesOutLwDec = new byte[b.length];
+ int done = bufSicAesDec.processBytes(b, 0, b.length, aesOutLwDec, 0);
+ try {
+ bufSicAesDec.doFinal(aesOutLwDec, done);
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+
+ return aesOutLwDec;
+ }
+
+ public byte[] aesEncrypt(byte[] key, byte[] ctr, byte[] b)
+ throws OtrCryptoException {
+
+ AESFastEngine aesEnc = new AESFastEngine();
+ SICBlockCipher sicAesEnc = new SICBlockCipher(aesEnc);
+ BufferedBlockCipher bufSicAesEnc = new BufferedBlockCipher(sicAesEnc);
+
+ // Create initial counter value 0.
+ if (ctr == null)
+ ctr = ZERO_CTR;
+ bufSicAesEnc.init(true,
+ new ParametersWithIV(new KeyParameter(key), ctr));
+ byte[] aesOutLwEnc = new byte[b.length];
+ int done = bufSicAesEnc.processBytes(b, 0, b.length, aesOutLwEnc, 0);
+ try {
+ bufSicAesEnc.doFinal(aesOutLwEnc, done);
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ return aesOutLwEnc;
+ }
+
+ public BigInteger generateSecret(PrivateKey privKey, PublicKey pubKey)
+ throws OtrCryptoException {
+ try {
+ KeyAgreement ka = KeyAgreement.getInstance("DH");
+ ka.init(privKey);
+ ka.doPhase(pubKey, true);
+ byte[] sb = ka.generateSecret();
+ BigInteger s = new BigInteger(1, sb);
+ return s;
+
+ } catch (Exception e) {
+ throw new OtrCryptoException(e);
+ }
+ }
+
+ public byte[] sign(byte[] b, PrivateKey privatekey)
+ throws OtrCryptoException {
+
+ if (!(privatekey instanceof DSAPrivateKey))
+ throw new IllegalArgumentException();
+
+ DSAParams dsaParams = ((DSAPrivateKey) privatekey).getParams();
+ DSAParameters bcDSAParameters = new DSAParameters(dsaParams.getP(),
+ dsaParams.getQ(), dsaParams.getG());
+
+ DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privatekey;
+ DSAPrivateKeyParameters bcDSAPrivateKeyParms = new DSAPrivateKeyParameters(
+ dsaPrivateKey.getX(), bcDSAParameters);
+
+ DSASigner dsaSigner = new DSASigner();
+ dsaSigner.init(true, bcDSAPrivateKeyParms);
+
+ BigInteger q = dsaParams.getQ();
+
+ // Ian: Note that if you can get the standard DSA implementation you're
+ // using to not hash its input, you should be able to pass it ((256-bit
+ // value) mod q), (rather than truncating the 256-bit value) and all
+ // should be well.
+ // ref: Interop problems with libotr - DSA signature
+ BigInteger bmpi = new BigInteger(1, b);
+ BigInteger[] rs = dsaSigner.generateSignature(BigIntegers
+ .asUnsignedByteArray(bmpi.mod(q)));
+
+ int siglen = q.bitLength() / 4;
+ int rslen = siglen / 2;
+ byte[] rb = BigIntegers.asUnsignedByteArray(rs[0]);
+ byte[] sb = BigIntegers.asUnsignedByteArray(rs[1]);
+
+ // Create the final signature array, padded with zeros if necessary.
+ byte[] sig = new byte[siglen];
+ Boolean writeR = false;
+ Boolean writeS = false;
+ for (int i = 0; i < siglen; i++) {
+ if (i < rslen) {
+ if (!writeR)
+ writeR = rb.length >= rslen - i;
+ sig[i] = (writeR) ? rb[i] : (byte) 0x0;
+ } else {
+ int j = i - rslen; // Rebase.
+ if (!writeS)
+ writeS = sb.length >= rslen - j;
+ sig[i] = (writeS) ? sb[j] : (byte) 0x0;
+ }
+ }
+ return sig;
+ }
+
+ public boolean verify(byte[] b, PublicKey pubKey, byte[] rs)
+ throws OtrCryptoException {
+
+ if (!(pubKey instanceof DSAPublicKey))
+ throw new IllegalArgumentException();
+
+ DSAParams dsaParams = ((DSAPublicKey) pubKey).getParams();
+ int qlen = dsaParams.getQ().bitLength() / 8;
+ ByteBuffer buff = ByteBuffer.wrap(rs);
+ byte[] r = new byte[qlen];
+ buff.get(r);
+ byte[] s = new byte[qlen];
+ buff.get(s);
+ return verify(b, pubKey, r, s);
+ }
+
+ private Boolean verify(byte[] b, PublicKey pubKey, byte[] r, byte[] s)
+ throws OtrCryptoException {
+ Boolean result = verify(b, pubKey, new BigInteger(1, r),
+ new BigInteger(1, s));
+ return result;
+ }
+
+ private Boolean verify(byte[] b, PublicKey pubKey, BigInteger r,
+ BigInteger s) throws OtrCryptoException {
+
+ if (!(pubKey instanceof DSAPublicKey))
+ throw new IllegalArgumentException();
+
+ DSAParams dsaParams = ((DSAPublicKey) pubKey).getParams();
+
+ BigInteger q = dsaParams.getQ();
+ DSAParameters bcDSAParams = new DSAParameters(dsaParams.getP(), q,
+ dsaParams.getG());
+
+ DSAPublicKey dsaPrivateKey = (DSAPublicKey) pubKey;
+ DSAPublicKeyParameters dsaPrivParms = new DSAPublicKeyParameters(
+ dsaPrivateKey.getY(), bcDSAParams);
+
+ // Ian: Note that if you can get the standard DSA implementation you're
+ // using to not hash its input, you should be able to pass it ((256-bit
+ // value) mod q), (rather than truncating the 256-bit value) and all
+ // should be well.
+ // ref: Interop problems with libotr - DSA signature
+ DSASigner dsaSigner = new DSASigner();
+ dsaSigner.init(false, dsaPrivParms);
+
+ BigInteger bmpi = new BigInteger(1, b);
+ Boolean result = dsaSigner.verifySignature(BigIntegers
+ .asUnsignedByteArray(bmpi.mod(q)), r, s);
+ return result;
+ }
+
+ public String getFingerprint(PublicKey pubKey) throws OtrCryptoException {
+ byte[] b;
+ try {
+ byte[] bRemotePubKey = SerializationUtils.writePublicKey(pubKey);
+
+ if (pubKey.getAlgorithm().equals("DSA")) {
+ byte[] trimmed = new byte[bRemotePubKey.length - 2];
+ System.arraycopy(bRemotePubKey, 2, trimmed, 0, trimmed.length);
+ b = new OtrCryptoEngineImpl().sha1Hash(trimmed);
+ } else
+ b = new OtrCryptoEngineImpl().sha1Hash(bRemotePubKey);
+ } catch (IOException e) {
+ throw new OtrCryptoException(e);
+ }
+ return this.byteArrayToHexString(b);
+ }
+
+ private String byteArrayToHexString(byte in[]) {
+ byte ch = 0x00;
+ int i = 0;
+ if (in == null || in.length <= 0)
+ return null;
+ String pseudo[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+ "A", "B", "C", "D", "E", "F" };
+ StringBuffer out = new StringBuffer(in.length * 2);
+ while (i < in.length) {
+ ch = (byte) (in[i] & 0xF0);
+ ch = (byte) (ch >>> 4);
+ ch = (byte) (ch & 0x0F);
+ out.append(pseudo[(int) ch]);
+ ch = (byte) (in[i] & 0x0F);
+ out.append(pseudo[(int) ch]);
+ i++;
+ }
+
+ String rslt = new String(out);
+ return rslt;
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/crypto/OtrCryptoException.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,12 @@
+package net.java.otr4j.crypto;
+
+import net.java.otr4j.OtrException;
+
+@SuppressWarnings("serial")
+public class OtrCryptoException extends OtrException {
+
+ public OtrCryptoException(Exception e) {
+ super(e);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/OtrInputStream.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,135 @@
+package net.java.otr4j.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+import java.security.spec.DSAPublicKeySpec;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.io.messages.SignatureX;
+
+public class OtrInputStream extends FilterInputStream implements
+ SerializationConstants {
+
+ public OtrInputStream(InputStream in) {
+ super(in);
+ }
+
+ private int readNumber(int length) throws IOException {
+ byte[] b = new byte[length];
+ read(b);
+
+ int value = 0;
+ for (int i = 0; i < b.length; i++) {
+ int shift = (b.length - 1 - i) * 8;
+ value += (b[i] & 0x000000FF) << shift;
+ }
+
+ return value;
+ }
+
+ public int readByte() throws IOException {
+ return readNumber(TYPE_LEN_BYTE);
+ }
+
+ public int readInt() throws IOException {
+ return readNumber(TYPE_LEN_INT);
+ }
+
+ public int readShort() throws IOException {
+ return readNumber(TYPE_LEN_SHORT);
+ }
+
+ public byte[] readCtr() throws IOException {
+ byte[] b = new byte[TYPE_LEN_CTR];
+ read(b);
+ return b;
+ }
+
+ public byte[] readMac() throws IOException {
+ byte[] b = new byte[TYPE_LEN_MAC];
+ read(b);
+ return b;
+ }
+
+ public BigInteger readBigInt() throws IOException {
+ byte[] b = readData();
+ return new BigInteger(1, b);
+ }
+
+ public byte[] readData() throws IOException {
+ int dataLen = readNumber(DATA_LEN);
+ byte[] b = new byte[dataLen];
+ read(b);
+ return b;
+ }
+
+ public PublicKey readPublicKey() throws IOException {
+ int type = readShort();
+ switch (type) {
+ case 0:
+ BigInteger p = readBigInt();
+ BigInteger q = readBigInt();
+ BigInteger g = readBigInt();
+ BigInteger y = readBigInt();
+ DSAPublicKeySpec keySpec = new DSAPublicKeySpec(y, p, q, g);
+ KeyFactory keyFactory;
+ try {
+ keyFactory = KeyFactory.getInstance("DSA");
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException();
+ }
+ try {
+ return keyFactory.generatePublic(keySpec);
+ } catch (InvalidKeySpecException e) {
+ throw new IOException();
+ }
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public DHPublicKey readDHPublicKey() throws IOException {
+ BigInteger gyMpi = readBigInt();
+ try {
+ return new OtrCryptoEngineImpl().getDHPublicKey(gyMpi);
+ } catch (Exception ex) {
+ throw new IOException();
+ }
+ }
+
+ public byte[] readTlvData() throws IOException {
+ int len = readNumber(TYPE_LEN_BYTE);
+
+ byte[] b = new byte[len];
+ in.read(b);
+ return b;
+ }
+
+ public byte[] readSignature(PublicKey pubKey) throws IOException {
+ if (!pubKey.getAlgorithm().equals("DSA"))
+ throw new UnsupportedOperationException();
+
+ DSAPublicKey dsaPubKey = (DSAPublicKey) pubKey;
+ DSAParams dsaParams = dsaPubKey.getParams();
+ byte[] sig = new byte[dsaParams.getQ().bitLength() / 4];
+ read(sig);
+ return sig;
+ }
+
+ public SignatureX readMysteriousX() throws IOException {
+ PublicKey pubKey = readPublicKey();
+ int dhKeyID = readInt();
+ byte[] sig = readSignature(pubKey);
+ return new SignatureX(pubKey, dhKeyID, sig);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/OtrOutputStream.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,140 @@
+package net.java.otr4j.io;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.security.interfaces.DSAParams;
+import java.security.interfaces.DSAPublicKey;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.io.messages.SignatureM;
+import net.java.otr4j.io.messages.MysteriousT;
+import net.java.otr4j.io.messages.SignatureX;
+
+import org.bouncycastle.util.BigIntegers;
+
+public class OtrOutputStream extends FilterOutputStream implements
+ SerializationConstants {
+
+ public OtrOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ private void writeNumber(int value, int length) throws IOException {
+ byte[] b = new byte[length];
+ for (int i = 0; i < length; i++) {
+ int offset = (b.length - 1 - i) * 8;
+ b[i] = (byte) ((value >>> offset) & 0xFF);
+ }
+ write(b);
+ }
+
+ public void writeBigInt(BigInteger bi) throws IOException {
+ byte[] b = BigIntegers.asUnsignedByteArray(bi);
+ writeData(b);
+ }
+
+ public void writeByte(int b) throws IOException {
+ writeNumber(b, TYPE_LEN_BYTE);
+ }
+
+ public void writeData(byte[] b) throws IOException {
+ int len = (b == null || b.length < 0) ? 0 : b.length;
+ writeNumber(len, DATA_LEN);
+ if (len > 0)
+ write(b);
+ }
+
+ public void writeInt(int i) throws IOException {
+ writeNumber(i, TYPE_LEN_INT);
+
+ }
+
+ public void writeShort(int s) throws IOException {
+ writeNumber(s, TYPE_LEN_SHORT);
+
+ }
+
+ public void writeMac(byte[] mac) throws IOException {
+ if (mac == null || mac.length != TYPE_LEN_MAC)
+ throw new IllegalArgumentException();
+
+ write(mac);
+ }
+
+ public void writeCtr(byte[] ctr) throws IOException {
+ if (ctr == null || ctr.length < 1)
+ return;
+
+ int i = 0;
+ while (i < TYPE_LEN_CTR && i < ctr.length) {
+ write(ctr[i]);
+ i++;
+ }
+ }
+
+ public void writeDHPublicKey(DHPublicKey dhPublicKey) throws IOException {
+ byte[] b = BigIntegers.asUnsignedByteArray(dhPublicKey.getY());
+ writeData(b);
+ }
+
+ public void writePublicKey(PublicKey pubKey) throws IOException {
+ if (!(pubKey instanceof DSAPublicKey))
+ throw new UnsupportedOperationException(
+ "Key types other than DSA are not supported at the moment.");
+
+ DSAPublicKey dsaKey = (DSAPublicKey) pubKey;
+
+ writeShort(0);
+
+ DSAParams dsaParams = dsaKey.getParams();
+ writeBigInt(dsaParams.getP());
+ writeBigInt(dsaParams.getQ());
+ writeBigInt(dsaParams.getG());
+ writeBigInt(dsaKey.getY());
+
+ }
+
+ public void writeTlvData(byte[] b) throws IOException {
+ int len = (b == null || b.length < 0) ? 0 : b.length;
+ writeNumber(len, TLV_LEN);
+ if (len > 0)
+ write(b);
+ }
+
+ public void writeSignature(byte[] signature, PublicKey pubKey)
+ throws IOException {
+ if (!pubKey.getAlgorithm().equals("DSA"))
+ throw new UnsupportedOperationException();
+ out.write(signature);
+ }
+
+ public void writeMysteriousX(SignatureX x) throws IOException {
+ writePublicKey(x.longTermPublicKey);
+ writeInt(x.dhKeyID);
+ writeSignature(x.signature, x.longTermPublicKey);
+ }
+
+ public void writeMysteriousX(SignatureM m) throws IOException {
+ writeBigInt(m.localPubKey.getY());
+ writeBigInt(m.remotePubKey.getY());
+ writePublicKey(m.localLongTermPubKey);
+ writeInt(m.keyPairID);
+ }
+
+ public void writeMysteriousT(MysteriousT t) throws IOException {
+ writeShort(t.protocolVersion);
+ writeByte(t.messageType);
+ writeByte(t.flags);
+
+ writeInt(t.senderKeyID);
+ writeInt(t.recipientKeyID);
+ writeDHPublicKey(t.nextDH);
+ writeCtr(t.ctr);
+ writeData(t.encryptedMessage);
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/SerializationConstants.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,29 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io;
+
+/**
+ *
+ * @author George Politis
+ */
+public interface SerializationConstants {
+
+ public static final String HEAD = "?OTR";
+ public static final char HEAD_ENCODED = ':';
+ public static final char HEAD_ERROR = ' ';
+ public static final char HEAD_QUERY_Q = '?';
+ public static final char HEAD_QUERY_V = 'v';
+
+ public static final int TYPE_LEN_BYTE = 1;
+ public static final int TYPE_LEN_SHORT = 2;
+ public static final int TYPE_LEN_INT = 4;
+ public static final int TYPE_LEN_MAC = 20;
+ public static final int TYPE_LEN_CTR = 8;
+
+ public static final int DATA_LEN = TYPE_LEN_INT;
+ public static final int TLV_LEN = TYPE_LEN_SHORT;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/SerializationUtils.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,341 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.util.List;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import org.bouncycastle.util.encoders.Base64;
+
+import net.java.otr4j.io.messages.AbstractEncodedMessage;
+import net.java.otr4j.io.messages.AbstractMessage;
+import net.java.otr4j.io.messages.DHCommitMessage;
+import net.java.otr4j.io.messages.DHKeyMessage;
+import net.java.otr4j.io.messages.DataMessage;
+import net.java.otr4j.io.messages.ErrorMessage;
+import net.java.otr4j.io.messages.MysteriousT;
+import net.java.otr4j.io.messages.PlainTextMessage;
+import net.java.otr4j.io.messages.QueryMessage;
+import net.java.otr4j.io.messages.RevealSignatureMessage;
+import net.java.otr4j.io.messages.SignatureM;
+import net.java.otr4j.io.messages.SignatureMessage;
+import net.java.otr4j.io.messages.SignatureX;
+
+/**
+ *
+ * @author George Politis
+ */
+public class SerializationUtils {
+ // Mysterious X IO.
+ public static SignatureX toMysteriousX(byte[] b) throws IOException {
+ ByteArrayInputStream in = new ByteArrayInputStream(b);
+ OtrInputStream ois = new OtrInputStream(in);
+ SignatureX x = ois.readMysteriousX();
+ ois.close();
+ return x;
+ }
+
+ public static byte[] toByteArray(SignatureX x) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OtrOutputStream oos = new OtrOutputStream(out);
+ oos.writeMysteriousX(x);
+ byte[] b = out.toByteArray();
+ oos.close();
+ return b;
+ }
+
+ // Mysterious M IO.
+ public static byte[] toByteArray(SignatureM m) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OtrOutputStream oos = new OtrOutputStream(out);
+ oos.writeMysteriousX(m);
+ byte[] b = out.toByteArray();
+ oos.close();
+ return b;
+ }
+
+ // Mysterious T IO.
+ public static byte[] toByteArray(MysteriousT t) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OtrOutputStream oos = new OtrOutputStream(out);
+ oos.writeMysteriousT(t);
+ byte[] b = out.toByteArray();
+ out.close();
+ return b;
+ }
+
+ // Basic IO.
+ public static byte[] writeData(byte[] b) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OtrOutputStream oos = new OtrOutputStream(out);
+ oos.writeData(b);
+ byte[] otrb = out.toByteArray();
+ out.close();
+ return otrb;
+ }
+
+ // BigInteger IO.
+ public static byte[] writeMpi(BigInteger bigInt) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OtrOutputStream oos = new OtrOutputStream(out);
+ oos.writeBigInt(bigInt);
+ byte[] b = out.toByteArray();
+ oos.close();
+ return b;
+ }
+
+ public static BigInteger readMpi(byte[] b) throws IOException {
+ ByteArrayInputStream in = new ByteArrayInputStream(b);
+ OtrInputStream ois = new OtrInputStream(in);
+ BigInteger bigint = ois.readBigInt();
+ ois.close();
+ return bigint;
+ }
+
+ // Public Key IO.
+ public static byte[] writePublicKey(PublicKey pubKey) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ OtrOutputStream oos = new OtrOutputStream(out);
+ oos.writePublicKey(pubKey);
+ byte[] b = out.toByteArray();
+ oos.close();
+ return b;
+ }
+
+ // Message IO.
+ public static String toString(AbstractMessage m) throws IOException {
+ StringWriter writer = new StringWriter();
+ writer.write(SerializationConstants.HEAD);
+
+ switch (m.messageType) {
+ case AbstractMessage.MESSAGE_ERROR:
+ ErrorMessage error = (ErrorMessage) m;
+ writer.write(SerializationConstants.HEAD_ERROR);
+ writer.write(error.error);
+ break;
+ case AbstractMessage.MESSAGE_PLAINTEXT:
+ PlainTextMessage plaintxt = (PlainTextMessage) m;
+ writer.write(plaintxt.cleanText);
+ if (plaintxt.versions != null && plaintxt.versions.size() > 0) {
+ writer.write(" \\t \\t\\t\\t\\t \\t \\t \\t ");
+ for (int version : plaintxt.versions) {
+ if (version == 1)
+ writer.write(" \\t\\t \\t ");
+
+ if (version == 2)
+ writer.write(" \\t \\t \\t ");
+ }
+ }
+ break;
+ case AbstractMessage.MESSAGE_QUERY:
+ QueryMessage query = (QueryMessage) m;
+ if (query.versions.size() == 1 && query.versions.get(0) == 1) {
+ writer.write(SerializationConstants.HEAD_QUERY_Q);
+ } else {
+ writer.write(SerializationConstants.HEAD_QUERY_V);
+ for (int version : query.versions)
+ writer.write(String.valueOf(version));
+
+ writer.write(SerializationConstants.HEAD_QUERY_Q);
+ }
+ break;
+ case AbstractEncodedMessage.MESSAGE_DHKEY:
+ case AbstractEncodedMessage.MESSAGE_REVEALSIG:
+ case AbstractEncodedMessage.MESSAGE_SIGNATURE:
+ case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+ case AbstractEncodedMessage.MESSAGE_DATA:
+ ByteArrayOutputStream o = new ByteArrayOutputStream();
+ OtrOutputStream s = new OtrOutputStream(o);
+
+ switch (m.messageType) {
+ case AbstractEncodedMessage.MESSAGE_DHKEY:
+ DHKeyMessage dhkey = (DHKeyMessage) m;
+ s.writeShort(dhkey.protocolVersion);
+ s.writeByte(dhkey.messageType);
+ s.writeDHPublicKey(dhkey.dhPublicKey);
+ break;
+ case AbstractEncodedMessage.MESSAGE_REVEALSIG:
+ RevealSignatureMessage revealsig = (RevealSignatureMessage) m;
+ s.writeShort(revealsig.protocolVersion);
+ s.writeByte(revealsig.messageType);
+ s.writeData(revealsig.revealedKey);
+ s.writeData(revealsig.xEncrypted);
+ s.writeMac(revealsig.xEncryptedMAC);
+ break;
+ case AbstractEncodedMessage.MESSAGE_SIGNATURE:
+ SignatureMessage sig = (SignatureMessage) m;
+ s.writeShort(sig.protocolVersion);
+ s.writeByte(sig.messageType);
+ s.writeData(sig.xEncrypted);
+ s.writeMac(sig.xEncryptedMAC);
+ break;
+ case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+ DHCommitMessage dhcommit = (DHCommitMessage) m;
+ s.writeShort(dhcommit.protocolVersion);
+ s.writeByte(dhcommit.messageType);
+ s.writeData(dhcommit.dhPublicKeyEncrypted);
+ s.writeData(dhcommit.dhPublicKeyHash);
+ break;
+ case AbstractEncodedMessage.MESSAGE_DATA:
+ DataMessage data = (DataMessage) m;
+ s.writeShort(data.protocolVersion);
+ s.writeByte(data.messageType);
+ s.writeByte(data.flags);
+ s.writeInt(data.senderKeyID);
+ s.writeInt(data.recipientKeyID);
+ s.writeDHPublicKey(data.nextDH);
+ s.writeCtr(data.ctr);
+ s.writeData(data.encryptedMessage);
+ s.writeMac(data.mac);
+ s.writeData(data.oldMACKeys);
+ break;
+ }
+
+ writer.write(SerializationConstants.HEAD_ENCODED);
+ writer.write(new String(Base64.encode(o.toByteArray())));
+ writer.write(".");
+ break;
+ default:
+ throw new IOException("Illegal message type.");
+ }
+
+ return writer.toString();
+ }
+
+ static final Pattern patternWhitespace = Pattern
+ .compile("( \\t \\t\\t\\t\\t \\t \\t \\t )( \\t\\t \\t )?( \\t \\t \\t )?");
+
+ public static AbstractMessage toMessage(String s) throws IOException {
+ if (s == null || s.length() <= 1)
+ return null;
+
+ if (s.indexOf(SerializationConstants.HEAD) != 0
+ || s.length() <= SerializationConstants.HEAD.length()) {
+ // Try to detect whitespace tag.
+ final Matcher matcher = patternWhitespace.matcher(s);
+
+ boolean v1 = false;
+ boolean v2 = false;
+ while (matcher.find()) {
+ if (!v1 && matcher.start(2) > -1)
+ v1 = true;
+
+ if (!v2 && matcher.start(3) > -1)
+ v2 = true;
+
+ if (v1 && v2)
+ break;
+ }
+
+ String cleanText = matcher.replaceAll("");
+ List<Integer> versions;
+ if (v1 && v2) {
+ versions = new Vector<Integer>(2);
+ versions.add(0, 1);
+ versions.add(0, 2);
+ } else if (v1) {
+ versions = new Vector<Integer>(1);
+ versions.add(0, 1);
+ } else if (v2) {
+ versions = new Vector<Integer>(1);
+ versions.add(2);
+ } else
+ versions = null;
+
+ return new PlainTextMessage(versions, cleanText);
+ } else {
+ char contentType = s.charAt(SerializationConstants.HEAD.length());
+ String content = s
+ .substring(SerializationConstants.HEAD.length() + 1);
+ switch (contentType) {
+ case SerializationConstants.HEAD_ENCODED:
+ ByteArrayInputStream bin = new ByteArrayInputStream(Base64
+ .decode(content.getBytes()));
+ OtrInputStream otr = new OtrInputStream(bin);
+ // We have an encoded message.
+ int protocolVersion = otr.readShort();
+ int messageType = otr.readByte();
+ switch (messageType) {
+ case AbstractEncodedMessage.MESSAGE_DATA:
+ int flags = otr.readByte();
+ int senderKeyID = otr.readInt();
+ int recipientKeyID = otr.readInt();
+ DHPublicKey nextDH = otr.readDHPublicKey();
+ byte[] ctr = otr.readCtr();
+ byte[] encryptedMessage = otr.readData();
+ byte[] mac = otr.readMac();
+ byte[] oldMacKeys = otr.readMac();
+ return new DataMessage(protocolVersion, flags, senderKeyID,
+ recipientKeyID, nextDH, ctr, encryptedMessage, mac,
+ oldMacKeys);
+ case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+ byte[] dhPublicKeyEncrypted = otr.readData();
+ byte[] dhPublicKeyHash = otr.readData();
+ return new DHCommitMessage(protocolVersion,
+ dhPublicKeyHash, dhPublicKeyEncrypted);
+ case AbstractEncodedMessage.MESSAGE_DHKEY:
+ DHPublicKey dhPublicKey = otr.readDHPublicKey();
+ return new DHKeyMessage(protocolVersion, dhPublicKey);
+ case AbstractEncodedMessage.MESSAGE_REVEALSIG: {
+ byte[] revealedKey = otr.readData();
+ byte[] xEncrypted = otr.readData();
+ byte[] xEncryptedMac = otr.readMac();
+ return new RevealSignatureMessage(protocolVersion,
+ xEncrypted, xEncryptedMac, revealedKey);
+ }
+ case AbstractEncodedMessage.MESSAGE_SIGNATURE: {
+ byte[] xEncryted = otr.readData();
+ byte[] xEncryptedMac = otr.readMac();
+ return new SignatureMessage(protocolVersion, xEncryted,
+ xEncryptedMac);
+ }
+ default:
+ throw new IOException("Illegal message type.");
+ }
+ case SerializationConstants.HEAD_ERROR:
+ return new ErrorMessage(AbstractMessage.MESSAGE_ERROR, content);
+ case SerializationConstants.HEAD_QUERY_V:
+ case SerializationConstants.HEAD_QUERY_Q:
+ List<Integer> versions = new Vector<Integer>();
+ String versionString = null;
+ if (SerializationConstants.HEAD_QUERY_Q == contentType) {
+ versions.add(1);
+ if (content.charAt(0) == 'v') {
+ versionString = content.substring(1, content
+ .indexOf('?'));
+ }
+ } else if (SerializationConstants.HEAD_QUERY_V == contentType) {
+ versionString = content.substring(0, content.indexOf('?'));
+ }
+
+ if (versionString != null) {
+ StringReader sr = new StringReader(versionString);
+ int c;
+ while ((c = sr.read()) != -1)
+ if (!versions.contains(c))
+ versions.add(Integer.parseInt(String
+ .valueOf((char) c)));
+ }
+ QueryMessage query = new QueryMessage(versions);
+ return query;
+ default:
+ throw new IOException("Uknown message type.");
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/AbstractEncodedMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,52 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+/**
+ *
+ * @author George Politis
+ */
+public abstract class AbstractEncodedMessage extends AbstractMessage {
+ // Fields.
+ public int protocolVersion;
+
+ // Ctor.
+ public AbstractEncodedMessage(int messageType, int protocolVersion) {
+ super(messageType);
+ this.protocolVersion = protocolVersion;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + protocolVersion;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AbstractEncodedMessage other = (AbstractEncodedMessage) obj;
+ if (protocolVersion != other.protocolVersion)
+ return false;
+ return true;
+ }
+
+ // Encoded Message Types
+ public static final int MESSAGE_DH_COMMIT = 0x02;
+ public static final int MESSAGE_DATA = 0x03;
+ public static final int MESSAGE_DHKEY = 0x0a;
+ public static final int MESSAGE_REVEALSIG = 0x11;
+ public static final int MESSAGE_SIGNATURE = 0x12;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/AbstractMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,49 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+/**
+ *
+ * @author George Politis
+ */
+public abstract class AbstractMessage {
+ // Fields.
+ public int messageType;
+
+ // Ctor.
+ public AbstractMessage(int messageType) {
+ this.messageType = messageType;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + messageType;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AbstractMessage other = (AbstractMessage) obj;
+ if (messageType != other.messageType)
+ return false;
+ return true;
+ }
+
+ // Unencoded
+ public static final int MESSAGE_ERROR = 0xff;
+ public static final int MESSAGE_QUERY = 0x100;
+ public static final int MESSAGE_PLAINTEXT = 0x102;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/DHCommitMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,55 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.util.Arrays;
+
+/**
+ *
+ * @author George Politis
+ */
+public class DHCommitMessage extends AbstractEncodedMessage {
+
+ // Fields.
+ public byte[] dhPublicKeyEncrypted;
+ public byte[] dhPublicKeyHash;
+
+ // Ctor.
+ public DHCommitMessage(int protocolVersion, byte[] dhPublicKeyHash,
+ byte[] dhPublicKeyEncrypted) {
+ super(MESSAGE_DH_COMMIT, protocolVersion);
+ this.dhPublicKeyEncrypted = dhPublicKeyEncrypted;
+ this.dhPublicKeyHash = dhPublicKeyHash;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(dhPublicKeyEncrypted);
+ result = prime * result + Arrays.hashCode(dhPublicKeyHash);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DHCommitMessage other = (DHCommitMessage) obj;
+ if (!Arrays.equals(dhPublicKeyEncrypted, other.dhPublicKeyEncrypted))
+ return false;
+ if (!Arrays.equals(dhPublicKeyHash, other.dhPublicKeyHash))
+ return false;
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/DHKeyMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,53 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+/**
+ *
+ * @author George Politis
+ */
+public class DHKeyMessage extends AbstractEncodedMessage {
+
+ // Fields.
+ public DHPublicKey dhPublicKey;
+
+ // Ctor.
+ public DHKeyMessage(int protocolVersion, DHPublicKey dhPublicKey) {
+ super(MESSAGE_DHKEY, protocolVersion);
+ this.dhPublicKey = dhPublicKey;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ // TODO: Needs work.
+ result = prime * result
+ + ((dhPublicKey == null) ? 0 : dhPublicKey.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DHKeyMessage other = (DHKeyMessage) obj;
+ if (dhPublicKey == null) {
+ if (other.dhPublicKey != null)
+ return false;
+ } else if (dhPublicKey.getY().compareTo(other.dhPublicKey.getY()) != 0)
+ return false;
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/DataMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,103 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.util.Arrays;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+/**
+ *
+ * @author George Politis
+ */
+public class DataMessage extends AbstractEncodedMessage {
+
+ // Fields.
+ public byte[] mac;
+ public byte[] oldMACKeys;
+
+ public int flags;
+ public int senderKeyID;
+ public int recipientKeyID;
+ public DHPublicKey nextDH;
+ public byte[] ctr;
+ public byte[] encryptedMessage;
+
+ // Ctor.
+ public DataMessage(int protocolVersion, int flags, int senderKeyID,
+ int recipientKeyID, DHPublicKey nextDH, byte[] ctr,
+ byte[] encryptedMessage, byte[] mac, byte[] oldMacKeys) {
+ super(MESSAGE_DATA, protocolVersion);
+
+ this.flags = flags;
+ this.senderKeyID = senderKeyID;
+ this.recipientKeyID = recipientKeyID;
+ this.nextDH = nextDH;
+ this.ctr = ctr;
+ this.encryptedMessage = encryptedMessage;
+ this.mac = mac;
+ this.oldMACKeys = oldMacKeys;
+ }
+
+ public DataMessage(MysteriousT t, byte[] mac, byte[] oldMacKeys) {
+ this(t.protocolVersion, t.flags, t.senderKeyID, t.recipientKeyID,
+ t.nextDH, t.ctr, t.encryptedMessage, mac, oldMacKeys);
+ }
+
+ // Methods.
+ public MysteriousT getT() {
+ return new MysteriousT(protocolVersion, flags, senderKeyID,
+ recipientKeyID, nextDH, ctr, encryptedMessage);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(ctr);
+ result = prime * result + Arrays.hashCode(encryptedMessage);
+ result = prime * result + flags;
+ result = prime * result + Arrays.hashCode(mac);
+ // TODO: Needs work.
+ result = prime * result + ((nextDH == null) ? 0 : nextDH.hashCode());
+ result = prime * result + Arrays.hashCode(oldMACKeys);
+ result = prime * result + recipientKeyID;
+ result = prime * result + senderKeyID;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DataMessage other = (DataMessage) obj;
+ if (!Arrays.equals(ctr, other.ctr))
+ return false;
+ if (!Arrays.equals(encryptedMessage, other.encryptedMessage))
+ return false;
+ if (flags != other.flags)
+ return false;
+ if (!Arrays.equals(mac, other.mac))
+ return false;
+ if (nextDH == null) {
+ if (other.nextDH != null)
+ return false;
+ } else if (!nextDH.equals(other.nextDH))
+ return false;
+ if (!Arrays.equals(oldMACKeys, other.oldMACKeys))
+ return false;
+ if (recipientKeyID != other.recipientKeyID)
+ return false;
+ if (senderKeyID != other.senderKeyID)
+ return false;
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/ErrorMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,48 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+/**
+ *
+ * @author George Politis
+ */
+public class ErrorMessage extends AbstractMessage {
+ // Fields.
+ public String error;
+
+ // Ctor.
+ public ErrorMessage(int messageType, String error) {
+ super(messageType);
+ this.error = error;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + ((error == null) ? 0 : error.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ErrorMessage other = (ErrorMessage) obj;
+ if (error == null) {
+ if (other.error != null)
+ return false;
+ } else if (!error.equals(other.error))
+ return false;
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/MysteriousT.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,82 @@
+package net.java.otr4j.io.messages;
+
+import java.util.Arrays;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+public class MysteriousT {
+ // Fields.
+ public int protocolVersion;
+ public int messageType;
+ public int flags;
+ public int senderKeyID;
+ public int recipientKeyID;
+ public DHPublicKey nextDH;
+ public byte[] ctr;
+ public byte[] encryptedMessage;
+
+ // Ctor.
+ public MysteriousT(int protocolVersion, int flags, int senderKeyID,
+ int recipientKeyID, DHPublicKey nextDH, byte[] ctr,
+ byte[] encryptedMessage) {
+
+ this.protocolVersion = protocolVersion;
+ this.messageType = AbstractEncodedMessage.MESSAGE_DATA;
+ this.flags = flags;
+ this.senderKeyID = senderKeyID;
+ this.recipientKeyID = recipientKeyID;
+ this.nextDH = nextDH;
+ this.ctr = ctr;
+ this.encryptedMessage = encryptedMessage;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ // TODO: Needs work.
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(ctr);
+ result = prime * result + Arrays.hashCode(encryptedMessage);
+ result = prime * result + flags;
+ result = prime * result + messageType;
+ result = prime * result + ((nextDH == null) ? 0 : nextDH.hashCode());
+ result = prime * result + protocolVersion;
+ result = prime * result + recipientKeyID;
+ result = prime * result + senderKeyID;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // TODO: Needs work.
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MysteriousT other = (MysteriousT) obj;
+ if (!Arrays.equals(ctr, other.ctr))
+ return false;
+ if (!Arrays.equals(encryptedMessage, other.encryptedMessage))
+ return false;
+ if (flags != other.flags)
+ return false;
+ if (messageType != other.messageType)
+ return false;
+ if (nextDH == null) {
+ if (other.nextDH != null)
+ return false;
+ } else if (!nextDH.equals(other.nextDH))
+ return false;
+ if (protocolVersion != other.protocolVersion)
+ return false;
+ if (recipientKeyID != other.recipientKeyID)
+ return false;
+ if (senderKeyID != other.senderKeyID)
+ return false;
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/PlainTextMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,52 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.util.List;
+
+/**
+ *
+ * @author George Politis
+ */
+public class PlainTextMessage extends QueryMessage {
+ // Fields.
+ public String cleanText;
+
+ // Ctor.
+ public PlainTextMessage(List<Integer> versions, String cleanText) {
+ super(MESSAGE_PLAINTEXT, versions);
+ this.cleanText = cleanText;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result
+ + ((cleanText == null) ? 0 : cleanText.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ PlainTextMessage other = (PlainTextMessage) obj;
+ if (cleanText == null) {
+ if (other.cleanText != null)
+ return false;
+ } else if (!cleanText.equals(other.cleanText))
+ return false;
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/QueryMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,56 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.util.List;
+
+/**
+ *
+ * @author George Politis
+ */
+public class QueryMessage extends AbstractMessage {
+ // Fields.
+ public List<Integer> versions;
+
+ // Ctor.
+ protected QueryMessage(int messageType, List<Integer> versions) {
+ super(messageType);
+ this.versions = versions;
+ }
+
+ public QueryMessage(List<Integer> versions) {
+ this(MESSAGE_QUERY, versions);
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result
+ + ((versions == null) ? 0 : versions.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ QueryMessage other = (QueryMessage) obj;
+ if (versions == null) {
+ if (other.versions != null)
+ return false;
+ } else if (!versions.equals(other.versions))
+ return false;
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/RevealSignatureMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,49 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.util.Arrays;
+
+/**
+ *
+ * @author George Politis
+ */
+public class RevealSignatureMessage extends SignatureMessage {
+ // Fields.
+ public byte[] revealedKey;
+
+ // Ctor.
+ public RevealSignatureMessage(int protocolVersion, byte[] xEncrypted,
+ byte[] xEncryptedMAC, byte[] revealedKey) {
+ super(MESSAGE_REVEALSIG, protocolVersion, xEncrypted, xEncryptedMAC);
+
+ this.revealedKey = revealedKey;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(revealedKey);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ RevealSignatureMessage other = (RevealSignatureMessage) obj;
+ if (!Arrays.equals(revealedKey, other.revealedKey))
+ return false;
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/SignatureM.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,82 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.security.PublicKey;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+/**
+ *
+ * @author George Politis
+ */
+public class SignatureM {
+ // Fields.
+ public DHPublicKey localPubKey;
+ public DHPublicKey remotePubKey;
+ public PublicKey localLongTermPubKey;
+ public int keyPairID;
+
+ // Ctor.
+ public SignatureM(DHPublicKey localPubKey, DHPublicKey remotePublicKey,
+ PublicKey localLongTermPublicKey, int keyPairID) {
+
+ this.localPubKey = localPubKey;
+ this.remotePubKey = remotePublicKey;
+ this.localLongTermPubKey = localLongTermPublicKey;
+ this.keyPairID = keyPairID;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + keyPairID;
+ // TODO: Needs work.
+ result = prime
+ * result
+ + ((localLongTermPubKey == null) ? 0 : localLongTermPubKey
+ .hashCode());
+ result = prime * result
+ + ((localPubKey == null) ? 0 : localPubKey.hashCode());
+ result = prime * result
+ + ((remotePubKey == null) ? 0 : remotePubKey.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // TODO: Needs work.
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SignatureM other = (SignatureM) obj;
+ if (keyPairID != other.keyPairID)
+ return false;
+ if (localLongTermPubKey == null) {
+ if (other.localLongTermPubKey != null)
+ return false;
+ } else if (!localLongTermPubKey.equals(other.localLongTermPubKey))
+ return false;
+ if (localPubKey == null) {
+ if (other.localPubKey != null)
+ return false;
+ } else if (!localPubKey.equals(other.localPubKey))
+ return false;
+ if (remotePubKey == null) {
+ if (other.remotePubKey != null)
+ return false;
+ } else if (!remotePubKey.equals(other.remotePubKey))
+ return false;
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/SignatureMessage.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,82 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.io.SerializationUtils;
+
+/**
+ *
+ * @author George Politis
+ */
+public class SignatureMessage extends AbstractEncodedMessage {
+ // Fields.
+ public byte[] xEncrypted;
+ public byte[] xEncryptedMAC;
+
+ // Ctor.
+ protected SignatureMessage(int messageType, int protocolVersion,
+ byte[] xEncrypted, byte[] xEncryptedMAC) {
+ super(messageType, protocolVersion);
+ this.xEncrypted = xEncrypted;
+ this.xEncryptedMAC = xEncryptedMAC;
+ }
+
+ public SignatureMessage(int protocolVersion, byte[] xEncrypted,
+ byte[] xEncryptedMAC) {
+ this(MESSAGE_SIGNATURE, protocolVersion, xEncrypted, xEncryptedMAC);
+ }
+
+ // Memthods.
+ public byte[] decrypt(byte[] key) throws OtrException {
+ return new OtrCryptoEngineImpl().aesDecrypt(key, null, xEncrypted);
+ }
+
+ public boolean verify(byte[] key) throws OtrException {
+ // Hash the key.
+ byte[] xbEncrypted;
+ try {
+ xbEncrypted = SerializationUtils.writeData(xEncrypted);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ byte[] xEncryptedMAC = new OtrCryptoEngineImpl().sha256Hmac160(
+ xbEncrypted, key);
+ // Verify signature.
+ return Arrays.equals(xEncryptedMAC, xEncryptedMAC);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Arrays.hashCode(xEncrypted);
+ result = prime * result + Arrays.hashCode(xEncryptedMAC);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SignatureMessage other = (SignatureMessage) obj;
+ if (!Arrays.equals(xEncrypted, other.xEncrypted))
+ return false;
+ if (!Arrays.equals(xEncryptedMAC, other.xEncryptedMAC))
+ return false;
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/io/messages/SignatureX.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,67 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.io.messages;
+
+import java.security.PublicKey;
+import java.util.Arrays;
+
+/**
+ *
+ * @author George Politis
+ */
+public class SignatureX {
+ // Fields.
+ public PublicKey longTermPublicKey;
+ public int dhKeyID;
+ public byte[] signature;
+
+ // Ctor.
+ public SignatureX(PublicKey ourLongTermPublicKey, int ourKeyID,
+ byte[] signature) {
+ this.longTermPublicKey = ourLongTermPublicKey;
+ this.dhKeyID = ourKeyID;
+ this.signature = signature;
+ }
+
+ // Methods.
+ @Override
+ public int hashCode() {
+ // TODO: Needs work.
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + dhKeyID;
+ result = prime
+ * result
+ + ((longTermPublicKey == null) ? 0 : longTermPublicKey
+ .hashCode());
+ result = prime * result + Arrays.hashCode(signature);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // TODO: Needs work.
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ SignatureX other = (SignatureX) obj;
+ if (dhKeyID != other.dhKeyID)
+ return false;
+ if (longTermPublicKey == null) {
+ if (other.longTermPublicKey != null)
+ return false;
+ } else if (!longTermPublicKey.equals(other.longTermPublicKey))
+ return false;
+ if (!Arrays.equals(signature, other.signature))
+ return false;
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/AuthContext.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,55 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.session;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.PublicKey;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.io.messages.AbstractMessage;
+
+/**
+ *
+ * @author George Politis
+ */
+interface AuthContext {
+
+ public static final int NONE = 0;
+ public static final int AWAITING_DHKEY = 1;
+ public static final int AWAITING_REVEALSIG = 2;
+ public static final int AWAITING_SIG = 3;
+ public static final int V1_SETUP = 4;
+ public static final byte C_START = (byte) 0x01;
+ public static final byte M1_START = (byte) 0x02;
+ public static final byte M2_START = (byte) 0x03;
+ public static final byte M1p_START = (byte) 0x04;
+ public static final byte M2p_START = (byte) 0x05;
+
+ public abstract void reset();
+
+ public abstract boolean getIsSecure();
+
+ public abstract DHPublicKey getRemoteDHPublicKey();
+
+ public abstract KeyPair getLocalDHKeyPair() throws OtrException;
+
+ public abstract BigInteger getS() throws OtrException;
+
+ public abstract void handleReceivingMessage(AbstractMessage m)
+ throws OtrException;
+
+ public abstract void startV2Auth() throws OtrException;
+
+ public abstract void respondV2Auth() throws OtrException;
+
+ public abstract PublicKey getRemoteLongTermPublicKey();
+
+ public abstract KeyPair getLocalLongTermKeyPair();
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/AuthContextImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,766 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.session;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.Vector;
+import java.util.logging.Logger;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.crypto.OtrCryptoEngine;
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.io.SerializationUtils;
+import net.java.otr4j.io.messages.DHCommitMessage;
+import net.java.otr4j.io.messages.DHKeyMessage;
+import net.java.otr4j.io.messages.AbstractEncodedMessage;
+import net.java.otr4j.io.messages.AbstractMessage;
+import net.java.otr4j.io.messages.SignatureM;
+import net.java.otr4j.io.messages.SignatureX;
+import net.java.otr4j.io.messages.QueryMessage;
+import net.java.otr4j.io.messages.RevealSignatureMessage;
+import net.java.otr4j.io.messages.SignatureMessage;
+
+/**
+ *
+ * @author George Politis
+ */
+class AuthContextImpl implements AuthContext {
+
+ public AuthContextImpl(Session session) {
+ this.setSession(session);
+ this.reset();
+ }
+
+ private Session session;
+
+ private int authenticationState;
+ private byte[] r;
+
+ private DHPublicKey remoteDHPublicKey;
+ private byte[] remoteDHPublicKeyEncrypted;
+ private byte[] remoteDHPublicKeyHash;
+
+ private KeyPair localDHKeyPair;
+ private int localDHPrivateKeyID;
+ private byte[] localDHPublicKeyBytes;
+ private byte[] localDHPublicKeyHash;
+ private byte[] localDHPublicKeyEncrypted;
+
+ private BigInteger s;
+ private byte[] c;
+ private byte[] m1;
+ private byte[] m2;
+ private byte[] cp;
+ private byte[] m1p;
+ private byte[] m2p;
+
+ private KeyPair localLongTermKeyPair;
+ private Boolean isSecure = false;
+ private int protocolVersion;
+
+ private int getProtocolVersion() {
+ return this.protocolVersion;
+ }
+
+ private void setProtocolVersion(int protoVersion) {
+ this.protocolVersion = protoVersion;
+ }
+
+ private static Logger logger = Logger.getLogger(AuthContextImpl.class
+ .getName());
+
+ class MessageFactory {
+
+ private QueryMessage getQueryMessage() {
+ Vector<Integer> versions = new Vector<Integer>();
+ versions.add(2);
+ return new QueryMessage(versions);
+ }
+
+ private DHCommitMessage getDHCommitMessage() throws OtrException {
+ return new DHCommitMessage(getProtocolVersion(),
+ getLocalDHPublicKeyHash(), getLocalDHPublicKeyEncrypted());
+ }
+
+ private DHKeyMessage getDHKeyMessage() throws OtrException {
+ return new DHKeyMessage(getProtocolVersion(),
+ (DHPublicKey) getLocalDHKeyPair().getPublic());
+ }
+
+ private RevealSignatureMessage getRevealSignatureMessage()
+ throws OtrException {
+ try {
+ SignatureM m = new SignatureM((DHPublicKey) getLocalDHKeyPair()
+ .getPublic(), getRemoteDHPublicKey(),
+ getLocalLongTermKeyPair().getPublic(),
+ getLocalDHKeyPairID());
+
+ OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl();
+ byte[] mhash = otrCryptoEngine.sha256Hmac(SerializationUtils
+ .toByteArray(m), getM1());
+ byte[] signature = otrCryptoEngine.sign(mhash,
+ getLocalLongTermKeyPair().getPrivate());
+
+ SignatureX mysteriousX = new SignatureX(
+ getLocalLongTermKeyPair().getPublic(),
+ getLocalDHKeyPairID(), signature);
+ byte[] xEncrypted = otrCryptoEngine.aesEncrypt(getC(), null,
+ SerializationUtils.toByteArray(mysteriousX));
+
+ byte[] tmp = SerializationUtils.writeData(xEncrypted);
+
+ byte[] xEncryptedHash = otrCryptoEngine.sha256Hmac160(tmp,
+ getM2());
+ return new RevealSignatureMessage(getProtocolVersion(),
+ xEncrypted, xEncryptedHash, getR());
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ }
+
+ private SignatureMessage getSignatureMessage() throws OtrException {
+ SignatureM m = new SignatureM((DHPublicKey) getLocalDHKeyPair()
+ .getPublic(), getRemoteDHPublicKey(),
+ getLocalLongTermKeyPair().getPublic(),
+ getLocalDHKeyPairID());
+
+ OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl();
+ byte[] mhash;
+ try {
+ mhash = otrCryptoEngine.sha256Hmac(SerializationUtils
+ .toByteArray(m), getM1p());
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ byte[] signature = otrCryptoEngine.sign(mhash,
+ getLocalLongTermKeyPair().getPrivate());
+
+ SignatureX mysteriousX = new SignatureX(getLocalLongTermKeyPair()
+ .getPublic(), getLocalDHKeyPairID(), signature);
+
+ byte[] xEncrypted;
+ try {
+ xEncrypted = otrCryptoEngine.aesEncrypt(getCp(), null,
+ SerializationUtils.toByteArray(mysteriousX));
+ byte[] tmp = SerializationUtils.writeData(xEncrypted);
+ byte[] xEncryptedHash = otrCryptoEngine.sha256Hmac160(tmp,
+ getM2p());
+ return new SignatureMessage(getProtocolVersion(), xEncrypted,
+ xEncryptedHash);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ }
+ }
+
+ private MessageFactory messageFactory = new MessageFactory();
+
+ public void reset() {
+ logger.finest("Resetting authentication state.");
+ authenticationState = AuthContext.NONE;
+ r = null;
+
+ remoteDHPublicKey = null;
+ remoteDHPublicKeyEncrypted = null;
+ remoteDHPublicKeyHash = null;
+
+ localDHKeyPair = null;
+ localDHPrivateKeyID = 1;
+ localDHPublicKeyBytes = null;
+ localDHPublicKeyHash = null;
+ localDHPublicKeyEncrypted = null;
+
+ s = null;
+ c = m1 = m2 = cp = m1p = m2p = null;
+
+ localLongTermKeyPair = null;
+ protocolVersion = 0;
+ setIsSecure(false);
+ }
+
+ private void setIsSecure(Boolean isSecure) {
+ this.isSecure = isSecure;
+ }
+
+ public boolean getIsSecure() {
+ return isSecure;
+ }
+
+ private void setAuthenticationState(int authenticationState) {
+ this.authenticationState = authenticationState;
+ }
+
+ private int getAuthenticationState() {
+ return authenticationState;
+ }
+
+ private byte[] getR() {
+ if (r == null) {
+ logger.finest("Picking random key r.");
+ r = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH];
+ new Random().nextBytes(r);
+ }
+ return r;
+ }
+
+ private void setRemoteDHPublicKey(DHPublicKey dhPublicKey) {
+ // Verifies that Alice's gy is a legal value (2 <= gy <= modulus-2)
+ if (dhPublicKey.getY().compareTo(OtrCryptoEngine.MODULUS_MINUS_TWO) > 0) {
+ throw new IllegalArgumentException(
+ "Illegal D-H Public Key value, Ignoring message.");
+ } else if (dhPublicKey.getY().compareTo(OtrCryptoEngine.BIGINTEGER_TWO) < 0) {
+ throw new IllegalArgumentException(
+ "Illegal D-H Public Key value, Ignoring message.");
+ }
+ logger.finest("Received D-H Public Key is a legal value.");
+
+ this.remoteDHPublicKey = dhPublicKey;
+ }
+
+ public DHPublicKey getRemoteDHPublicKey() {
+ return remoteDHPublicKey;
+ }
+
+ private void setRemoteDHPublicKeyEncrypted(byte[] remoteDHPublicKeyEncrypted) {
+ logger.finest("Storing encrypted remote public key.");
+ this.remoteDHPublicKeyEncrypted = remoteDHPublicKeyEncrypted;
+ }
+
+ private byte[] getRemoteDHPublicKeyEncrypted() {
+ return remoteDHPublicKeyEncrypted;
+ }
+
+ private void setRemoteDHPublicKeyHash(byte[] remoteDHPublicKeyHash) {
+ logger.finest("Storing encrypted remote public key hash.");
+ this.remoteDHPublicKeyHash = remoteDHPublicKeyHash;
+ }
+
+ private byte[] getRemoteDHPublicKeyHash() {
+ return remoteDHPublicKeyHash;
+ }
+
+ public KeyPair getLocalDHKeyPair() throws OtrException {
+ if (localDHKeyPair == null) {
+ localDHKeyPair = new OtrCryptoEngineImpl().generateDHKeyPair();
+ logger.finest("Generated local D-H key pair.");
+ }
+ return localDHKeyPair;
+ }
+
+ private int getLocalDHKeyPairID() {
+ return localDHPrivateKeyID;
+ }
+
+ private byte[] getLocalDHPublicKeyHash() throws OtrException {
+ if (localDHPublicKeyHash == null) {
+ localDHPublicKeyHash = new OtrCryptoEngineImpl()
+ .sha256Hash(getLocalDHPublicKeyBytes());
+ logger.finest("Hashed local D-H public key.");
+ }
+ return localDHPublicKeyHash;
+ }
+
+ private byte[] getLocalDHPublicKeyEncrypted() throws OtrException {
+ if (localDHPublicKeyEncrypted == null) {
+ localDHPublicKeyEncrypted = new OtrCryptoEngineImpl().aesEncrypt(
+ getR(), null, getLocalDHPublicKeyBytes());
+ logger.finest("Encrypted our D-H public key.");
+ }
+ return localDHPublicKeyEncrypted;
+ }
+
+ public BigInteger getS() throws OtrException {
+ if (s == null) {
+ s = new OtrCryptoEngineImpl().generateSecret(this
+ .getLocalDHKeyPair().getPrivate(), this
+ .getRemoteDHPublicKey());
+ logger.finest("Generated shared secret.");
+ }
+ return s;
+ }
+
+ private byte[] getC() throws OtrException {
+ if (c != null)
+ return c;
+
+ byte[] h2 = h2(C_START);
+ ByteBuffer buff = ByteBuffer.wrap(h2);
+ this.c = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH];
+ buff.get(this.c);
+ logger.finest("Computed c.");
+ return c;
+
+ }
+
+ private byte[] getM1() throws OtrException {
+ if (m1 != null)
+ return m1;
+
+ byte[] h2 = h2(M1_START);
+ ByteBuffer buff = ByteBuffer.wrap(h2);
+ byte[] m1 = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH];
+ buff.get(m1);
+ logger.finest("Computed m1.");
+ this.m1 = m1;
+ return m1;
+ }
+
+ private byte[] getM2() throws OtrException {
+ if (m2 != null)
+ return m2;
+
+ byte[] h2 = h2(M2_START);
+ ByteBuffer buff = ByteBuffer.wrap(h2);
+ byte[] m2 = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH];
+ buff.get(m2);
+ logger.finest("Computed m2.");
+ this.m2 = m2;
+ return m2;
+ }
+
+ private byte[] getCp() throws OtrException {
+ if (cp != null)
+ return cp;
+
+ byte[] h2 = h2(C_START);
+ ByteBuffer buff = ByteBuffer.wrap(h2);
+ byte[] cp = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH];
+ buff.position(OtrCryptoEngine.AES_KEY_BYTE_LENGTH);
+ buff.get(cp);
+ logger.finest("Computed c'.");
+ this.cp = cp;
+ return cp;
+ }
+
+ private byte[] getM1p() throws OtrException {
+ if (m1p != null)
+ return m1p;
+
+ byte[] h2 = h2(M1p_START);
+ ByteBuffer buff = ByteBuffer.wrap(h2);
+ byte[] m1p = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH];
+ buff.get(m1p);
+ this.m1p = m1p;
+ logger.finest("Computed m1'.");
+ return m1p;
+ }
+
+ private byte[] getM2p() throws OtrException {
+ if (m2p != null)
+ return m2p;
+
+ byte[] h2 = h2(M2p_START);
+ ByteBuffer buff = ByteBuffer.wrap(h2);
+ byte[] m2p = new byte[OtrCryptoEngine.SHA256_HMAC_KEY_BYTE_LENGTH];
+ buff.get(m2p);
+ this.m2p = m2p;
+ logger.finest("Computed m2'.");
+ return m2p;
+ }
+
+ public KeyPair getLocalLongTermKeyPair() {
+ if (localLongTermKeyPair == null) {
+ localLongTermKeyPair = getSession().getLocalKeyPair();
+ }
+ return localLongTermKeyPair;
+ }
+
+ private byte[] h2(byte b) throws OtrException {
+ byte[] secbytes;
+ try {
+ secbytes = SerializationUtils.writeMpi(getS());
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ int len = secbytes.length + 1;
+ ByteBuffer buff = ByteBuffer.allocate(len);
+ buff.put(b);
+ buff.put(secbytes);
+ byte[] sdata = buff.array();
+ return new OtrCryptoEngineImpl().sha256Hash(sdata);
+ }
+
+ private byte[] getLocalDHPublicKeyBytes() throws OtrException {
+ if (localDHPublicKeyBytes == null) {
+ try {
+ this.localDHPublicKeyBytes = SerializationUtils
+ .writeMpi(((DHPublicKey) getLocalDHKeyPair()
+ .getPublic()).getY());
+
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ }
+ return localDHPublicKeyBytes;
+ }
+
+ public void handleReceivingMessage(AbstractMessage m) throws OtrException {
+
+ switch (m.messageType) {
+ case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+ handleDHCommitMessage((DHCommitMessage) m);
+ break;
+ case AbstractEncodedMessage.MESSAGE_DHKEY:
+ handleDHKeyMessage((DHKeyMessage) m);
+ break;
+ case AbstractEncodedMessage.MESSAGE_REVEALSIG:
+ handleRevealSignatureMessage((RevealSignatureMessage) m);
+ break;
+ case AbstractEncodedMessage.MESSAGE_SIGNATURE:
+ handleSignatureMessage((SignatureMessage) m);
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private void handleSignatureMessage(SignatureMessage m) throws OtrException {
+ Session session = getSession();
+ SessionID sessionID = session.getSessionID();
+ logger.finest(sessionID.getAccountID()
+ + " received a signature message from " + sessionID.getUserID()
+ + " throught " + sessionID.getProtocolName() + ".");
+ if (!session.getSessionPolicy().getAllowV2()) {
+ logger.finest("Policy does not allow OTRv2, ignoring message.");
+ return;
+ }
+
+ switch (this.getAuthenticationState()) {
+ case AWAITING_SIG:
+ // Verify MAC.
+ if (!m.verify(this.getM2p())) {
+ logger
+ .finest("Signature MACs are not equal, ignoring message.");
+ return;
+ }
+
+ // Decrypt X.
+ byte[] remoteXDecrypted = m.decrypt(this.getCp());
+ SignatureX remoteX;
+ try {
+ remoteX = SerializationUtils.toMysteriousX(remoteXDecrypted);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ // Compute signature.
+ PublicKey remoteLongTermPublicKey = remoteX.longTermPublicKey;
+ SignatureM remoteM = new SignatureM(this.getRemoteDHPublicKey(),
+ (DHPublicKey) this.getLocalDHKeyPair().getPublic(),
+ remoteLongTermPublicKey, remoteX.dhKeyID);
+ OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl();
+ // Verify signature.
+ byte[] signature;
+ try {
+ signature = otrCryptoEngine.sha256Hmac(SerializationUtils
+ .toByteArray(remoteM), this.getM1p());
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ if (!otrCryptoEngine.verify(signature, remoteLongTermPublicKey,
+ remoteX.signature)) {
+ logger.finest("Signature verification failed.");
+ return;
+ }
+
+ this.setIsSecure(true);
+ this.setRemoteLongTermPublicKey(remoteLongTermPublicKey);
+ break;
+ default:
+ logger
+ .finest("We were not expecting a signature, ignoring message.");
+ return;
+ }
+ }
+
+ private void handleRevealSignatureMessage(RevealSignatureMessage m)
+ throws OtrException {
+ Session session = getSession();
+ SessionID sessionID = session.getSessionID();
+ logger.finest(sessionID.getAccountID()
+ + " received a reveal signature message from "
+ + sessionID.getUserID() + " throught "
+ + sessionID.getProtocolName() + ".");
+
+ if (!session.getSessionPolicy().getAllowV2()) {
+ logger.finest("Policy does not allow OTRv2, ignoring message.");
+ return;
+ }
+
+ switch (this.getAuthenticationState()) {
+ case AWAITING_REVEALSIG:
+ // Use the received value of r to decrypt the value of gx
+ // received
+ // in the D-H Commit Message, and verify the hash therein.
+ // Decrypt
+ // the encrypted signature, and verify the signature and the
+ // MACs.
+ // If everything checks out:
+
+ // * Reply with a Signature Message.
+ // * Transition authstate to AUTHSTATE_NONE.
+ // * Transition msgstate to MSGSTATE_ENCRYPTED.
+ // * TODO If there is a recent stored message, encrypt it and
+ // send
+ // it as a Data Message.
+
+ OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl();
+ // Uses r to decrypt the value of gx sent earlier
+ byte[] remoteDHPublicKeyDecrypted = otrCryptoEngine.aesDecrypt(
+ m.revealedKey, null, this.getRemoteDHPublicKeyEncrypted());
+
+ // Verifies that HASH(gx) matches the value sent earlier
+ byte[] remoteDHPublicKeyHash = otrCryptoEngine
+ .sha256Hash(remoteDHPublicKeyDecrypted);
+ if (!Arrays.equals(remoteDHPublicKeyHash, this
+ .getRemoteDHPublicKeyHash())) {
+ logger.finest("Hashes don't match, ignoring message.");
+ return;
+ }
+
+ // Verifies that Bob's gx is a legal value (2 <= gx <=
+ // modulus-2)
+ BigInteger remoteDHPublicKeyMpi;
+ try {
+ remoteDHPublicKeyMpi = SerializationUtils
+ .readMpi(remoteDHPublicKeyDecrypted);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ this.setRemoteDHPublicKey(otrCryptoEngine
+ .getDHPublicKey(remoteDHPublicKeyMpi));
+
+ // Verify received Data.
+ if (!m.verify(this.getM2())) {
+ logger
+ .finest("Signature MACs are not equal, ignoring message.");
+ return;
+ }
+
+ // Decrypt X.
+ byte[] remoteXDecrypted = m.decrypt(this.getC());
+ SignatureX remoteX;
+ try {
+ remoteX = SerializationUtils.toMysteriousX(remoteXDecrypted);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ // Compute signature.
+ PublicKey remoteLongTermPublicKey = remoteX.longTermPublicKey;
+ SignatureM remoteM = new SignatureM(this.getRemoteDHPublicKey(),
+ (DHPublicKey) this.getLocalDHKeyPair().getPublic(),
+ remoteLongTermPublicKey, remoteX.dhKeyID);
+
+ // Verify signature.
+ byte[] signature;
+ try {
+ signature = otrCryptoEngine.sha256Hmac(SerializationUtils
+ .toByteArray(remoteM), this.getM1());
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ if (!otrCryptoEngine.verify(signature, remoteLongTermPublicKey,
+ remoteX.signature)) {
+ logger.finest("Signature verification failed.");
+ return;
+ }
+
+ logger.finest("Signature verification succeeded.");
+
+ this.setAuthenticationState(AuthContext.NONE);
+ this.setIsSecure(true);
+ this.setRemoteLongTermPublicKey(remoteLongTermPublicKey);
+ getSession().injectMessage(messageFactory.getSignatureMessage());
+ break;
+ default:
+ logger.finest("Ignoring message.");
+ break;
+ }
+ }
+
+ private void handleDHKeyMessage(DHKeyMessage m) throws OtrException {
+ Session session = getSession();
+ SessionID sessionID = session.getSessionID();
+ logger.finest(sessionID.getAccountID()
+ + " received a D-H key message from " + sessionID.getUserID()
+ + " throught " + sessionID.getProtocolName() + ".");
+
+ if (!session.getSessionPolicy().getAllowV2()) {
+ logger.finest("If ALLOW_V2 is not set, ignore this message.");
+ return;
+ }
+
+ switch (this.getAuthenticationState()) {
+ case AWAITING_DHKEY:
+ // Reply with a Reveal Signature Message and transition
+ // authstate to
+ // AUTHSTATE_AWAITING_SIG
+ this.setRemoteDHPublicKey(m.dhPublicKey);
+ this.setAuthenticationState(AuthContext.AWAITING_SIG);
+ getSession().injectMessage(
+ messageFactory.getRevealSignatureMessage());
+ logger.finest("Sent Reveal Signature.");
+ break;
+ case AWAITING_SIG:
+
+ if (m.dhPublicKey.getY().equals(this.getRemoteDHPublicKey().getY())) {
+ // If this D-H Key message is the same the one you received
+ // earlier (when you entered AUTHSTATE_AWAITING_SIG):
+ // Retransmit
+ // your Reveal Signature Message.
+ getSession().injectMessage(
+ messageFactory.getRevealSignatureMessage());
+ logger.finest("Resent Reveal Signature.");
+ } else {
+ // Otherwise: Ignore the message.
+ logger.finest("Ignoring message.");
+ }
+ break;
+ default:
+ // Ignore the message
+ break;
+ }
+ }
+
+ private void handleDHCommitMessage(DHCommitMessage m) throws OtrException {
+ Session session = getSession();
+ SessionID sessionID = session.getSessionID();
+ logger.finest(sessionID.getAccountID()
+ + " received a D-H commit message from "
+ + sessionID.getUserID() + " throught "
+ + sessionID.getProtocolName() + ".");
+
+ if (!session.getSessionPolicy().getAllowV2()) {
+ logger.finest("ALLOW_V2 is not set, ignore this message.");
+ return;
+ }
+
+ switch (this.getAuthenticationState()) {
+ case NONE:
+ // Reply with a D-H Key Message, and transition authstate to
+ // AUTHSTATE_AWAITING_REVEALSIG.
+ this.reset();
+ this.setProtocolVersion(2);
+ this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted);
+ this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash);
+ this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG);
+ getSession().injectMessage(messageFactory.getDHKeyMessage());
+ logger.finest("Sent D-H key.");
+ break;
+
+ case AWAITING_DHKEY:
+ // This is the trickiest transition in the whole protocol. It
+ // indicates that you have already sent a D-H Commit message to
+ // your
+ // correspondent, but that he either didn't receive it, or just
+ // didn't receive it yet, and has sent you one as well. The
+ // symmetry
+ // will be broken by comparing the hashed gx you sent in your
+ // D-H
+ // Commit Message with the one you received, considered as
+ // 32-byte
+ // unsigned big-endian values.
+ BigInteger ourHash = new BigInteger(1, this
+ .getLocalDHPublicKeyHash());
+ BigInteger theirHash = new BigInteger(1, m.dhPublicKeyHash);
+
+ if (theirHash.compareTo(ourHash) == -1) {
+ // Ignore the incoming D-H Commit message, but resend your
+ // D-H
+ // Commit message.
+ getSession().injectMessage(messageFactory.getDHCommitMessage());
+ logger
+ .finest("Ignored the incoming D-H Commit message, but resent our D-H Commit message.");
+ } else {
+ // *Forget* your old gx value that you sent (encrypted)
+ // earlier,
+ // and pretend you're in AUTHSTATE_NONE; i.e. reply with a
+ // D-H
+ // Key Message, and transition authstate to
+ // AUTHSTATE_AWAITING_REVEALSIG.
+ this.reset();
+ this.setProtocolVersion(2);
+ this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted);
+ this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash);
+ this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG);
+ getSession().injectMessage(messageFactory.getDHKeyMessage());
+ logger
+ .finest("Forgot our old gx value that we sent (encrypted) earlier, and pretended we're in AUTHSTATE_NONE -> Sent D-H key.");
+ }
+ break;
+
+ case AWAITING_REVEALSIG:
+ // Retransmit your D-H Key Message (the same one as you sent
+ // when
+ // you entered AUTHSTATE_AWAITING_REVEALSIG). Forget the old D-H
+ // Commit message, and use this new one instead.
+ this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted);
+ this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash);
+ getSession().injectMessage(messageFactory.getDHKeyMessage());
+ logger.finest("Sent D-H key.");
+ break;
+ case AWAITING_SIG:
+ // Reply with a new D-H Key message, and transition authstate to
+ // AUTHSTATE_AWAITING_REVEALSIG
+ this.reset();
+ this.setRemoteDHPublicKeyEncrypted(m.dhPublicKeyEncrypted);
+ this.setRemoteDHPublicKeyHash(m.dhPublicKeyHash);
+ this.setAuthenticationState(AuthContext.AWAITING_REVEALSIG);
+ getSession().injectMessage(messageFactory.getDHKeyMessage());
+ logger.finest("Sent D-H key.");
+ break;
+ case V1_SETUP:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public void startV2Auth() throws OtrException {
+ logger
+ .finest("Starting Authenticated Key Exchange, sending query message");
+ getSession().injectMessage(messageFactory.getQueryMessage());
+ }
+
+ public void respondV2Auth() throws OtrException {
+ logger.finest("Responding to Query Message");
+ this.reset();
+ this.setProtocolVersion(2);
+ this.setAuthenticationState(AuthContext.AWAITING_DHKEY);
+ logger.finest("Sending D-H Commit.");
+ getSession().injectMessage(messageFactory.getDHCommitMessage());
+ }
+
+ private void setSession(Session session) {
+ this.session = session;
+ }
+
+ private Session getSession() {
+ return session;
+ }
+
+ private PublicKey remoteLongTermPublicKey;
+
+ public PublicKey getRemoteLongTermPublicKey() {
+ return remoteLongTermPublicKey;
+ }
+
+ private void setRemoteLongTermPublicKey(PublicKey pubKey) {
+ this.remoteLongTermPublicKey = pubKey;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/Session.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,42 @@
+package net.java.otr4j.session;
+
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.List;
+
+import net.java.otr4j.OtrEngineListener;
+import net.java.otr4j.OtrException;
+import net.java.otr4j.OtrPolicy;
+import net.java.otr4j.io.messages.AbstractMessage;
+import net.java.otr4j.session.SessionImpl.TLV;
+
+public interface Session {
+
+ public abstract SessionStatus getSessionStatus();
+
+ public abstract SessionID getSessionID();
+
+ public abstract void injectMessage(AbstractMessage m) throws OtrException;
+
+ public abstract KeyPair getLocalKeyPair();
+
+ public abstract OtrPolicy getSessionPolicy();
+
+ public abstract String transformReceiving(String content)
+ throws OtrException;
+
+ public abstract String transformSending(String content, List<TLV> tlvs)
+ throws OtrException;
+
+ public abstract void startSession() throws OtrException;
+
+ public abstract void endSession() throws OtrException;
+
+ public abstract void refreshSession() throws OtrException;
+
+ public abstract PublicKey getRemotePublicKey();
+
+ public abstract void addOtrEngineListener(OtrEngineListener l);
+
+ public abstract void removeOtrEngineListener(OtrEngineListener l);
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/SessionID.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,70 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.session;
+
+/**
+ *
+ * @author George Politis
+ *
+ */
+public final class SessionID {
+
+ public SessionID(String accountID, String userID, String protocolName) {
+ this.setAccountID(accountID);
+ this.setUserID(userID);
+ this.setProtocolName(protocolName);
+ }
+
+ private String accountID;
+ private String userID;
+ private String protocolName;
+ public static final SessionID Empty = new SessionID(null, null, null);
+
+ public void setAccountID(String accountID) {
+ this.accountID = accountID;
+ }
+
+ public String getAccountID() {
+ return accountID;
+ }
+
+ private void setUserID(String userID) {
+ this.userID = userID;
+ }
+
+ public String getUserID() {
+ return userID;
+ }
+
+ private void setProtocolName(String protocolName) {
+ this.protocolName = protocolName;
+ }
+
+ public String getProtocolName() {
+ return protocolName;
+ }
+
+ public String toString() {
+ return this.getAccountID() + "_" + this.getProtocolName() + "_"
+ + this.getUserID();
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (obj == null || obj.getClass() != this.getClass())
+ return false;
+
+ SessionID sessionID = (SessionID) obj;
+
+ return this.toString().equals(sessionID.toString());
+ }
+
+ public int hashCode() {
+ return this.toString().hashCode();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/SessionImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,795 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+
+package net.java.otr4j.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.security.KeyPair;
+import java.security.PublicKey;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+import java.util.logging.Logger;
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.OtrEngineHost;
+import net.java.otr4j.OtrEngineListener;
+import net.java.otr4j.OtrException;
+import net.java.otr4j.OtrPolicy;
+import net.java.otr4j.crypto.OtrCryptoEngine;
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.io.OtrInputStream;
+import net.java.otr4j.io.OtrOutputStream;
+import net.java.otr4j.io.SerializationConstants;
+import net.java.otr4j.io.SerializationUtils;
+import net.java.otr4j.io.messages.DataMessage;
+import net.java.otr4j.io.messages.AbstractEncodedMessage;
+import net.java.otr4j.io.messages.ErrorMessage;
+import net.java.otr4j.io.messages.AbstractMessage;
+import net.java.otr4j.io.messages.MysteriousT;
+import net.java.otr4j.io.messages.PlainTextMessage;
+import net.java.otr4j.io.messages.QueryMessage;
+
+/**
+ *
+ * @author George Politis
+ */
+public class SessionImpl implements Session {
+
+ /**
+ *
+ * @author George Politis
+ *
+ */
+ class TLV {
+ public TLV(int type, byte[] value) {
+ this.setType(type);
+ this.setValue(value);
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setValue(byte[] value) {
+ this.value = value;
+ }
+
+ public byte[] getValue() {
+ return value;
+ }
+
+ private int type;
+ private byte[] value;
+ }
+
+ private SessionID sessionID;
+ private OtrEngineHost listener;
+ private SessionStatus sessionStatus;
+ private AuthContext authContext;
+ private SessionKeys[][] sessionKeys;
+ private Vector<byte[]> oldMacKeys;
+ private static Logger logger = Logger
+ .getLogger(SessionImpl.class.getName());
+
+ public SessionImpl(SessionID sessionID, OtrEngineHost listener) {
+
+ this.setSessionID(sessionID);
+ this.setListener(listener);
+
+ // client application calls OtrEngine.getSessionStatus()
+ // -> create new session if it does not exist, end up here
+ // -> setSessionStatus() fires statusChangedEvent
+ // -> client application calls OtrEngine.getSessionStatus()
+ this.sessionStatus = SessionStatus.PLAINTEXT;
+ }
+
+ private SessionKeys getEncryptionSessionKeys() {
+ logger.finest("Getting encryption keys");
+ return getSessionKeysByIndex(SessionKeys.Previous, SessionKeys.Current);
+ }
+
+ private SessionKeys getMostRecentSessionKeys() {
+ logger.finest("Getting most recent keys.");
+ return getSessionKeysByIndex(SessionKeys.Current, SessionKeys.Current);
+ }
+
+ private SessionKeys getSessionKeysByID(int localKeyID, int remoteKeyID) {
+ logger
+ .finest("Searching for session keys with (localKeyID, remoteKeyID) = ("
+ + localKeyID + "," + remoteKeyID + ")");
+
+ for (int i = 0; i < getSessionKeys().length; i++) {
+ for (int j = 0; j < getSessionKeys()[i].length; j++) {
+ SessionKeys current = getSessionKeysByIndex(i, j);
+ if (current.getLocalKeyID() == localKeyID
+ && current.getRemoteKeyID() == remoteKeyID) {
+ logger.finest("Matching keys found.");
+ return current;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private SessionKeys getSessionKeysByIndex(int localKeyIndex,
+ int remoteKeyIndex) {
+ if (getSessionKeys()[localKeyIndex][remoteKeyIndex] == null)
+ getSessionKeys()[localKeyIndex][remoteKeyIndex] = new SessionKeysImpl(
+ localKeyIndex, remoteKeyIndex);
+
+ return getSessionKeys()[localKeyIndex][remoteKeyIndex];
+ }
+
+ private void rotateRemoteSessionKeys(DHPublicKey pubKey)
+ throws OtrException {
+
+ logger.finest("Rotating remote keys.");
+ SessionKeys sess1 = getSessionKeysByIndex(SessionKeys.Current,
+ SessionKeys.Previous);
+ if (sess1.getIsUsedReceivingMACKey()) {
+ logger
+ .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it.");
+ getOldMacKeys().add(sess1.getReceivingMACKey());
+ }
+
+ SessionKeys sess2 = getSessionKeysByIndex(SessionKeys.Previous,
+ SessionKeys.Previous);
+ if (sess2.getIsUsedReceivingMACKey()) {
+ logger
+ .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it.");
+ getOldMacKeys().add(sess2.getReceivingMACKey());
+ }
+
+ SessionKeys sess3 = getSessionKeysByIndex(SessionKeys.Current,
+ SessionKeys.Current);
+ sess1
+ .setRemoteDHPublicKey(sess3.getRemoteKey(), sess3
+ .getRemoteKeyID());
+
+ SessionKeys sess4 = getSessionKeysByIndex(SessionKeys.Previous,
+ SessionKeys.Current);
+ sess2
+ .setRemoteDHPublicKey(sess4.getRemoteKey(), sess4
+ .getRemoteKeyID());
+
+ sess3.setRemoteDHPublicKey(pubKey, sess3.getRemoteKeyID() + 1);
+ sess4.setRemoteDHPublicKey(pubKey, sess4.getRemoteKeyID() + 1);
+ }
+
+ private void rotateLocalSessionKeys() throws OtrException {
+
+ logger.finest("Rotating local keys.");
+ SessionKeys sess1 = getSessionKeysByIndex(SessionKeys.Previous,
+ SessionKeys.Current);
+ if (sess1.getIsUsedReceivingMACKey()) {
+ logger
+ .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it.");
+ getOldMacKeys().add(sess1.getReceivingMACKey());
+ }
+
+ SessionKeys sess2 = getSessionKeysByIndex(SessionKeys.Previous,
+ SessionKeys.Previous);
+ if (sess2.getIsUsedReceivingMACKey()) {
+ logger
+ .finest("Detected used Receiving MAC key. Adding to old MAC keys to reveal it.");
+ getOldMacKeys().add(sess2.getReceivingMACKey());
+ }
+
+ SessionKeys sess3 = getSessionKeysByIndex(SessionKeys.Current,
+ SessionKeys.Current);
+ sess1.setLocalPair(sess3.getLocalPair(), sess3.getLocalKeyID());
+ SessionKeys sess4 = getSessionKeysByIndex(SessionKeys.Current,
+ SessionKeys.Previous);
+ sess2.setLocalPair(sess4.getLocalPair(), sess4.getLocalKeyID());
+
+ KeyPair newPair = new OtrCryptoEngineImpl().generateDHKeyPair();
+ sess3.setLocalPair(newPair, sess3.getLocalKeyID() + 1);
+ sess4.setLocalPair(newPair, sess4.getLocalKeyID() + 1);
+ }
+
+ private byte[] collectOldMacKeys() {
+ logger.finest("Collecting old MAC keys to be revealed.");
+ int len = 0;
+ for (int i = 0; i < getOldMacKeys().size(); i++)
+ len += getOldMacKeys().get(i).length;
+
+ ByteBuffer buff = ByteBuffer.allocate(len);
+ for (int i = 0; i < getOldMacKeys().size(); i++)
+ buff.put(getOldMacKeys().get(i));
+
+ getOldMacKeys().clear();
+ return buff.array();
+ }
+
+ private void setSessionStatus(SessionStatus sessionStatus)
+ throws OtrException {
+
+ if (sessionStatus == this.sessionStatus)
+ return;
+
+ switch (sessionStatus) {
+ case ENCRYPTED:
+ AuthContext auth = this.getAuthContext();
+ logger.finest("Setting most recent session keys from auth.");
+ for (int i = 0; i < this.getSessionKeys()[0].length; i++) {
+ SessionKeys current = getSessionKeysByIndex(0, i);
+ current.setLocalPair(auth.getLocalDHKeyPair(), 1);
+ current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1);
+ current.setS(auth.getS());
+ }
+
+ KeyPair nextDH = new OtrCryptoEngineImpl().generateDHKeyPair();
+ for (int i = 0; i < this.getSessionKeys()[1].length; i++) {
+ SessionKeys current = getSessionKeysByIndex(1, i);
+ current.setRemoteDHPublicKey(auth.getRemoteDHPublicKey(), 1);
+ current.setLocalPair(nextDH, 2);
+ }
+
+ this.setRemotePublicKey(auth.getRemoteLongTermPublicKey());
+
+ auth.reset();
+ break;
+ }
+
+ this.sessionStatus = sessionStatus;
+
+ for (OtrEngineListener l : this.listeners)
+ l.sessionStatusChanged(getSessionID());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.java.otr4j.session.ISession#getSessionStatus()
+ */
+
+ public SessionStatus getSessionStatus() {
+ return sessionStatus;
+ }
+
+ private void setSessionID(SessionID sessionID) {
+ this.sessionID = sessionID;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.java.otr4j.session.ISession#getSessionID()
+ */
+ public SessionID getSessionID() {
+ return sessionID;
+ }
+
+ private void setListener(OtrEngineHost listener) {
+ this.listener = listener;
+ }
+
+ private OtrEngineHost getListener() {
+ return listener;
+ }
+
+ private SessionKeys[][] getSessionKeys() {
+ if (sessionKeys == null)
+ sessionKeys = new SessionKeys[2][2];
+ return sessionKeys;
+ }
+
+ private AuthContext getAuthContext() {
+ if (authContext == null)
+ authContext = new AuthContextImpl(this);
+ return authContext;
+ }
+
+ private Vector<byte[]> getOldMacKeys() {
+ if (oldMacKeys == null)
+ oldMacKeys = new Vector<byte[]>();
+ return oldMacKeys;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * net.java.otr4j.session.ISession#handleReceivingMessage(java.lang.String)
+ */
+ public String transformReceiving(String msgText) throws OtrException {
+ OtrPolicy policy = getSessionPolicy();
+ if (!policy.getAllowV1() && !policy.getAllowV2()) {
+ logger
+ .finest("Policy does not allow neither V1 not V2, ignoring message.");
+ return msgText;
+ }
+
+ AbstractMessage m;
+ try {
+ m = SerializationUtils.toMessage(msgText);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ switch (m.messageType) {
+ case AbstractEncodedMessage.MESSAGE_DATA:
+ return handleDataMessage((DataMessage) m);
+ case AbstractMessage.MESSAGE_ERROR:
+ handleErrorMessage((ErrorMessage) m);
+ return null;
+ case AbstractMessage.MESSAGE_PLAINTEXT:
+ return handlePlainTextMessage((PlainTextMessage) m);
+ case AbstractMessage.MESSAGE_QUERY:
+ handleQueryMessage((QueryMessage) m);
+ return null;
+ case AbstractEncodedMessage.MESSAGE_DH_COMMIT:
+ case AbstractEncodedMessage.MESSAGE_DHKEY:
+ case AbstractEncodedMessage.MESSAGE_REVEALSIG:
+ case AbstractEncodedMessage.MESSAGE_SIGNATURE:
+ AuthContext auth = this.getAuthContext();
+ auth.handleReceivingMessage(m);
+
+ if (auth.getIsSecure()) {
+ this.setSessionStatus(SessionStatus.ENCRYPTED);
+ logger.finest("Gone Secure.");
+ }
+ return null;
+ default:
+ throw new UnsupportedOperationException(
+ "Received an uknown message type.");
+ }
+ }
+
+ private void handleQueryMessage(QueryMessage queryMessage)
+ throws OtrException {
+ logger.finest(getSessionID().getAccountID()
+ + " received a query message from "
+ + getSessionID().getUserID() + " throught "
+ + getSessionID().getProtocolName() + ".");
+
+ setSessionStatus(SessionStatus.PLAINTEXT);
+
+ OtrPolicy policy = getSessionPolicy();
+ if (queryMessage.versions.contains(2) && policy.getAllowV2()) {
+ logger.finest("Query message with V2 support found.");
+ getAuthContext().respondV2Auth();
+ } else if (queryMessage.versions.contains(1) && policy.getAllowV1()) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private void handleErrorMessage(ErrorMessage errorMessage)
+ throws OtrException {
+ logger.finest(getSessionID().getAccountID()
+ + " received an error message from "
+ + getSessionID().getUserID() + " throught "
+ + getSessionID().getUserID() + ".");
+
+ getListener().showError(this.getSessionID(), errorMessage.error);
+
+ OtrPolicy policy = getSessionPolicy();
+ if (policy.getErrorStartAKE()) {
+ logger.finest("Error message starts AKE.");
+ Vector<Integer> versions = new Vector<Integer>();
+ if (policy.getAllowV1())
+ versions.add(1);
+
+ if (policy.getAllowV2())
+ versions.add(2);
+
+ logger.finest("Sending Query");
+ injectMessage(new QueryMessage(versions));
+ }
+ }
+
+ private String handleDataMessage(DataMessage data) throws OtrException {
+ logger.finest(getSessionID().getAccountID()
+ + " received a data message from " + getSessionID().getUserID()
+ + ".");
+
+ switch (this.getSessionStatus()) {
+ case ENCRYPTED:
+ logger
+ .finest("Message state is ENCRYPTED. Trying to decrypt message.");
+
+ // Find matching session keys.
+ int senderKeyID = data.senderKeyID;
+ int receipientKeyID = data.recipientKeyID;
+ SessionKeys matchingKeys = this.getSessionKeysByID(receipientKeyID,
+ senderKeyID);
+
+ if (matchingKeys == null) {
+ logger.finest("No matching keys found.");
+ return null;
+ }
+
+ // Verify received MAC with a locally calculated MAC.
+ logger
+ .finest("Transforming T to byte[] to calculate it's HmacSHA1.");
+
+ byte[] serializedT;
+ try {
+ serializedT = SerializationUtils.toByteArray(data.getT());
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl();
+
+ byte[] computedMAC = otrCryptoEngine.sha1Hmac(serializedT,
+ matchingKeys.getReceivingMACKey(),
+ SerializationConstants.TYPE_LEN_MAC);
+
+ if (!Arrays.equals(computedMAC, data.mac)) {
+ logger.finest("MAC verification failed, ignoring message");
+ return null;
+ }
+
+ logger.finest("Computed HmacSHA1 value matches sent one.");
+
+ // Mark this MAC key as old to be revealed.
+ matchingKeys.setIsUsedReceivingMACKey(true);
+
+ matchingKeys.setReceivingCtr(data.ctr);
+
+ byte[] dmc = otrCryptoEngine.aesDecrypt(matchingKeys
+ .getReceivingAESKey(), matchingKeys.getReceivingCtr(),
+ data.encryptedMessage);
+ String decryptedMsgContent;
+ try {
+ // Expect bytes to be text encoded in UTF-8.
+ decryptedMsgContent = new String(dmc, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new OtrException(e);
+ }
+
+ logger.finest("Decrypted message: \"" + decryptedMsgContent + "\"");
+
+ // Rotate keys if necessary.
+ SessionKeys mostRecent = this.getMostRecentSessionKeys();
+ if (mostRecent.getLocalKeyID() == receipientKeyID)
+ this.rotateLocalSessionKeys();
+
+ if (mostRecent.getRemoteKeyID() == senderKeyID)
+ this.rotateRemoteSessionKeys(data.nextDH);
+
+ // Handle TLVs
+ List<TLV> tlvs = null;
+ int tlvIndex = decryptedMsgContent.indexOf((char) 0x0);
+ if (tlvIndex > -1) {
+ decryptedMsgContent = decryptedMsgContent
+ .substring(0, tlvIndex);
+ tlvIndex++;
+ byte[] tlvsb = new byte[dmc.length - tlvIndex];
+ System.arraycopy(dmc, tlvIndex, tlvsb, 0, tlvsb.length);
+
+ tlvs = new Vector<TLV>();
+ ByteArrayInputStream tin = new ByteArrayInputStream(tlvsb);
+ while (tin.available() > 0) {
+ int type;
+ byte[] tdata;
+ OtrInputStream eois = new OtrInputStream(tin);
+ try {
+ type = eois.readShort();
+ tdata = eois.readTlvData();
+ eois.close();
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ tlvs.add(new TLV(type, tdata));
+ }
+ }
+ if (tlvs != null && tlvs.size() > 0) {
+ for (TLV tlv : tlvs) {
+ switch (tlv.getType()) {
+ case 1:
+ this.setSessionStatus(SessionStatus.FINISHED);
+ return null;
+ default:
+ return decryptedMsgContent;
+ }
+ }
+ }
+
+ return decryptedMsgContent;
+
+ case FINISHED:
+ case PLAINTEXT:
+ getListener().showWarning(this.getSessionID(),
+ "Unreadable encrypted message was received.");
+
+ injectMessage(new ErrorMessage(AbstractMessage.MESSAGE_ERROR,
+ "You sent me an unreadable encrypted message.."));
+ break;
+ }
+
+ return null;
+ }
+
+ public void injectMessage(AbstractMessage m) throws OtrException {
+ String msg;
+ try {
+ msg = SerializationUtils.toString(m);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ getListener().injectMessage(getSessionID(), msg);
+ }
+
+ private String handlePlainTextMessage(PlainTextMessage plainTextMessage)
+ throws OtrException {
+ logger.finest(getSessionID().getAccountID()
+ + " received a plaintext message from "
+ + getSessionID().getUserID() + " throught "
+ + getSessionID().getProtocolName() + ".");
+
+ OtrPolicy policy = getSessionPolicy();
+ List<Integer> versions = plainTextMessage.versions;
+ if (versions == null || versions.size() < 1) {
+ logger
+ .finest("Received plaintext message without the whitespace tag.");
+ switch (this.getSessionStatus()) {
+ case ENCRYPTED:
+ case FINISHED:
+ // Display the message to the user, but warn him that the
+ // message was received unencrypted.
+ getListener().showWarning(this.getSessionID(),
+ "The message was received unencrypted.");
+ return plainTextMessage.cleanText;
+ case PLAINTEXT:
+ // Simply display the message to the user. If
+ // REQUIRE_ENCRYPTION
+ // is set, warn him that the message was received
+ // unencrypted.
+ if (policy.getRequireEncryption()) {
+ getListener().showWarning(this.getSessionID(),
+ "The message was received unencrypted.");
+ }
+ return plainTextMessage.cleanText;
+ }
+ } else {
+ logger
+ .finest("Received plaintext message with the whitespace tag.");
+ switch (this.getSessionStatus()) {
+ case ENCRYPTED:
+ case FINISHED:
+ // Remove the whitespace tag and display the message to the
+ // user, but warn him that the message was received
+ // unencrypted.
+ getListener().showWarning(this.getSessionID(),
+ "The message was received unencrypted.");
+ case PLAINTEXT:
+ // Remove the whitespace tag and display the message to the
+ // user. If REQUIRE_ENCRYPTION is set, warn him that the
+ // message
+ // was received unencrypted.
+ if (policy.getRequireEncryption())
+ getListener().showWarning(this.getSessionID(),
+ "The message was received unencrypted.");
+ }
+
+ if (policy.getWhitespaceStartAKE()) {
+ logger.finest("WHITESPACE_START_AKE is set");
+
+ if (plainTextMessage.versions.contains(2)
+ && policy.getAllowV2()) {
+ logger.finest("V2 tag found.");
+ getAuthContext().respondV2Auth();
+ } else if (plainTextMessage.versions.contains(1)
+ && policy.getAllowV1()) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ return plainTextMessage.cleanText;
+ }
+
+ // Retransmit last sent message. Spec document does not mention where or
+ // when that should happen, must check libotr code.
+ private String lastSentMessage;
+
+ public String transformSending(String msgText, List<TLV> tlvs)
+ throws OtrException {
+
+ switch (this.getSessionStatus()) {
+ case PLAINTEXT:
+ if (getSessionPolicy().getRequireEncryption()) {
+ this.lastSentMessage = msgText;
+ this.startSession();
+ } else
+ // TODO this does not precisly behave according to
+ // specification.
+ return msgText;
+ case ENCRYPTED:
+ this.lastSentMessage = msgText;
+ logger.finest(getSessionID().getAccountID()
+ + " sends an encrypted message to "
+ + getSessionID().getUserID() + " throught "
+ + getSessionID().getProtocolName() + ".");
+
+ // Get encryption keys.
+ SessionKeys encryptionKeys = this.getEncryptionSessionKeys();
+ int senderKeyID = encryptionKeys.getLocalKeyID();
+ int receipientKeyID = encryptionKeys.getRemoteKeyID();
+
+ // Increment CTR.
+ encryptionKeys.incrementSendingCtr();
+ byte[] ctr = encryptionKeys.getSendingCtr();
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ if (msgText != null && msgText.length() > 0)
+ try {
+ out.write(msgText.getBytes("UTF8"));
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ // Append tlvs
+ if (tlvs != null && tlvs.size() > 0) {
+ out.write((byte) 0x00);
+
+ OtrOutputStream eoos = new OtrOutputStream(out);
+ for (TLV tlv : tlvs) {
+ try {
+ eoos.writeShort(tlv.type);
+ eoos.writeTlvData(tlv.value);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ }
+ }
+
+ OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl();
+
+ byte[] data = out.toByteArray();
+ // Encrypt message.
+ logger
+ .finest("Encrypting message with keyids (localKeyID, remoteKeyID) = ("
+ + senderKeyID + ", " + receipientKeyID + ")");
+ byte[] encryptedMsg = otrCryptoEngine.aesEncrypt(encryptionKeys
+ .getSendingAESKey(), ctr, data);
+
+ // Get most recent keys to get the next D-H public key.
+ SessionKeys mostRecentKeys = this.getMostRecentSessionKeys();
+ DHPublicKey nextDH = (DHPublicKey) mostRecentKeys.getLocalPair()
+ .getPublic();
+
+ // Calculate T.
+ MysteriousT t = new MysteriousT(2, 0, senderKeyID, receipientKeyID,
+ nextDH, ctr, encryptedMsg);
+
+ // Calculate T hash.
+ byte[] sendingMACKey = encryptionKeys.getSendingMACKey();
+
+ logger
+ .finest("Transforming T to byte[] to calculate it's HmacSHA1.");
+ byte[] serializedT;
+ try {
+ serializedT = SerializationUtils.toByteArray(t);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+
+ byte[] mac = otrCryptoEngine.sha1Hmac(serializedT, sendingMACKey,
+ SerializationConstants.TYPE_LEN_MAC);
+
+ // Get old MAC keys to be revealed.
+ byte[] oldKeys = this.collectOldMacKeys();
+ DataMessage m = new DataMessage(t, mac, oldKeys);
+
+ try {
+ return SerializationUtils.toString(m);
+ } catch (IOException e) {
+ throw new OtrException(e);
+ }
+ case FINISHED:
+ this.lastSentMessage = msgText;
+ getListener()
+ .showError(
+ sessionID,
+ "Your message to "
+ + sessionID.getUserID()
+ + " was not sent. Either end your private conversation, or restart it.");
+ return null;
+ default:
+ logger.finest("Uknown message state, not processing.");
+ return msgText;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.java.otr4j.session.ISession#startSession()
+ */
+ public void startSession() throws OtrException {
+ if (this.getSessionStatus() == SessionStatus.ENCRYPTED)
+ return;
+
+ if (!getSessionPolicy().getAllowV2())
+ throw new UnsupportedOperationException();
+
+ this.getAuthContext().startV2Auth();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.java.otr4j.session.ISession#endSession()
+ */
+ public void endSession() throws OtrException {
+ SessionStatus status = this.getSessionStatus();
+ switch (status) {
+ case ENCRYPTED:
+ Vector<TLV> tlvs = new Vector<TLV>();
+ tlvs.add(new TLV(1, null));
+
+ String msg = this.transformSending(null, tlvs);
+ getListener().injectMessage(getSessionID(), msg);
+ this.setSessionStatus(SessionStatus.PLAINTEXT);
+ break;
+ case FINISHED:
+ this.setSessionStatus(SessionStatus.PLAINTEXT);
+ break;
+ case PLAINTEXT:
+ return;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see net.java.otr4j.session.ISession#refreshSession()
+ */
+ public void refreshSession() throws OtrException {
+ this.endSession();
+ this.startSession();
+ }
+
+ private PublicKey remotePublicKey;
+
+ private void setRemotePublicKey(PublicKey pubKey) {
+ this.remotePublicKey = pubKey;
+ }
+
+ public PublicKey getRemotePublicKey() {
+ return remotePublicKey;
+ }
+
+ private List<OtrEngineListener> listeners = new Vector<OtrEngineListener>();
+
+ public void addOtrEngineListener(OtrEngineListener l) {
+ synchronized (listeners) {
+ if (!listeners.contains(l))
+ listeners.add(l);
+ }
+
+ }
+
+ public void removeOtrEngineListener(OtrEngineListener l) {
+ synchronized (listeners) {
+ listeners.remove(l);
+ }
+ }
+
+ public OtrPolicy getSessionPolicy() {
+ return getListener().getSessionPolicy(getSessionID());
+ }
+
+ public KeyPair getLocalKeyPair() {
+ return getListener().getKeyPair(this.getSessionID());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/SessionKeys.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,54 @@
+package net.java.otr4j.session;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.OtrException;
+
+interface SessionKeys {
+
+ public static final int Previous = 0;
+ public static final int Current = 1;
+ public static final byte HIGH_SEND_BYTE = (byte) 0x01;
+ public static final byte HIGH_RECEIVE_BYTE = (byte) 0x02;
+ public static final byte LOW_SEND_BYTE = (byte) 0x02;
+ public static final byte LOW_RECEIVE_BYTE = (byte) 0x01;
+
+ public abstract void setLocalPair(KeyPair keyPair, int localPairKeyID);
+
+ public abstract void setRemoteDHPublicKey(DHPublicKey pubKey,
+ int remoteKeyID);
+
+ public abstract void incrementSendingCtr();
+
+ public abstract byte[] getSendingCtr();
+
+ public abstract byte[] getReceivingCtr();
+
+ public abstract void setReceivingCtr(byte[] ctr);
+
+ public abstract byte[] getSendingAESKey() throws OtrException;
+
+ public abstract byte[] getReceivingAESKey() throws OtrException;
+
+ public abstract byte[] getSendingMACKey() throws OtrException;
+
+ public abstract byte[] getReceivingMACKey() throws OtrException;
+
+ public abstract void setS(BigInteger s);
+
+ public abstract void setIsUsedReceivingMACKey(Boolean isUsedReceivingMACKey);
+
+ public abstract Boolean getIsUsedReceivingMACKey();
+
+ public abstract int getLocalKeyID();
+
+ public abstract int getRemoteKeyID();
+
+ public abstract DHPublicKey getRemoteKey();
+
+ public abstract KeyPair getLocalPair();
+
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/SessionKeysImpl.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,240 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.session;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import javax.crypto.interfaces.DHPublicKey;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.crypto.OtrCryptoEngine;
+import net.java.otr4j.crypto.OtrCryptoEngineImpl;
+import net.java.otr4j.io.SerializationUtils;
+
+/**
+ *
+ * @author George Politis
+ */
+class SessionKeysImpl implements SessionKeys {
+
+ private static Logger logger = Logger.getLogger(SessionKeysImpl.class
+ .getName());
+ private String keyDescription;
+
+ public SessionKeysImpl(int localKeyIndex, int remoteKeyIndex) {
+ if (localKeyIndex == 0)
+ keyDescription = "(Previous local, ";
+ else
+ keyDescription = "(Most recent local, ";
+
+ if (remoteKeyIndex == 0)
+ keyDescription += "Previous remote)";
+ else
+ keyDescription += "Most recent remote)";
+
+ }
+
+ public void setLocalPair(KeyPair keyPair, int localPairKeyID) {
+ this.localPair = keyPair;
+ this.setLocalKeyID(localPairKeyID);
+ logger.finest(keyDescription + " current local key ID: "
+ + this.getLocalKeyID());
+ this.reset();
+ }
+
+ public void setRemoteDHPublicKey(DHPublicKey pubKey, int remoteKeyID) {
+ this.setRemoteKey(pubKey);
+ this.setRemoteKeyID(remoteKeyID);
+ logger.finest(keyDescription + " current remote key ID: "
+ + this.getRemoteKeyID());
+ this.reset();
+ }
+
+ private byte[] sendingCtr = new byte[16];
+ private byte[] receivingCtr = new byte[16];
+
+ public void incrementSendingCtr() {
+ logger.finest("Incrementing counter for (localkeyID, remoteKeyID) = ("
+ + getLocalKeyID() + "," + getRemoteKeyID() + ")");
+ // logger.debug("Counter prior increament: " +
+ // Utils.dump(sendingCtr,
+ // true, 16));
+ for (int i = 7; i >= 0; i--)
+ if (++sendingCtr[i] != 0)
+ break;
+ // logger.debug("Counter after increament: " +
+ // Utils.dump(sendingCtr,
+ // true, 16));
+ }
+
+ public byte[] getSendingCtr() {
+ return sendingCtr;
+ }
+
+ public byte[] getReceivingCtr() {
+ return receivingCtr;
+ }
+
+ public void setReceivingCtr(byte[] ctr) {
+ for (int i = 0; i < ctr.length; i++)
+ receivingCtr[i] = ctr[i];
+ }
+
+ private void reset() {
+ logger.finest("Resetting " + keyDescription + " session keys.");
+ Arrays.fill(this.sendingCtr, (byte) 0x00);
+ Arrays.fill(this.receivingCtr, (byte) 0x00);
+ this.sendingAESKey = null;
+ this.receivingAESKey = null;
+ this.sendingMACKey = null;
+ this.receivingMACKey = null;
+ this.setIsUsedReceivingMACKey(false);
+ this.s = null;
+ if (getLocalPair() != null && getRemoteKey() != null) {
+ this.isHigh = ((DHPublicKey) getLocalPair().getPublic()).getY()
+ .abs().compareTo(getRemoteKey().getY().abs()) == 1;
+ }
+
+ }
+
+ private byte[] h1(byte b) throws OtrException {
+
+ try {
+ byte[] secbytes = SerializationUtils.writeMpi(getS());
+
+ int len = secbytes.length + 1;
+ ByteBuffer buff = ByteBuffer.allocate(len);
+ buff.put(b);
+ buff.put(secbytes);
+ byte[] result = new OtrCryptoEngineImpl().sha1Hash(buff.array());
+ return result;
+ } catch (Exception e) {
+ throw new OtrException(e);
+ }
+ }
+
+ public byte[] getSendingAESKey() throws OtrException {
+ if (sendingAESKey != null)
+ return sendingAESKey;
+
+ byte sendbyte = LOW_SEND_BYTE;
+ if (this.isHigh)
+ sendbyte = HIGH_SEND_BYTE;
+
+ byte[] h1 = h1(sendbyte);
+
+ byte[] key = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH];
+ ByteBuffer buff = ByteBuffer.wrap(h1);
+ buff.get(key);
+ logger.finest("Calculated sending AES key.");
+ this.sendingAESKey = key;
+ return sendingAESKey;
+ }
+
+ public byte[] getReceivingAESKey() throws OtrException {
+ if (receivingAESKey != null)
+ return receivingAESKey;
+
+ byte receivebyte = LOW_RECEIVE_BYTE;
+ if (this.isHigh)
+ receivebyte = HIGH_RECEIVE_BYTE;
+
+ byte[] h1 = h1(receivebyte);
+
+ byte[] key = new byte[OtrCryptoEngine.AES_KEY_BYTE_LENGTH];
+ ByteBuffer buff = ByteBuffer.wrap(h1);
+ buff.get(key);
+ logger.finest("Calculated receiving AES key.");
+ this.receivingAESKey = key;
+
+ return receivingAESKey;
+ }
+
+ public byte[] getSendingMACKey() throws OtrException {
+ if (sendingMACKey != null)
+ return sendingMACKey;
+
+ sendingMACKey = new OtrCryptoEngineImpl().sha1Hash(getSendingAESKey());
+ logger.finest("Calculated sending MAC key.");
+ return sendingMACKey;
+ }
+
+ public byte[] getReceivingMACKey() throws OtrException {
+ if (receivingMACKey == null) {
+ receivingMACKey = new OtrCryptoEngineImpl()
+ .sha1Hash(getReceivingAESKey());
+ logger.finest("Calculated receiving AES key.");
+ }
+ return receivingMACKey;
+ }
+
+ private BigInteger getS() throws OtrException {
+ if (s == null) {
+ s = new OtrCryptoEngineImpl().generateSecret(getLocalPair()
+ .getPrivate(), getRemoteKey());
+ logger.finest("Calculating shared secret S.");
+ }
+ return s;
+ }
+
+ public void setS(BigInteger s) {
+ this.s = s;
+ }
+
+ public void setIsUsedReceivingMACKey(Boolean isUsedReceivingMACKey) {
+ this.isUsedReceivingMACKey = isUsedReceivingMACKey;
+ }
+
+ public Boolean getIsUsedReceivingMACKey() {
+ return isUsedReceivingMACKey;
+ }
+
+ private void setLocalKeyID(int localKeyID) {
+ this.localKeyID = localKeyID;
+ }
+
+ public int getLocalKeyID() {
+ return localKeyID;
+ }
+
+ private void setRemoteKeyID(int remoteKeyID) {
+ this.remoteKeyID = remoteKeyID;
+ }
+
+ public int getRemoteKeyID() {
+ return remoteKeyID;
+ }
+
+ private void setRemoteKey(DHPublicKey remoteKey) {
+ this.remoteKey = remoteKey;
+ }
+
+ public DHPublicKey getRemoteKey() {
+ return remoteKey;
+ }
+
+ public KeyPair getLocalPair() {
+ return localPair;
+ }
+
+ private int localKeyID;
+ private int remoteKeyID;
+ private DHPublicKey remoteKey;
+ private KeyPair localPair;
+
+ private byte[] sendingAESKey;
+ private byte[] receivingAESKey;
+ private byte[] sendingMACKey;
+ private byte[] receivingMACKey;
+ private Boolean isUsedReceivingMACKey;
+ private BigInteger s;
+ private Boolean isHigh;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/net/java/otr4j/session/SessionStatus.java Sun Dec 05 18:43:51 2010 +0100
@@ -0,0 +1,17 @@
+/*
+ * otr4j, the open source java otr library.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ */
+package net.java.otr4j.session;
+
+/**
+ *
+ * @author George Politis
+ */
+public enum SessionStatus {
+ PLAINTEXT,
+ ENCRYPTED,
+ FINISHED
+}
\ No newline at end of file