package info.guardianproject.otr;
// Originally: package com.zadov.beem;
import info.guardianproject.otr.app.im.ImService;
import info.guardianproject.otr.app.im.app.SmpResponseActivity;
import info.guardianproject.otr.app.im.engine.Message;
import info.guardianproject.otr.app.im.service.ImConnectionAdapter;
import info.guardianproject.otr.app.im.service.ImServiceConstants;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Hashtable;
import java.util.List;
import net.java.otr4j.OtrEngineImpl;
import net.java.otr4j.OtrEngineListener;
import net.java.otr4j.OtrException;
import net.java.otr4j.OtrKeyManager;
import net.java.otr4j.OtrPolicy;
import net.java.otr4j.OtrPolicyImpl;
import net.java.otr4j.session.OtrSm;
import net.java.otr4j.session.OtrSm.OtrSmEngineHost;
import net.java.otr4j.session.Session;
import net.java.otr4j.session.SessionID;
import net.java.otr4j.session.SessionStatus;
import net.java.otr4j.session.TLV;
import android.content.Intent;
/*
* OtrChatManager keeps track of the status of chats and their OTR stuff
*/
public class OtrChatManager implements OtrEngineListener, OtrSmEngineHost {
//the singleton instance
private static OtrChatManager mInstance;
private OtrEngineHostImpl mOtrEngineHost;
private OtrEngineImpl mOtrEngine;
private Hashtable<String, SessionID> mSessions;
private Hashtable<SessionID, OtrSm> mOtrSms;
private ImService mContext;
private OtrKeyManager mOtrKeyManager;
private OtrChatManager(int otrPolicy, ImService context, OtrKeyManager otrKeyManager) throws Exception {
mOtrEngineHost = new OtrEngineHostImpl(new OtrPolicyImpl(otrPolicy),
context, otrKeyManager);
mOtrEngine = new OtrEngineImpl(mOtrEngineHost);
mOtrEngine.addOtrEngineListener(this);
mSessions = new Hashtable<String, SessionID>();
mOtrSms = new Hashtable<SessionID, OtrSm>();
mContext = context;
mOtrKeyManager = otrKeyManager;
}
public static synchronized OtrChatManager getInstance(int otrPolicy, ImService context, OtrKeyManager otrKeyManager)
throws Exception {
if (mInstance == null) {
mInstance = new OtrChatManager(otrPolicy, context,otrKeyManager);
}
return mInstance;
}
public void addConnection(ImConnectionAdapter imConnectionAdapter) {
mOtrEngineHost.addConnection(imConnectionAdapter);
}
public void removeConnection(ImConnectionAdapter imConnectionAdapter) {
mOtrEngineHost.removeConnection(imConnectionAdapter);
}
public void addOtrEngineListener(OtrEngineListener oel) {
mOtrEngine.addOtrEngineListener(oel);
}
public void setPolicy(int otrPolicy) {
mOtrEngineHost.setSessionPolicy(new OtrPolicyImpl(otrPolicy));
}
public OtrKeyManager getKeyManager() {
return mOtrEngineHost.getKeyManager();
}
public static String processUserId(String userId) {
String result = userId.split(":")[0]; //remove any port indication in the username
result = userId.split("/")[0];
return result;
}
public static String processResource(String userId) {
String[] splits = userId.split("/", 2);
if (splits.length > 1)
return splits[1];
else
return "UNKNOWN";
}
public SessionID getSessionId(String localUserId, String remoteUserId) {
String sessionIdKey = processUserId(localUserId) + "+" + processUserId(remoteUserId);
SessionID sessionId = mSessions.get(sessionIdKey);
if (sessionId == null ||
(!sessionId.getFullUserID().equals(remoteUserId) &&
remoteUserId.contains("/"))) {
// Remote has changed (either different presence, or from generic JID to specific presence),
// or we didn't have a session yet.
// Create or replace sessionId with one that is specific to the new presence.
sessionId = new SessionID(processUserId(localUserId), remoteUserId, "XMPP");
mSessions.put(sessionIdKey, sessionId);
}
return sessionId;
}
/**
* Tell if the session represented by a local user account and a remote user
* account is currently encrypted or not.
*
* @param localUserId
* @param remoteUserId
* @return state
*/
public SessionStatus getSessionStatus(String localUserId, String remoteUserId) {
SessionID sessionId = getSessionId(localUserId, remoteUserId);
if (sessionId == null)
return null;
return mOtrEngine.getSessionStatus(sessionId);
}
public SessionStatus getSessionStatus(SessionID sessionId) {
return mOtrEngine.getSessionStatus(sessionId);
}
public void refreshSession(String localUserId, String remoteUserId) {
try {
mOtrEngine.refreshSession(getSessionId(localUserId, remoteUserId));
} catch (OtrException e) {
OtrDebugLogger.log("refreshSession", e);
}
}
/**
* Start a new OTR encryption session for the chat session represented by a
* local user address and a remote user address.
*
* @param localUserId i.e. the account of the user of this phone
* @param remoteUserId i.e. the account that this user is talking to
*/
public SessionID startSession(String localUserId, String remoteUserId) {
try {
SessionID sessionId = getSessionId(localUserId, remoteUserId);
mOtrEngine.startSession(sessionId);
return sessionId;
} catch (OtrException e) {
OtrDebugLogger.log("startSession", e);
}
return null;
}
public void endSession(String localUserId, String remoteUserId) {
try {
SessionID sessionId = getSessionId(localUserId, remoteUserId);
mOtrEngine.endSession(sessionId);
} catch (OtrException e) {
OtrDebugLogger.log("endSession", e);
}
}
public void status(String localUserId, String remoteUserId) {
mOtrEngine.getSessionStatus(getSessionId(localUserId, remoteUserId)).toString();
}
public String decryptMessage(String localUserId, String remoteUserId, String msg, List<TLV> tlvs) throws OtrException {
String plain = null;
SessionID sessionId = getSessionId(localUserId, remoteUserId);
OtrDebugLogger.log("session status: " + mOtrEngine.getSessionStatus(sessionId));
if (mOtrEngine != null && sessionId != null) {
mOtrEngineHost.putSessionResource(sessionId, processResource(remoteUserId));
plain = mOtrEngine.transformReceiving(sessionId, msg, tlvs);
OtrSm otrSm = mOtrSms.get(sessionId);
if (otrSm != null) {
List<TLV> smTlvs = otrSm.getPendingTlvs();
if (smTlvs != null) {
String encrypted = mOtrEngine.transformSending(sessionId, "", smTlvs);
mOtrEngineHost.injectMessage(sessionId, encrypted);
}
}
if (plain != null && plain.length() == 0)
return null;
}
return plain;
}
public void transformSending(Message message) {
transformSending(message, false, null);
}
public void transformSending(Message message, boolean isResponse, byte[] data) {
String localUserId = message.getFrom().getAddress();
String remoteUserId = message.getTo().getAddress();
String body = message.getBody();
SessionID sessionId = getSessionId(localUserId, remoteUserId);
if (mOtrEngine != null && sessionId != null) {
SessionStatus sessionStatus = mOtrEngine.getSessionStatus(sessionId);
OtrDebugLogger.log("session status: " + sessionStatus);
try {
OtrPolicy sessionPolicy = getSessionPolicy(sessionId);
if (sessionStatus != SessionStatus.PLAINTEXT || sessionPolicy.getRequireEncryption()) {
body = mOtrEngine.transformSending(sessionId, body, isResponse, data);
message.setTo(mOtrEngineHost.appendSessionResource(sessionId, message.getTo()));
} else if (sessionStatus == SessionStatus.PLAINTEXT && sessionPolicy.getAllowV2()
&& sessionPolicy.getSendWhitespaceTag()) {
// Work around asmack not sending whitespace tag for auto discovery
body += " \t \t\t\t\t \t \t \t \t \t \t \t\t \t ";
}
} catch (OtrException e) {
OtrDebugLogger.log("error encrypting", e);
}
}
message.setBody(body);
}
@Override
public void sessionStatusChanged(SessionID sessionID) {
SessionStatus sStatus = mOtrEngine.getSessionStatus(sessionID);
OtrDebugLogger.log("session status changed: " + sStatus);
final Session session = mOtrEngine.getSession(sessionID);
OtrSm otrSm = mOtrSms.get(sessionID);
if (sStatus == SessionStatus.ENCRYPTED) {
PublicKey remoteKey = mOtrEngine.getRemotePublicKey(sessionID);
mOtrEngineHost.storeRemoteKey(sessionID, remoteKey);
if (otrSm == null) {
// SMP handler - make sure we only add this once per session!
otrSm = new OtrSm(session, mOtrEngineHost.getKeyManager(),
sessionID, OtrChatManager.this);
session.addTlvHandler(otrSm);
mOtrSms.put(sessionID, otrSm);
}
} else if (sStatus == SessionStatus.PLAINTEXT) {
if (otrSm != null) {
session.removeTlvHandler(otrSm);
mOtrSms.remove(sessionID);
}
mOtrEngineHost.removeSessionResource(sessionID);
} else if (sStatus == SessionStatus.FINISHED) {
// Do nothing. The user must take affirmative action to
// restart or end the session, so that they don't send
// plaintext by mistake.
}
}
public String getLocalKeyFingerprint(String localUserId, String remoteUserId) {
return mOtrEngineHost.getLocalKeyFingerprint(getSessionId(localUserId, remoteUserId));
}
@Override
public void injectMessage(SessionID sessionID, String msg) {
mOtrEngineHost.injectMessage(sessionID, msg);
}
@Override
public void showWarning(SessionID sessionID, String warning) {
mOtrEngineHost.showWarning(sessionID, warning);
}
@Override
public void showError(SessionID sessionID, String error) {
mOtrEngineHost.showError(sessionID, error);
}
@Override
public OtrPolicy getSessionPolicy(SessionID sessionID) {
return mOtrEngineHost.getSessionPolicy(sessionID);
}
@Override
public KeyPair getKeyPair(SessionID sessionID) {
return mOtrEngineHost.getKeyPair(sessionID);
}
@Override
public void askForSecret(SessionID sessionID, String question) {
Intent dialog = new Intent(mContext.getApplicationContext(), SmpResponseActivity.class);
dialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dialog.putExtra("q", question);
dialog.putExtra("sid", sessionID.getUserID());
ImConnectionAdapter connection = mOtrEngineHost.findConnection(sessionID.getAccountID());
if (connection == null) {
OtrDebugLogger.log("Could ask for secret - no connection for " + sessionID.getAccountID());
return;
}
dialog.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, connection.getProviderId());
mContext.getApplicationContext().startActivity(dialog);
}
public void respondSmp(SessionID sessionID, String secret) throws OtrException {
OtrSm otrSm = mOtrSms.get(sessionID);
List<TLV> tlvs;
if (otrSm == null) {
showError(sessionID, "Could not respond to verification because conversation is not encrypted");
return;
}
tlvs = otrSm.initRespondSmp(null, secret, false);
String encrypted = mOtrEngine.transformSending(sessionID, "", tlvs);
mOtrEngineHost.injectMessage(sessionID, encrypted);
}
public void initSmp(SessionID sessionID, String question, String secret) throws OtrException {
OtrSm otrSm = mOtrSms.get(sessionID);
List<TLV> tlvs;
if (otrSm == null) {
showError(sessionID, "Could not perform verification because conversation is not encrypted");
return;
}
tlvs = otrSm.initRespondSmp(question, secret, true);
String encrypted = mOtrEngine.transformSending(sessionID, "", tlvs);
mOtrEngineHost.injectMessage(sessionID, encrypted);
}
public void abortSmp(SessionID sessionID) throws OtrException {
OtrSm otrSm = mOtrSms.get(sessionID);
if (otrSm == null)
return;
List<TLV> tlvs = otrSm.abortSmp();
String encrypted = mOtrEngine.transformSending(sessionID, "", tlvs);
mOtrEngineHost.injectMessage(sessionID, encrypted);
}
public OtrKeyManager getOtrKeyManager ()
{
return mOtrKeyManager;
}
}