# HG changeset patch # User Nikita Kozlov # Date 1306358015 -7200 # Node ID defdbe820907ee2564e45c62072ab2fecf5031b5 # Parent 547bb144e3a4c86bf75f5fd18df7b2fb348d72f7# Parent c17e4b9ac7dead9c040c34b5e9ef282811c5064e Merge diff -r c17e4b9ac7de -r defdbe820907 .hgignore diff -r c17e4b9ac7de -r defdbe820907 .hgtags diff -r c17e4b9ac7de -r defdbe820907 AndroidManifest.xml diff -r c17e4b9ac7de -r defdbe820907 COPYING diff -r c17e4b9ac7de -r defdbe820907 CREDITS diff -r c17e4b9ac7de -r defdbe820907 INSTALL diff -r c17e4b9ac7de -r defdbe820907 README diff -r c17e4b9ac7de -r defdbe820907 build.properties diff -r c17e4b9ac7de -r defdbe820907 build.xml diff -r c17e4b9ac7de -r defdbe820907 default.properties --- a/default.properties Wed May 25 11:34:25 2011 +0000 +++ b/default.properties Wed May 25 23:13:35 2011 +0200 @@ -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-10 diff -r c17e4b9ac7de -r defdbe820907 doc/UmlGraph.jar diff -r c17e4b9ac7de -r defdbe820907 doc/android/package-list diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/README.txt diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/beem-build-process.patch diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/beem_patches/50-fix-chatmanager.patch diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/beem_patches/50-fix-sasl-incorrect-encoding.patch diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/beem_patches/50-public-info-features.patch diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/beem_patches/COPYING diff -r c17e4b9ac7de -r defdbe820907 doc/asmack-beem/beem_patches/README.txt diff -r c17e4b9ac7de -r defdbe820907 doc/favicon.ico diff -r c17e4b9ac7de -r defdbe820907 doc/smack/package-list diff -r c17e4b9ac7de -r defdbe820907 libs/README.txt diff -r c17e4b9ac7de -r defdbe820907 libs/asmack-android-7-beem.jar diff -r c17e4b9ac7de -r defdbe820907 libs/lcrypto-jdk16-146-20110415.jar Binary file libs/lcrypto-jdk16-146-20110415.jar has changed diff -r c17e4b9ac7de -r defdbe820907 res/anim/rotate_and_scale.xml diff -r c17e4b9ac7de -r defdbe820907 res/drawable-hdpi/beem_icon_launcher_color.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable-ldpi/beem_icon_launcher_color.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable-mdpi/beem_icon_launcher_color.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/beem_launcher_icon_silver.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/beem_status_icon.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/bottombar.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/button_indicator_next.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/button_indicator_prev.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_add.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_blocked_user.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_chat_dashboard.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_close_clear_cancel.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_end_conversation.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_friendslist.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_invite.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_login.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/ic_menu_manage.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/icon.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/logo.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/not_in_the_roster.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/scrollbar_vertical_thumb.xml diff -r c17e4b9ac7de -r defdbe820907 res/drawable/scrollbar_vertical_track.xml diff -r c17e4b9ac7de -r defdbe820907 res/drawable/shape_border_green.xml diff -r c17e4b9ac7de -r defdbe820907 res/drawable/shape_line_green.xml diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_available.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_away.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_blocked.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_dnd.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_error.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_icon.xml diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_idle.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_invisible.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_new_message.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_offline.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_requested.png diff -r c17e4b9ac7de -r defdbe820907 res/drawable/status_typing.png diff -r c17e4b9ac7de -r defdbe820907 res/layout/addcontact.xml diff -r c17e4b9ac7de -r defdbe820907 res/layout/changestatus.xml diff -r c17e4b9ac7de -r defdbe820907 res/layout/chat.xml --- a/res/layout/chat.xml Wed May 25 11:34:25 2011 +0000 +++ b/res/layout/chat.xml Wed May 25 23:13:35 2011 +0200 @@ -28,6 +28,10 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="italic" android:textSize="12sp" android:focusable="true"/> + + + + + + + + + + diff -r c17e4b9ac7de -r defdbe820907 res/menu/contact_list.xml diff -r c17e4b9ac7de -r defdbe820907 res/menu/contactlist_context.xml diff -r c17e4b9ac7de -r defdbe820907 res/menu/create_account.xml diff -r c17e4b9ac7de -r defdbe820907 res/menu/edit_settings.xml diff -r c17e4b9ac7de -r defdbe820907 res/menu/login.xml diff -r c17e4b9ac7de -r defdbe820907 res/menu/privacy_list.xml diff -r c17e4b9ac7de -r defdbe820907 res/menu/privacy_list_context.xml diff -r c17e4b9ac7de -r defdbe820907 res/values-de/strings.xml diff -r c17e4b9ac7de -r defdbe820907 res/values-es/strings.xml diff -r c17e4b9ac7de -r defdbe820907 res/values-fr/strings.xml diff -r c17e4b9ac7de -r defdbe820907 res/values-ru/strings.xml diff -r c17e4b9ac7de -r defdbe820907 res/values-zh-rCN/strings.xml diff -r c17e4b9ac7de -r defdbe820907 res/values-zh-rTW/strings.xml diff -r c17e4b9ac7de -r defdbe820907 res/values/arrays.xml diff -r c17e4b9ac7de -r defdbe820907 res/values/colors.xml diff -r c17e4b9ac7de -r defdbe820907 res/values/strings.xml --- a/res/values/strings.xml Wed May 25 11:34:25 2011 +0000 +++ b/res/values/strings.xml Wed May 25 23:13:35 2011 +0200 @@ -271,6 +271,12 @@ Send Contacts list Switch chat + Start OTR session + Listen for OTR session + Stop OTR session + OTR local key + OTR remote key + OTR actions Opened chats Close this chat No more active chats @@ -278,6 +284,11 @@ has left the conversation pays attention to the conversation is doing another thing + PLAINTEXT + ENCRYPTED + FINISHED + Verify remote fingerprint %s ? + Local fingerprint %s Available Available to chat diff -r c17e4b9ac7de -r defdbe820907 res/values/styles.xml diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/BeemApplication.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/BeemService.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/otr/BeemOtrManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/beem/project/beem/otr/BeemOtrManager.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,175 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009-2011 by Frederic-Charles Barthelery, + Nikita Kozlov, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://www.beem-project.com/ + + */ +package com.beem.project.beem.otr; + +import java.io.IOException; +import java.security.KeyPair; +import java.util.HashMap; +import java.util.Map; + +import net.java.otr4j.OtrEngine; +import net.java.otr4j.OtrEngineHost; +import net.java.otr4j.OtrEngineImpl; +import net.java.otr4j.OtrEngineListener; +import net.java.otr4j.OtrException; +import net.java.otr4j.OtrKeyManagerImpl; +import net.java.otr4j.OtrPolicy; +import net.java.otr4j.OtrPolicyImpl; +import net.java.otr4j.session.SessionID; +import net.java.otr4j.session.SessionStatus; +import android.util.Log; + +import com.beem.project.beem.service.ChatAdapter; +/** + * BeemOtrManager. + */ +public class BeemOtrManager implements OtrEngineHost { + + private static final String TAG = "BeemOtrEngineHostImpl"; + private static BeemOtrManager INSTANCE; + //We will have a global policy for Beem as long as we won't need to modify the policy per chat. + private static final OtrPolicy mGlobalPolicy = new OtrPolicyImpl(OtrPolicy.ALLOW_V2 | OtrPolicy.ERROR_START_AKE); + + + private OtrEngine mOtrEngine; + private OtrKeyManagerImpl mOtrKeyManager; + + //Map of chat, needed because of the message injection + private final Map mChats = new HashMap(); + + /** + * Private constructor prevents instantiation from other classes. + */ + private BeemOtrManager() { + mOtrEngine = new OtrEngineImpl(this); + mOtrEngine.addOtrEngineListener(new BeemOtrListener()); + try { + mOtrKeyManager = new OtrKeyManagerImpl("/sdcard/beem.keystore"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * getOtrManager. + * @return OtrEngine + */ + public OtrEngine getOtrManager() { + return mOtrEngine; + } + + + /** + * BeemOtrManager.getInstance. + * @return BeemOtrManager + */ + public static BeemOtrManager getInstance() { + if (INSTANCE == null) + INSTANCE = new BeemOtrManager(); + return INSTANCE; + } + + /** + * We must call addChat before stating a new otr session because we will need the chat instance for message injection. + * @param sessionID the otr sessionID. + * @param chat instance. + */ + public void addChat(final SessionID sessionID, final ChatAdapter chat) { + mChats.put(sessionID, chat); + Log.d(TAG, "adding new OTR session " + sessionID); + } + + /** + * We must remove the chat from the map after we ended the corresponding otr session. + * @param sessionID the otr sessionID to remove. + */ + public void removeChat(final SessionID sessionID) { + mChats.remove(sessionID); + } + + public String getRemoteFingerprint(final SessionID sessionID) { + return mOtrKeyManager.getRemoteFingerprint(sessionID); + } + + public String getLocalFingerprint(final SessionID sessionID) { + return mOtrKeyManager.getLocalFingerprint(sessionID); + } + + @Override + public void injectMessage(SessionID sessionID, String msg) { + ChatAdapter chat = mChats.get(sessionID); + chat.sendMessage(msg); + } + + @Override + public void showWarning(SessionID sessionID, String warning) { + Log.d(TAG, "Warning for " + sessionID + " : " + warning); + } + + @Override + public void showError(SessionID sessionID, String error) { + Log.d(TAG, "Error for " + sessionID + " : " + error); + } + + @Override + public OtrPolicy getSessionPolicy(SessionID sessionID) { + return mGlobalPolicy; + } + + @Override + public KeyPair getKeyPair(SessionID sessionID) { + KeyPair kp = mOtrKeyManager.loadLocalKeyPair(sessionID); + + if(kp != null) + return kp; + + mOtrKeyManager.generateLocalKeyPair(sessionID); + return mOtrKeyManager.loadLocalKeyPair(sessionID); + } + + /** + * BeemOtrListener. + */ + private class BeemOtrListener implements OtrEngineListener { + + @Override + public void sessionStatusChanged(final SessionID sessionID) { + Log.d(TAG, "OTR Status changed for " + sessionID + " : " + mOtrEngine.getSessionStatus(sessionID)); + if (mOtrKeyManager.loadRemotePublicKey(sessionID) == null) { + mOtrKeyManager.savePublicKey(sessionID, mOtrEngine.getRemotePublicKey(sessionID)); + } + mChats.get(sessionID).otrStateChanged(mOtrEngine.getSessionStatus(sessionID).toString()); + if(mOtrEngine.getSessionStatus(sessionID) == SessionStatus.FINISHED) { + try { + mChats.get(sessionID).localEndOtrSession(); + } catch (OtrException e) { + e.printStackTrace(); + } + } + } + } +} diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/BeemCapsManager.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/BeemChatManager.java --- a/src/com/beem/project/beem/service/BeemChatManager.java Wed May 25 11:34:25 2011 +0000 +++ b/src/com/beem/project/beem/service/BeemChatManager.java Wed May 25 23:13:35 2011 +0200 @@ -315,7 +315,12 @@ } @Override - public void stateChanged(final IChat chat) { + public void stateChanged(final IChat chat) { } + + @Override + public void otrStateChanged(String otrState) throws RemoteException { + // TODO Auto-generated method stub + } } } diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/ChatAdapter.java --- a/src/com/beem/project/beem/service/ChatAdapter.java Wed May 25 11:34:25 2011 +0000 +++ b/src/com/beem/project/beem/service/ChatAdapter.java Wed May 25 23:13:35 2011 +0200 @@ -50,6 +50,9 @@ import java.util.LinkedList; import java.util.List; +import net.java.otr4j.OtrException; +import net.java.otr4j.session.SessionID; + import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.util.StringUtils; @@ -61,6 +64,7 @@ import android.os.RemoteException; import android.util.Log; +import com.beem.project.beem.otr.BeemOtrManager; import com.beem.project.beem.service.aidl.IChat; import com.beem.project.beem.service.aidl.IMessageListener; @@ -71,6 +75,7 @@ public class ChatAdapter extends IChat.Stub { private static final int HISTORY_MAX_SIZE = 50; private static final String TAG = "ChatAdapter"; + private static final String PROTOCOL = "XMPP"; private final Chat mAdaptee; private final Contact mParticipant; @@ -79,6 +84,7 @@ private final List mMessages; private final RemoteCallbackList mRemoteListeners = new RemoteCallbackList(); private final MsgListener mMsgListener = new MsgListener(); + private SessionID mOtrSessionId; private boolean mIsHistory; private File mHistoryPath; private String mAccountUser; @@ -107,9 +113,35 @@ */ @Override public void sendMessage(com.beem.project.beem.service.Message message) throws RemoteException { + sendMessage(message, true); + } + + /** + * private method for sending message. + * @param message the message to send + * @param log do we want to log (in memory and history) the message? + */ + private void sendMessage(com.beem.project.beem.service.Message message, boolean log) { org.jivesoftware.smack.packet.Message send = new org.jivesoftware.smack.packet.Message(); + String msgBody = message.getBody(); + String otrBody = null; send.setTo(message.getTo()); - send.setBody(message.getBody()); + Log.w(TAG, "message to " + message.getTo()); + + if (mOtrSessionId != null) { + + try { + otrBody = BeemOtrManager.getInstance().getOtrManager().transformSending(mOtrSessionId, msgBody); + send.setBody(otrBody); + } catch (OtrException e) { + e.printStackTrace(); + } + } else { + send.setBody(msgBody); + otrBody = msgBody; + } + + send.setThread(message.getThread()); send.setSubject(message.getSubject()); send.setType(org.jivesoftware.smack.packet.Message.Type.chat); @@ -117,16 +149,27 @@ // send.set try { mAdaptee.sendMessage(send); - mMessages.add(message); + if (log && otrBody != null) + mMessages.add(message); } catch (XMPPException e) { e.printStackTrace(); } String state = Environment.getExternalStorageState(); - if (mIsHistory && Environment.MEDIA_MOUNTED.equals(state)) + if (log && mIsHistory && Environment.MEDIA_MOUNTED.equals(state)) saveHistory(message, mAccountUser); } /** + * send message. + * @param msg to send. + */ + public void sendMessage(String msg) { + Message msgToSend = new Message(mParticipant.getJIDWithRes(), Message.MSG_TYPE_CHAT); + msgToSend.setBody(msg); + sendMessage(msgToSend, false); + } + + /** * {@inheritDoc} */ @Override @@ -145,6 +188,7 @@ } } + /** * {@inheritDoc} */ @@ -215,16 +259,16 @@ */ public void saveHistory(Message msg, String contactName) { File path = getHistoryPath(); - File filepath; - if (contactName.equals(msg.getFrom())) - filepath = new File(path, StringUtils.parseBareAddress(contactName)); - else - filepath = new File(path, StringUtils.parseBareAddress(msg.getTo())); - path.mkdirs(); + File filepath; + if (contactName.equals(msg.getFrom())) + filepath = new File(path, StringUtils.parseBareAddress(contactName)); + else + filepath = new File(path, StringUtils.parseBareAddress(msg.getTo())); + path.mkdirs(); try { FileWriter file = new FileWriter(filepath, true); String log = msg.getTimestamp() + " " + contactName + " " + msg.getBody() - + System.getProperty("line.separator"); + + System.getProperty("line.separator"); file.write(log); file.close(); } catch (IOException e) { @@ -287,12 +331,24 @@ /** * Constructor. */ - public MsgListener() { - } + public MsgListener() { } @Override public void processMessage(Chat chat, org.jivesoftware.smack.packet.Message message) { - Message msg = new Message(message); + Message msg = new Message(message); + Log.d(TAG, "new msg " + msg.getBody()); + String body; + + if (mOtrSessionId != null) { + + try { + body = BeemOtrManager.getInstance().getOtrManager().transformReceiving(mOtrSessionId, msg.getBody()); + msg.setBody(body); + } catch (OtrException e) { + e.printStackTrace(); + } + } + //TODO add que les message pas de type errors ChatAdapter.this.addMessage(msg); final int n = mRemoteListeners.beginBroadcast(); @@ -326,5 +382,101 @@ } mRemoteListeners.finishBroadcast(); } + + } + /** + * This method is executed when the otr session status change. + * @param otrState the new state of otr session. + */ + public void otrStateChanged(final String otrState) { + final int n = mRemoteListeners.beginBroadcast(); + + for (int i = 0; i < n; i++) { + IMessageListener listener = mRemoteListeners.getBroadcastItem(i); + try { + listener.otrStateChanged(otrState); + } catch (RemoteException e) { + Log.w(TAG, e.getMessage()); + } + } + mRemoteListeners.finishBroadcast(); + } + + + @Override + public void startOtrSession() throws RemoteException { + if (mOtrSessionId != null) + return; + + mOtrSessionId = new SessionID(mParticipant.getJIDWithRes(), mParticipant.getJID(), PROTOCOL); + try { + BeemOtrManager.getInstance().addChat(mOtrSessionId, this); + BeemOtrManager.getInstance().getOtrManager().startSession(mOtrSessionId); + } catch (OtrException e) { + mOtrSessionId = null; + e.printStackTrace(); + throw new RemoteException(); + } + } + + @Override + public void endOtrSession() throws RemoteException { + try { + localEndOtrSession(); + } catch (OtrException e) { + e.printStackTrace(); + throw new RemoteException(); + } + } + + /** + * end an Otr session. + * @return false if something bad happened. + */ + public boolean localEndOtrSession() throws OtrException { + if (mOtrSessionId == null) + return true; + + BeemOtrManager.getInstance().getOtrManager().endSession(mOtrSessionId); + BeemOtrManager.getInstance().removeChat(mOtrSessionId); + mOtrSessionId = null; + return true; + } + + @Override + public void listenOtrSession() throws RemoteException { + if (mOtrSessionId != null) + return; + + mOtrSessionId = new SessionID(mParticipant.getJIDWithRes(), mParticipant.getJID(), PROTOCOL); + BeemOtrManager.getInstance().addChat(mOtrSessionId, this); + //OtrEngineImpl will make a call to "this.getSession(sessionID)" which will instantiate our session. + BeemOtrManager.getInstance().getOtrManager().getSessionStatus(mOtrSessionId); + + } + + @Override + public String getLocalOtrFingerprint() throws RemoteException { + if (mOtrSessionId == null) + return null; + + return BeemOtrManager.getInstance().getLocalFingerprint(mOtrSessionId); + } + + @Override + public String getRemoteOtrFingerprint() throws RemoteException { + if (mOtrSessionId == null) + return null; + + return BeemOtrManager.getInstance().getRemoteFingerprint(mOtrSessionId); + } + + @Override + public String getOtrStatus() throws RemoteException { + if (mOtrSessionId == null) + return null; + return BeemOtrManager.getInstance().getOtrManager().getSessionStatus(mOtrSessionId).toString(); + } } + diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/Contact.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/Contact.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/LoginAsyncTask.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/Message.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/Message.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/PresenceAdapter.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/PresenceAdapter.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/PrivacyListItem.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/PrivacyListItem.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/PrivacyListManagerAdapter.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/RosterAdapter.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/XmppConnectionAdapter.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/XmppFacade.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IBeemConnectionListener.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IBeemRosterListener.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IChat.aidl --- a/src/com/beem/project/beem/service/aidl/IChat.aidl Wed May 25 11:34:25 2011 +0000 +++ b/src/com/beem/project/beem/service/aidl/IChat.aidl Wed May 25 23:13:35 2011 +0200 @@ -85,5 +85,37 @@ void setState(in String state); List getMessages(); + + /** + * Try to start an OTR session. + */ + void startOtrSession(); + + /** + * Stop the OTR session. + */ + void endOtrSession(); + + /** + * Listen for an incoming OTR session. + */ + void listenOtrSession(); + + /** + * get local OTR key fingerprints. + */ + String getLocalOtrFingerprint(); + + + /** + * get remote OTR key fingerprints. + */ + String getRemoteOtrFingerprint(); + + /** + * get current OTR status. + */ + String getOtrStatus(); + } diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IChatManager.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IChatManagerListener.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IContact.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IMessageListener.aidl --- a/src/com/beem/project/beem/service/aidl/IMessageListener.aidl Wed May 25 11:34:25 2011 +0000 +++ b/src/com/beem/project/beem/service/aidl/IMessageListener.aidl Wed May 25 23:13:35 2011 +0200 @@ -61,4 +61,9 @@ * @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); } diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IPrivacyListListener.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IPrivacyListManager.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IRoster.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IXmppConnection.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/aidl/IXmppFacade.aidl diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/service/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/smack/caps/CapsExtension.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/smack/caps/CapsManager.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/smack/caps/CapsProvider.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/smack/caps/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/AddContact.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/ChangeStatus.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/Chat.java --- a/src/com/beem/project/beem/ui/Chat.java Wed May 25 11:34:25 2011 +0000 +++ b/src/com/beem/project/beem/ui/Chat.java Wed May 25 23:13:35 2011 +0200 @@ -43,14 +43,14 @@ */ package com.beem.project.beem.ui; +import java.io.IOException; +import java.io.InputStream; import java.text.DateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.io.InputStream; -import java.io.IOException; import org.jivesoftware.smack.packet.Presence.Mode; import org.jivesoftware.smack.util.StringUtils; @@ -62,11 +62,11 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -75,7 +75,6 @@ import android.preference.PreferenceManager; import android.text.util.Linkify; import android.util.Log; -import android.view.inputmethod.EditorInfo; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -84,6 +83,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; +import android.view.inputmethod.EditorInfo; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.EditText; @@ -91,9 +91,8 @@ import android.widget.ListView; import android.widget.TextView; - +import com.beem.project.beem.BeemApplication; import com.beem.project.beem.R; -import com.beem.project.beem.BeemApplication; import com.beem.project.beem.providers.AvatarProvider; import com.beem.project.beem.service.Contact; import com.beem.project.beem.service.Message; @@ -106,6 +105,7 @@ import com.beem.project.beem.service.aidl.IRoster; import com.beem.project.beem.service.aidl.IXmppFacade; import com.beem.project.beem.ui.dialogs.builders.ChatList; +import com.beem.project.beem.ui.dialogs.builders.DisplayOtrFingerprint; import com.beem.project.beem.utils.BeemBroadcastReceiver; import com.beem.project.beem.utils.Status; @@ -128,6 +128,7 @@ private TextView mContactNameTextView; private TextView mContactStatusMsgTextView; private TextView mContactChatState; + private TextView mContactOtrState; private ImageView mContactStatusIcon; private LayerDrawable mAvatarStatusDrawable; private ListView mMessagesListView; @@ -175,6 +176,7 @@ mContactChatState = (TextView) findViewById(R.id.chat_contact_chat_state); mContactStatusIcon = (ImageView) findViewById(R.id.chat_contact_status_icon); mAvatarStatusDrawable = (LayerDrawable) mContactStatusIcon.getDrawable(); + mContactOtrState = (TextView) findViewById(R.id.chat_contact_otr_state); mAvatarStatusDrawable.setLayerInset(1, 36, 36, 0, 0); } else { setContentView(R.layout.chat_compact); @@ -191,6 +193,7 @@ sendMessage(); } }); + prepareIconsStatus(); } @@ -291,7 +294,6 @@ case R.id.chat_menu_change_chat: try { final List openedChats = mChatManager.getOpenedChatList(); - Log.d(TAG, "opened chats = " + openedChats); Dialog chatList = new ChatList(Chat.this, openedChats).create(); chatList.show(); } catch (RemoteException e) { @@ -306,6 +308,75 @@ } this.finish(); break; + case R.id.chat_menu_start_otr_session: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + mChat.startOtrSession(); + } catch (RemoteException e) { + Log.e(TAG, "start otr chats failed " + mChat , e); + } + break; + case R.id.chat_menu_listen_otr_session: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + mChat.listenOtrSession(); + } catch (RemoteException e) { + Log.e(TAG, "listen for otr chats failed " + mChat , e); + } + break; + case R.id.chat_menu_stop_otr_session: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + mChat.endOtrSession(); + } catch (RemoteException e) { + Log.e(TAG, "close otr chats failed " + mChat , e); + } + break; + case R.id.chat_menu_otr_local_key: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + String fk = mChat.getLocalOtrFingerprint(); + Dialog otrDialog = new DisplayOtrFingerprint(this, fk, true).create(); + otrDialog.show(); + } catch (RemoteException e) { + Log.e(TAG, "getting local otr key failed " + mChat , e); + } + break; + case R.id.chat_menu_otr_remote_key: + try { + if (mChat == null) { + mChat = mChatManager.createChat(mContact, mMessageListener); + if (mChat != null) { + mChat.setOpen(true); + } + } + String fk = mChat.getRemoteOtrFingerprint(); + Dialog otrDialog = new DisplayOtrFingerprint(this, fk, false).create(); + otrDialog.show(); + } catch (RemoteException e) { + Log.e(TAG, "getting remote otr key failed " + mChat , e); + } + break; default: return false; } @@ -327,6 +398,7 @@ mChat.setOpen(true); mChat.addMessageListener(mMessageListener); mChatManager.deleteChatNotification(mChat); + updateOtrInformations(mChat.getOtrStatus()); } mContact = mRoster.getContact(contact.getJID()); String res = contact.getSelectedRes(); @@ -560,6 +632,17 @@ }); } + + @Override + public void otrStateChanged(final String otrState) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + updateOtrInformations(otrState); + } + }); + + } } /** @@ -588,6 +671,22 @@ setTitle(getString(R.string.chat_name) + " " + name + " (" + m.name() + ")"); } } + + /** + * Update the OTR informations. + */ + private void updateOtrInformations(final String otrState) { + String text = null; + if ("ENCRYPTED".equals(otrState)) { + text = Chat.this.getString(R.string.chat_otrstate_encrypted); + } else if ("FINISHED".equals(otrState)) { + text = Chat.this.getString(R.string.chat_otrstate_finished); + } else { + text = Chat.this.getString(R.string.chat_otrstate_plaintext); + } + mContactOtrState.setText(text); + } + @@ -623,7 +722,7 @@ try { try { in = getContentResolver().openInputStream(uri); - avatarDrawable = Drawable.createFromStream(in, avatarId); + avatarDrawable = Drawable.createFromStream(in, avatarId); } finally { if (in != null) in.close(); @@ -706,7 +805,7 @@ View sv; if (convertView == null) { LayoutInflater inflater = Chat.this.getLayoutInflater(); - sv = inflater.inflate(R.layout.chat_msg_row, null); + sv = inflater.inflate(R.layout.chat_msg_row, null); } else { sv = convertView; } diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/ContactList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/CreateAccount.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/GroupList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/Login.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/LoginAnim.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/PrivacyList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/Settings.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/Subscription.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/Alias.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/ChatList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/CreatePrivacyList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/DeleteContact.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/DeletePrivacyList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/DisplayOtrFingerprint.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/beem/project/beem/ui/dialogs/builders/DisplayOtrFingerprint.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,96 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ +package com.beem.project.beem.ui.dialogs.builders; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; + +import com.beem.project.beem.R; + +/** + * + */ +public class DisplayOtrFingerprint extends AlertDialog.Builder { + + private static final String TAG = "DisplayOtrFingerprint"; + + /** + * Constructor. + * @param context context activity. + * @param roster the roster which has the contact you want to delete. + * @param contact the contact to delete. + */ + public DisplayOtrFingerprint(final Context context, final String otrFingerprint, final boolean local) { + super(context); + + if (local) { + setMessage(context.getString(R.string.chat_otr_local_key, otrFingerprint)); + } else { + setMessage(context.getString(R.string.chat_otr_verify_key, otrFingerprint)); + DialogClickListener dl = new DialogClickListener(); + setPositiveButton(R.string.userinfo_yes, dl); + setNegativeButton(R.string.userinfo_no, dl); + } + } + + /** + * Event click listener. + */ + private class DialogClickListener implements DialogInterface.OnClickListener { + + /** + * Constructor. + */ + public DialogClickListener() { + } + + @Override + public void onClick(final DialogInterface dialog, final int which) { + if (which == DialogInterface.BUTTON_POSITIVE) { + + } + } + } +} diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/ResendSubscription.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/dialogs/builders/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/wizard/Account.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/wizard/AccountConfigure.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/ui/wizard/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/BeemBroadcastReceiver.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/BeemConnectivity.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/FreePort.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/PresenceType.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/SortedList.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/Status.java diff -r c17e4b9ac7de -r defdbe820907 src/com/beem/project/beem/utils/package-info.java diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrEngine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrEngine.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,84 @@ +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. + * @throws OtrException + */ + public abstract String transformReceiving(SessionID sessionID, + String content) throws OtrException; + + /** + * + * @param sessionID + * The session identifier. + * @param content + * The message content to be transformed. + * @return The transformed message content. + * @throws OtrException + */ + public abstract String transformSending(SessionID sessionID, String content) throws OtrException; + + /** + * Starts an Off-the-Record session, if there is no active one. + * + * @param sessionID + * The session identifier. + * @throws OtrException + */ + public abstract void startSession(SessionID sessionID) throws OtrException; + + /** + * Ends the Off-the-Record session, if exists. + * + * @param sessionID + * The session identifier. + * @throws OtrException + */ + public abstract void endSession(SessionID sessionID) throws OtrException; + + /** + * Stops/Starts the Off-the-Record session. + * + * @param sessionID + * The session identifier. + * @throws OtrException + */ + public abstract void refreshSession(SessionID sessionID) throws OtrException; + + /** + * + * @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); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrEngineHost.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrEngineHost.java Wed May 25 23:13:35 2011 +0200 @@ -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); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrEngineImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrEngineImpl.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,114 @@ +/* + * 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 host) { + if (host == null) + throw new IllegalArgumentException("OtrEgineHost is required."); + + this.setHost(host); + } + + private OtrEngineHost host; + private Map sessions; + + private Session getSession(SessionID sessionID) { + + if (sessionID == null || sessionID.equals(SessionID.Empty)) + throw new IllegalArgumentException(); + + if (sessions == null) + sessions = new Hashtable(); + + if (!sessions.containsKey(sessionID)) { + Session session = new SessionImpl(sessionID, getHost()); + sessions.put(sessionID, session); + + session.addOtrEngineListener(new OtrEngineListener() { + + public void sessionStatusChanged(SessionID sessionID) { + for (OtrEngineListener l : listeners) + l.sessionStatusChanged(sessionID); + } + }); + return session; + } else + return sessions.get(sessionID); + } + + public SessionStatus getSessionStatus(SessionID sessionID) { + return this.getSession(sessionID).getSessionStatus(); + } + + public String transformReceiving(SessionID sessionID, String msgText) + throws OtrException { + return this.getSession(sessionID).transformReceiving(msgText); + } + + public String transformSending(SessionID sessionID, String msgText) + throws OtrException { + return this.getSession(sessionID).transformSending(msgText, null); + } + + public void endSession(SessionID sessionID) throws OtrException { + this.getSession(sessionID).endSession(); + } + + public void startSession(SessionID sessionID) throws OtrException { + this.getSession(sessionID).startSession(); + } + + private void setHost(OtrEngineHost host) { + this.host = host; + } + + private OtrEngineHost getHost() { + return host; + } + + public void refreshSession(SessionID sessionID) throws OtrException { + this.getSession(sessionID).refreshSession(); + } + + public PublicKey getRemotePublicKey(SessionID sessionID) { + return this.getSession(sessionID).getRemotePublicKey(); + } + + private List listeners = new Vector(); + + public void addOtrEngineListener(OtrEngineListener l) { + synchronized (listeners) { + if (!listeners.contains(l)) + listeners.add(l); + } + } + + public void removeOtrEngineListener(OtrEngineListener l) { + synchronized (listeners) { + listeners.remove(l); + } + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrEngineListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrEngineListener.java Wed May 25 23:13:35 2011 +0200 @@ -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); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrException.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,8 @@ +package net.java.otr4j; + +@SuppressWarnings("serial") +public class OtrException extends Exception { + public OtrException(Exception e){ + super(e); + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrKeyManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrKeyManager.java Wed May 25 23:13:35 2011 +0200 @@ -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); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrKeyManagerImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrKeyManagerImpl.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,304 @@ +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.bouncycastle2.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); + if (value == null) + return null; + 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 listeners = new Vector(); + + 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); + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrKeyManagerListener.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrKeyManagerListener.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,7 @@ +package net.java.otr4j; + +import net.java.otr4j.session.SessionID; + +public interface OtrKeyManagerListener { + public abstract void verificationStatusChanged(SessionID session); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrKeyManagerStore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrKeyManagerStore.java Wed May 25 23:13:35 2011 +0200 @@ -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); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrPolicy.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrPolicy.java Wed May 25 23:13:35 2011 +0200 @@ -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(); +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/OtrPolicyImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/OtrPolicyImpl.java Wed May 25 23:13:35 2011 +0200 @@ -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(); + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/crypto/OtrCryptoEngine.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/crypto/OtrCryptoEngine.java Wed May 25 23:13:35 2011 +0200 @@ -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; +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/crypto/OtrCryptoEngineImpl.java Wed May 25 23:13:35 2011 +0200 @@ -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.bouncycastle2.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle2.crypto.BufferedBlockCipher; +import org.bouncycastle2.crypto.engines.AESFastEngine; +import org.bouncycastle2.crypto.generators.DHKeyPairGenerator; +import org.bouncycastle2.crypto.modes.SICBlockCipher; +import org.bouncycastle2.crypto.params.DHKeyGenerationParameters; +import org.bouncycastle2.crypto.params.DHParameters; +import org.bouncycastle2.crypto.params.DHPrivateKeyParameters; +import org.bouncycastle2.crypto.params.DHPublicKeyParameters; +import org.bouncycastle2.crypto.params.DSAParameters; +import org.bouncycastle2.crypto.params.DSAPrivateKeyParameters; +import org.bouncycastle2.crypto.params.DSAPublicKeyParameters; +import org.bouncycastle2.crypto.params.KeyParameter; +import org.bouncycastle2.crypto.params.ParametersWithIV; +import org.bouncycastle2.crypto.signers.DSASigner; +import org.bouncycastle2.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; + + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/crypto/OtrCryptoException.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/crypto/OtrCryptoException.java Wed May 25 23:13:35 2011 +0200 @@ -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); + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/OtrInputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/OtrInputStream.java Wed May 25 23:13:35 2011 +0200 @@ -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); + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/OtrOutputStream.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/OtrOutputStream.java Wed May 25 23:13:35 2011 +0200 @@ -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.bouncycastle2.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); + + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/SerializationConstants.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/SerializationConstants.java Wed May 25 23:13:35 2011 +0200 @@ -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; +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/SerializationUtils.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/SerializationUtils.java Wed May 25 23:13:35 2011 +0200 @@ -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.bouncycastle2.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 versions; + if (v1 && v2) { + versions = new Vector(2); + versions.add(0, 1); + versions.add(0, 2); + } else if (v1) { + versions = new Vector(1); + versions.add(0, 1); + } else if (v2) { + versions = new Vector(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 versions = new Vector(); + 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."); + } + } + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/AbstractEncodedMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/AbstractEncodedMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/AbstractMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/AbstractMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/DHCommitMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/DHCommitMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/DHKeyMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/DHKeyMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/DataMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/DataMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/ErrorMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/ErrorMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/MysteriousT.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/MysteriousT.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/PlainTextMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/PlainTextMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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 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; + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/QueryMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/QueryMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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 versions; + + // Ctor. + protected QueryMessage(int messageType, List versions) { + super(messageType); + this.versions = versions; + } + + public QueryMessage(List 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; + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/RevealSignatureMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/RevealSignatureMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/SignatureM.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/SignatureM.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/SignatureMessage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/SignatureMessage.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/io/messages/SignatureX.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/io/messages/SignatureX.java Wed May 25 23:13:35 2011 +0200 @@ -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; + } + +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/AuthContext.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/AuthContext.java Wed May 25 23:13:35 2011 +0200 @@ -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 diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/AuthContextImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/AuthContextImpl.java Wed May 25 23:13:35 2011 +0200 @@ -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 versions = new Vector(); + 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; + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/Session.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/Session.java Wed May 25 23:13:35 2011 +0200 @@ -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 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 diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/SessionID.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/SessionID.java Wed May 25 23:13:35 2011 +0200 @@ -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(); + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/SessionImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/SessionImpl.java Wed May 25 23:13:35 2011 +0200 @@ -0,0 +1,792 @@ +/* + * 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 { + + 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 host; + private SessionStatus sessionStatus; + private AuthContext authContext; + private SessionKeys[][] sessionKeys; + private Vector oldMacKeys; + private static Logger logger = Logger + .getLogger(SessionImpl.class.getName()); + + public SessionImpl(SessionID sessionID, OtrEngineHost listener) { + + this.setSessionID(sessionID); + this.setHost(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 setHost(OtrEngineHost host) { + this.host = host; + } + + private OtrEngineHost getHost() { + return host; + } + + 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 getOldMacKeys() { + if (oldMacKeys == null) + oldMacKeys = new Vector(); + 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); + } + + if (m == null) + return msgText; // Propably null or empty. + + 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() + "."); + + getHost().showError(this.getSessionID(), errorMessage.error); + + OtrPolicy policy = getSessionPolicy(); + if (policy.getErrorStartAKE()) { + logger.finest("Error message starts AKE."); + Vector versions = new Vector(); + 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 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(); + 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: + getHost().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); + } + getHost().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 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. + getHost().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()) { + getHost().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. + getHost().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()) + getHost().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 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; + getHost() + .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 tlvs = new Vector(); + tlvs.add(new TLV(1, null)); + + String msg = this.transformSending(null, tlvs); + getHost().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 listeners = new Vector(); + + 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 getHost().getSessionPolicy(getSessionID()); + } + + public KeyPair getLocalKeyPair() { + return getHost().getKeyPair(this.getSessionID()); + } +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/SessionKeys.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/SessionKeys.java Wed May 25 23:13:35 2011 +0200 @@ -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 diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/SessionKeysImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/SessionKeysImpl.java Wed May 25 23:13:35 2011 +0200 @@ -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; +} diff -r c17e4b9ac7de -r defdbe820907 src/net/java/otr4j/session/SessionStatus.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/java/otr4j/session/SessionStatus.java Wed May 25 23:13:35 2011 +0200 @@ -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 +} diff -r c17e4b9ac7de -r defdbe820907 tools/JavaHeaderCheck.regex diff -r c17e4b9ac7de -r defdbe820907 tools/LicenseHeader.txt diff -r c17e4b9ac7de -r defdbe820907 tools/README diff -r c17e4b9ac7de -r defdbe820907 tools/checkstyle.xml diff -r c17e4b9ac7de -r defdbe820907 tools/eclipse_formatter.xml