/*
* Copyright 2011 David Brazdil
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.ac.cam.db538.cryptosms.state;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import uk.ac.cam.db538.cryptosms.MyApplication;
import uk.ac.cam.db538.cryptosms.crypto.Encryption;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.BadInputException;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.ConnectionListener;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.DeclinedException;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.NotConnectedException;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.PKIErrorException;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.PKInotInstalledException;
import uk.ac.cam.dje38.PKIwrapper.PKIwrapper.TimeoutException;
/*
* Static class that takes care of PKI login session
*/
public class Pki {
private static final String INTENT_PKI_LOGIN = "uk.ac.cam.dje38.pki.login";
private static final String INTENT_PKI_LOGOUT = "uk.ac.cam.dje38.pki.logout";
private static final int TIMEOUT_DEFAULT = 60;
private static final int TIMEOUT_LOGIN_CHECK = 5;
private static PKIwrapper mPki = null;
private static Context mContext = null;
private static byte[] mMasterKey = null;
private static final String KEY_STORAGE = "CRYPTOSMS_MASTER_KEY";
private static boolean mMissing = false;
private static boolean mLoggedIn = false;
public static class PkiNotReadyException extends Exception {
private static final long serialVersionUID = 5247215307724480706L;
public PkiNotReadyException() {
super("PKI is not available (either not connected or logged out)");
}
}
/**
* Initializes all PKI related stuff and calls connect()
*
* @param context the context
*/
public static void init(Context context) {
mContext = context;
if (mPki != null) return;
IntentFilter filterLogin = new IntentFilter();
filterLogin.addAction(INTENT_PKI_LOGIN);
filterLogin.addAction(INTENT_PKI_LOGOUT);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(INTENT_PKI_LOGIN)) {
mPki.setTimeout(TIMEOUT_LOGIN_CHECK);
try {
Pki.getMasterKey(true); // calls authorize
Pki.setLoggedIn(true);
} catch (PkiNotReadyException e) {
// if this happens then setLoggedIn didn't
}
mPki.setTimeout(TIMEOUT_DEFAULT);
} else if (intent.getAction().equals(INTENT_PKI_LOGOUT))
Pki.setLoggedIn(false);
}
}, filterLogin);
IntentFilter filterInstall = new IntentFilter();
filterInstall.addAction(Intent.ACTION_PACKAGE_ADDED);
filterInstall.addAction(Intent.ACTION_PACKAGE_REMOVED);
filterInstall.addCategory("android.intent.category.DEFAULT");
filterInstall.addDataScheme("package");
filterInstall.addDataPath("uk.ac.cam.dje38.pki", 0);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED)) {
if (intent.getDataString().equals("package:" + MyApplication.PKI_PACKAGE))
Pki.setMissing();
} else if (intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
if (intent.getDataString().equals("package:" + MyApplication.PKI_PACKAGE))
Pki.connect();
}
}
}, filterInstall);
try {
mPki = new PKIwrapper(mContext);
} catch (InterruptedException e1) {
// ignore
}
connect();
}
public static PKIwrapper getPkiWrapper() {
return mPki;
}
/**
* Connects to PKIwrapper
*/
public static void connect() {
try {
if (mPki != null) {
mPki.setTimeout(TIMEOUT_DEFAULT);
mPki.connect(new ConnectionListener() {
@Override
public void onConnectionDeclined() {
Log.d(MyApplication.APP_TAG, "onConnectionDeclined");
}
@Override
public void onConnectionFailed() {
Log.d(MyApplication.APP_TAG, "onConnectionFailed");
}
@Override
public void onConnectionTimeout() {
Log.d(MyApplication.APP_TAG, "onConnectionTimeout");
}
@Override
public void onDisconnect() {
Log.d(MyApplication.APP_TAG, "onDisconnect");
if (!State.isInFatalState()) {
setLoggedIn(false);
State.notifyDisconnect();
}
}
@Override
public void onConnect() {
Log.d(MyApplication.APP_TAG, "onConnect");
State.notifyConnect();
setLoggedIn(false, true);
login(false);
}
});
mMissing = false;
}
} catch (PKInotInstalledException e) {
mMissing = true;
State.notifyPkiMissing();
}
}
public static boolean isConnected() {
return !State.isInFatalState() && mPki != null && mPki.isConnected();
}
/**
* Call login on PKI
*
* @param force login synchronously
*/
public static void login(boolean force) {
Log.d(MyApplication.APP_TAG, "Login: " + (isConnected() ? "connected" : "disconnected") + ", " + (isLoggedIn() ? "logged in" : "logged out") + ", " + (force ? "forced" : "unforced") );
if (isConnected() && !isLoggedIn()) {
if (force) {
try {
mPki.setTimeout(TIMEOUT_DEFAULT);
setLoggedIn(mPki.authorise());
} catch (TimeoutException e) {
} catch (PKIErrorException e) {
} catch (NotConnectedException e) {
}
} else {
setLoggedIn(false);
Intent intent = new Intent(MyApplication.PKI_LOGIN);
mContext.startService(intent);
}
} else if (isLoggedIn())
State.notifyLogin();
}
/**
* Disconnect from PKI
*/
public static void disconnect() {
if (mPki != null)
try {
mPki.disconnect();
} catch (TimeoutException e) {
} catch (PKIErrorException e) {
}
}
/**
* PKI is missing
*/
static void setMissing() {
Log.d(MyApplication.APP_TAG, "PKI missing");
mMissing = true;
State.notifyPkiMissing();
}
/*
* Is PKI missing
*/
static boolean isMissing() {
return mMissing;
}
public static boolean isLoggedIn() {
return isConnected() && mLoggedIn;
}
static void setLoggedIn(boolean value) {
setLoggedIn(value, false);
}
/**
* Sets whether the user is logged in
*
* @param value
* @param forceNotify send notification to listeners
*/
static void setLoggedIn(boolean value, boolean forceNotify) {
if (mLoggedIn != value || forceNotify) {
mLoggedIn = value;
if (mLoggedIn) {
State.notifyLogin();
} else {
// mMasterKey = null; // forget the master key
State.notifyLogout();
}
}
}
/**
* Gets the master key to storage file.
*
* @param forceLogIn
* @return the master key
* @throws PkiNotReadyException
*/
public static byte[] getMasterKey(boolean forceLogIn) throws PkiNotReadyException {
return getMasterKey(forceLogIn, true);
}
/**
* Gets the master key to storage file.
*
* @param forceLogIn
* @param generateAllow if the key is not stored, allow it to be generated
* @return the master key
* @throws PkiNotReadyException
*/
public static byte[] getMasterKey(boolean forceLogIn, boolean generateAllow) throws PkiNotReadyException {
if (mMasterKey != null)
return mMasterKey;
else if (isLoggedIn()) {
try {
if (mPki.hasDataStore(KEY_STORAGE)) {
mMasterKey = mPki.getDataStore(KEY_STORAGE);
if (mMasterKey == null)
throw new PkiNotReadyException();
else
return mMasterKey;
} else if (generateAllow) {
mPki.setDataStore(KEY_STORAGE, Encryption.getEncryption().generateRandomData(Encryption.SYM_KEY_LENGTH));
return getMasterKey(forceLogIn, false);
} else
throw new PkiNotReadyException();
} catch (NotConnectedException e) {
throw new PkiNotReadyException();
} catch (TimeoutException e) {
throw new PkiNotReadyException();
} catch (DeclinedException e) {
throw new PkiNotReadyException();
} catch (PKIErrorException e) {
throw new PkiNotReadyException();
} catch (BadInputException e) {
throw new PkiNotReadyException();
}
} else if (isConnected() && forceLogIn) {
Pki.login(true);
// no need to alter our own login information - broadcast will be sent by PKI
return getMasterKey(false, generateAllow);
} else
throw new PkiNotReadyException();
}
}