/*
Copyright © 2013-2014, Silent Circle, LLC.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Any redistribution, use, or modification is done solely for personal
benefit and not for any commercial purpose or for monetary gain
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name Silent Circle nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.silentcircle.keymngrsupport;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
/**
* The SupportProvider implements the application part of SilentCircle's Android key manager provider.
* <p/>
* Applications shall use the functions of {@link KeyManagerSupport} class only.
* <p/>
* Applications <b>must not use</b> the support provider functions: {@code the constructor, onCreate, query, insert,
* update, delete, getType}.
*
* <p/>
* Created by werner on 23.08.13.
*/
public final class SupportProvider extends ContentProvider {
private static final String TAG = "SupportProvider";
private static final Collection<KeyManagerSupport.KeyManagerListener> keyManagerListeners =
new LinkedList<KeyManagerSupport.KeyManagerListener>();
private static final int REGISTER = 1;
private static final int PRIVATE_DATA = 2;
private static final int SHARED_DATA = 3;
private static final int LOCK_REQUEST = 4;
private static final int UNLOCK_REQUEST = 5;
/**
* The MIME type of a {@link # CONTENT_URI} single data item.
*/
private static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/com.silentcircle.keymngr.data";
// The following URLs are for the application's KeyMngr support provider. The URLs are different
// for each application thus we cannot do it statically but we need an initialization during runtime.
private static final String SUPPORT_NAME = ".keymngrsupport";
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
protected static long registerToken;
/*
* The next code section contains the mandatory public provider functions.
*/
public SupportProvider() {
}
@Override
public boolean onCreate() {
return true;
}
/**
* Applications shall not use this function.
*
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final int match = sURIMatcher.match(uri);
switch (match) {
case PRIVATE_DATA:
case SHARED_DATA:
if (selectionArgs.length != 1 || TextUtils.isEmpty(selectionArgs[0]))
return null;
long id;
try {
id = Long.parseLong(selectionArgs[0]);
} catch (NumberFormatException e) {
return null;
}
String data = Base64.encodeToString(getDataForId(id), Base64.DEFAULT);
final MatrixCursor c = new MatrixCursor(new String[]{"data"}, 1);
final MatrixCursor.RowBuilder row = c.newRow();
row.add(data); // return data to KeyManager provider
synchronized (keyManagerListeners) {
for (KeyManagerSupport.KeyManagerListener l : keyManagerListeners)
l.onKeyDataRead();
}
return c;
case LOCK_REQUEST:
synchronized (keyManagerListeners) {
for (KeyManagerSupport.KeyManagerListener l : keyManagerListeners)
l.onKeyManagerLockRequest();
}
break;
case UNLOCK_REQUEST:
synchronized (keyManagerListeners) {
for (KeyManagerSupport.KeyManagerListener l : keyManagerListeners)
l.onKeyManagerUnlockRequest();
}
break;
}
return null;
}
/**
* Applications shall not use this function.
*
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
final int match = sURIMatcher.match(uri);
switch (match) {
case REGISTER:
if (!values.containsKey("token"))
return null;
registerToken = values.getAsLong("token");
if (registerToken == 0) // registration token must never be zero
return null;
return uri;
}
throw new UnsupportedOperationException("Cannot insert URL: " + uri);
}
/**
* Applications shall not use this function.
*
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Cannot update URL: " + uri);
}
/**
* Applications shall not use this function.
*
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
}
/**
* Applications shall not use this function.
*
*/
@Override
public String getType(Uri uri) {
final int match = sURIMatcher.match(uri);
switch (match) {
case REGISTER:
case PRIVATE_DATA:
case SHARED_DATA:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
/**
* Get the key manager's registration token.
*
* @return the token. If the token is {@code 0} then this application is not registered.
*/
protected static long getRegisterToken() {
return registerToken;
}
/**
* Adds a {@code KeyManagerListener} to the list of listeners.
*
* @param l the {@code KeyManagerListener} to add
*/
protected static void addListener(KeyManagerSupport.KeyManagerListener l) {
synchronized (keyManagerListeners) {
if (keyManagerListeners.contains(l)) // don't add twice
return;
keyManagerListeners.add(l);
}
}
/**
* Removes a {@code KeyManagerListener} from the list of listeners.
*
* @param l the {@code KeyManagerListener} to remove
*/
protected static void removeListener(KeyManagerSupport.KeyManagerListener l) {
synchronized (keyManagerListeners) {
keyManagerListeners.remove(l);
}
}
protected static void initialize(String pkgName) {
String AUTHORITY = pkgName + SUPPORT_NAME;
sURIMatcher.addURI(AUTHORITY, "register", REGISTER);
sURIMatcher.addURI(AUTHORITY, "private", PRIVATE_DATA);
sURIMatcher.addURI(AUTHORITY, "shared", SHARED_DATA);
sURIMatcher.addURI(AUTHORITY, "lock", LOCK_REQUEST);
sURIMatcher.addURI(AUTHORITY, "unlock", UNLOCK_REQUEST);
}
private static ConcurrentHashMap<Long, byte[]> preparedStorage = new ConcurrentHashMap<Long, byte[]>(4, 0.75f, 3);
protected static long prepareForStorage(byte[] data) {
SecureRandom prng = new SecureRandom();
int r = prng.nextInt();
long id = r << 31;
r = prng.nextInt();
id |= id | r;
preparedStorage.put(id, data);
return id;
}
private byte[] getDataForId(long id) {
byte[] data = preparedStorage.get(id);
preparedStorage.remove(id);
return data;
}
}