Merge branch 'master' into improve-file

This commit is contained in:
mar-v-in 2014-06-18 21:43:54 +02:00
commit d9c937bbe7
94 changed files with 4014 additions and 2111 deletions

3
.gitmodules vendored
View File

@ -25,6 +25,9 @@
[submodule "extern/openkeychain-api-lib"]
path = extern/openkeychain-api-lib
url = https://github.com/open-keychain/openkeychain-api-lib.git
[submodule "extern/SuperToasts"]
path = extern/SuperToasts
url = https://github.com/open-keychain/SuperToasts.git
[submodule "extern/dnsjava"]
path = extern/dnsjava
url = https://github.com/open-keychain/dnsjava.git

View File

@ -4,13 +4,13 @@ before_install:
# Install base Android SDK
- sudo apt-get update -qq
- if [ `uname -m` = x86_64 ]; then sudo apt-get install -qq --force-yes libgd2-xpm lib32z1 lib32stdc++6; fi
- wget http://dl.google.com/android/android-sdk_r22.3-linux.tgz
- tar xzf android-sdk_r22.3-linux.tgz
- wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz
- tar xzf android-sdk_r22.6.2-linux.tgz
- export ANDROID_HOME=$PWD/android-sdk-linux
- export PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
# Install required Android components.
- echo "y" | android update sdk -a --filter build-tools-19.0.3,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
- echo "y" | android update sdk -a --filter build-tools-19.1.0,android-19,platform-tools,extra-android-support,extra-android-m2repository --no-ui --force
install: echo "Installation done"
script: gradle assemble -S -q

View File

@ -1,12 +1,12 @@
apply plugin: 'android'
apply plugin: 'android-test'
//apply plugin: 'android-test'
sourceSets {
androidTest {
java.srcDir file('src/test/java')
//androidTest {
//java.srcDir file('src/test/java')
// configure the set of classes for JUnit tests
// include '**/*Test.class'
}
//}
}
dependencies {
@ -26,10 +26,11 @@ dependencies {
compile project(':extern:spongycastle:pkix')
compile project(':extern:spongycastle:prov')
compile project(':extern:AppMsg:library')
compile project(':extern:SuperToasts:supertoasts')
compile project(':extern:dnsjava')
// Dependencies for the `instrumentTest` task, make sure to list all your global dependencies here as well
androidTestCompile 'junit:junit:4.10'
androidTestCompile 'junit:junit:4.11'
androidTestCompile 'org.robolectric:robolectric:2.3'
androidTestCompile 'com.squareup:fest-android:1.0.8'
androidTestCompile 'com.google.android:android:4.1.1.4'
@ -47,11 +48,13 @@ dependencies {
androidTestCompile project(':extern:spongycastle:pkix')
androidTestCompile project(':extern:spongycastle:prov')
androidTestCompile project(':extern:AppMsg:library')
androidTestCompile project(':extern:SuperToasts:supertoasts')
}
android {
compileSdkVersion 19
buildToolsVersion "19.0.3"
buildToolsVersion "19.1"
defaultConfig {
minSdkVersion 9

View File

@ -445,6 +445,12 @@
</intent-filter>
</service>
<activity
android:name=".ui.LogDisplayActivity"
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_log_display"
android:exported="false" />
<service android:name=".service.DummyAccountService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator"/>

View File

@ -22,7 +22,6 @@ import android.accounts.AccountManager;
import android.content.*;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Patterns;
import org.sufficientlysecure.keychain.Constants;
@ -32,10 +31,7 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
public class ContactHelper {
@ -43,12 +39,26 @@ public class ContactHelper {
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.FINGERPRINT,
KeychainContract.KeyRings.KEY_ID,
KeychainContract.KeyRings.MASTER_KEY_ID};
public static final String[] RAW_CONTACT_ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID};
public static final String FIND_RAW_CONTACT_SELECTION =
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?";
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.EXPIRY,
KeychainContract.KeyRings.IS_REVOKED};
public static final String[] USER_IDS_PROJECTION = new String[]{
KeychainContract.UserIds.USER_ID
};
public static final List<String> getMailAccounts(Context context) {
public static final String NON_REVOKED_SELECTION = KeychainContract.UserIds.IS_REVOKED + "=0";
public static final String[] ID_PROJECTION = new String[]{ContactsContract.RawContacts._ID};
public static final String[] SOURCE_ID_PROJECTION = new String[]{ContactsContract.RawContacts.SOURCE_ID};
public static final String ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION =
ContactsContract.RawContacts.ACCOUNT_TYPE + "=? AND " + ContactsContract.RawContacts.SOURCE_ID + "=?";
public static final String ACCOUNT_TYPE_SELECTION = ContactsContract.RawContacts.ACCOUNT_TYPE + "=?";
public static final String RAW_CONTACT_AND_MIMETYPE_SELECTION =
ContactsContract.Data.RAW_CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?";
public static final String ID_SELECTION = ContactsContract.RawContacts._ID + "=?";
public static List<String> getMailAccounts(Context context) {
final Account[] accounts = AccountManager.get(context).getAccounts();
final Set<String> emailSet = new HashSet<String>();
for (Account account : accounts) {
@ -78,7 +88,8 @@ public class ContactHelper {
}
public static Uri dataUriFromContactUri(Context context, Uri contactUri) {
Cursor contactMasterKey = context.getContentResolver().query(contactUri, new String[]{ContactsContract.Data.DATA2}, null, null, null, null);
Cursor contactMasterKey = context.getContentResolver().query(contactUri,
new String[]{ContactsContract.Data.DATA2}, null, null, null, null);
if (contactMasterKey != null) {
if (contactMasterKey.moveToNext()) {
return KeychainContract.KeyRings.buildGenericKeyRingUri(contactMasterKey.getLong(0));
@ -88,62 +99,178 @@ public class ContactHelper {
return null;
}
/**
* Write the current Keychain to the contact db
*/
public static void writeKeysToContacts(Context context) {
ContentResolver resolver = context.getContentResolver();
Set<String> contactFingerprints = getRawContactFingerprints(resolver);
// Load all Keys from OK
Cursor cursor = resolver.query(KeychainContract.KeyRings.buildUnifiedKeyRingsUri(), KEYS_TO_CONTACT_PROJECTION,
null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
String[] userId = KeyRing.splitUserId(cursor.getString(0));
String[] primaryUserId = KeyRing.splitUserId(cursor.getString(0));
String fingerprint = PgpKeyHelper.convertFingerprintToHex(cursor.getBlob(1));
contactFingerprints.remove(fingerprint);
String keyIdShort = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(2));
long masterKeyId = cursor.getLong(3);
boolean isExpired = !cursor.isNull(4) && new Date(cursor.getLong(4) * 1000).before(new Date());
boolean isRevoked = cursor.getInt(5) > 0;
int rawContactId = findRawContactId(resolver, fingerprint);
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// Do not store expired or revoked keys in contact db - and remove them if they already exist
if (isExpired || isRevoked) {
if (rawContactId != -1) {
resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ID_SELECTION,
new String[]{Integer.toString(rawContactId)});
}
} else {
// Create a new rawcontact with corresponding key if it does not exist yet
if (rawContactId == -1) {
insertContact(ops, context, fingerprint);
writeContactKey(ops, context, rawContactId, masterKeyId, keyIdShort);
}
// We always update the display name (which is derived from primary user id)
// and email addresses from user id
writeContactDisplayName(ops, rawContactId, primaryUserId[0]);
writeContactEmail(ops, resolver, rawContactId, masterKeyId);
try {
resolver.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (Exception e) {
Log.w(Constants.TAG, e);
}
}
}
cursor.close();
}
// Delete fingerprints that are no longer present in OK
for (String fingerprint : contactFingerprints) {
resolver.delete(ContactsContract.RawContacts.CONTENT_URI, ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION,
new String[]{Constants.PACKAGE_NAME, fingerprint});
}
}
/**
* @return a set of all key fingerprints currently present in the contact db
*/
private static Set<String> getRawContactFingerprints(ContentResolver resolver) {
HashSet<String> result = new HashSet<String>();
Cursor fingerprints = resolver.query(ContactsContract.RawContacts.CONTENT_URI, SOURCE_ID_PROJECTION,
ACCOUNT_TYPE_SELECTION, new String[]{Constants.PACKAGE_NAME}, null);
if (fingerprints != null) {
while (fingerprints.moveToNext()) {
result.add(fingerprints.getString(0));
}
fingerprints.close();
}
return result;
}
/**
* This will search the contact db for a raw contact with a given fingerprint
*
* @return raw contact id or -1 if not found
*/
private static int findRawContactId(ContentResolver resolver, String fingerprint) {
int rawContactId = -1;
Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, RAW_CONTACT_ID_PROJECTION,
FIND_RAW_CONTACT_SELECTION, new String[]{Constants.PACKAGE_NAME, fingerprint}, null, null);
Cursor raw = resolver.query(ContactsContract.RawContacts.CONTENT_URI, ID_PROJECTION,
ACCOUNT_TYPE_AND_SOURCE_ID_SELECTION, new String[]{Constants.PACKAGE_NAME, fingerprint}, null, null);
if (raw != null) {
if (raw.moveToNext()) {
rawContactId = raw.getInt(0);
}
raw.close();
}
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
if (rawContactId == -1) {
return rawContactId;
}
/**
* Creates a empty raw contact with a given fingerprint
*/
private static void insertContact(ArrayList<ContentProviderOperation> ops, Context context, String fingerprint) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, context.getString(R.string.app_name))
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, Constants.PACKAGE_NAME)
.withValue(ContactsContract.RawContacts.SOURCE_ID, fingerprint)
.build());
if (userId[0] != null) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, userId[0])
.build());
}
if (userId[1] != null) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, userId[1])
.build());
}
ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
/**
* Adds a key id to the given raw contact.
* <p/>
* This creates the link to OK in contact details
*/
private static void writeContactKey(ArrayList<ContentProviderOperation> ops, Context context, int rawContactId,
long masterKeyId, String keyIdShort) {
ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI), rawContactId)
.withValue(ContactsContract.Data.MIMETYPE, Constants.CUSTOM_CONTACT_DATA_MIME_TYPE)
.withValue(ContactsContract.Data.DATA1, String.format(context.getString(R.string.contact_show_key), keyIdShort))
.withValue(ContactsContract.Data.DATA1, context.getString(R.string.contact_show_key, keyIdShort))
.withValue(ContactsContract.Data.DATA2, masterKeyId)
.build());
}
try {
resolver.applyBatch(ContactsContract.AUTHORITY, ops);
} catch (RemoteException e) {
e.printStackTrace();
} catch (OperationApplicationException e) {
e.printStackTrace();
/**
* Write all known email addresses of a key (derived from user ids) to a given raw contact
*/
private static void writeContactEmail(ArrayList<ContentProviderOperation> ops, ContentResolver resolver,
int rawContactId, long masterKeyId) {
ops.add(selectByRawContactAndItemType(ContentProviderOperation.newDelete(ContactsContract.Data.CONTENT_URI),
rawContactId, ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE).build());
Cursor ids = resolver.query(KeychainContract.UserIds.buildUserIdsUri(Long.toString(masterKeyId)),
USER_IDS_PROJECTION, NON_REVOKED_SELECTION, null, null);
if (ids != null) {
while (ids.moveToNext()) {
String[] userId = KeyRing.splitUserId(ids.getString(0));
if (userId[1] != null) {
ops.add(referenceRawContact(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI),
rawContactId)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.Email.DATA, userId[1])
.build());
}
}
cursor.close();
ids.close();
}
}
private static void writeContactDisplayName(ArrayList<ContentProviderOperation> ops, int rawContactId,
String displayName) {
if (displayName != null) {
ops.add(insertOrUpdateForRawContact(ContactsContract.Data.CONTENT_URI, rawContactId,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
.build());
}
}
private static ContentProviderOperation.Builder referenceRawContact(ContentProviderOperation.Builder builder,
int rawContactId) {
return rawContactId == -1 ?
builder.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) :
builder.withValue(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
}
private static ContentProviderOperation.Builder insertOrUpdateForRawContact(Uri uri, int rawContactId,
String itemType) {
if (rawContactId == -1) {
return referenceRawContact(ContentProviderOperation.newInsert(uri), rawContactId).withValue(
ContactsContract.Data.MIMETYPE, itemType);
} else {
return selectByRawContactAndItemType(ContentProviderOperation.newUpdate(uri), rawContactId, itemType);
}
}
private static ContentProviderOperation.Builder selectByRawContactAndItemType(
ContentProviderOperation.Builder builder, int rawContactId, String itemType) {
return builder.withSelection(RAW_CONTACT_AND_MIMETYPE_SELECTION,
new String[]{Integer.toString(rawContactId), itemType});
}
}

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.helper;
import android.content.Context;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -67,4 +68,12 @@ public class OtherHelper {
return sb;
}
public static int dpToPx(Context context, int dp) {
return (int) ((dp * context.getResources().getDisplayMetrics().density) + 0.5);
}
public static int pxToDp(Context context, int px) {
return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5);
}
}

View File

@ -251,7 +251,7 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
this.mKeyId = key.getKeyId();
this.mKeyIdHex = PgpKeyHelper.convertKeyIdToHex(mKeyId);
this.mRevoked = key.maybeRevoked();
this.mRevoked = key.isRevoked();
this.mFingerprintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
this.mBitStrength = key.getBitStrength();
final int algorithm = key.getAlgorithm();

View File

@ -3,7 +3,7 @@ package org.sufficientlysecure.keychain.keyimport;
import android.os.Parcel;
import android.os.Parcelable;
/** This is a trivial wrapper around UncachedKeyRing which implements Parcelable. It exists
/** This is a trivial wrapper around keyring bytes which implements Parcelable. It exists
* for the sole purpose of keeping spongycastle and android imports in separate packages.
*/
public class ParcelableKeyRing implements Parcelable {

View File

@ -1,169 +0,0 @@
package org.sufficientlysecure.keychain.pgp;
import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.R;
import java.util.ArrayList;
/** Represent the result of an operation.
*
* This class holds a result and the log of an operation. It can be subclassed
* to include typed additional information specific to the operation. To keep
* the class structure (somewhat) simple, this class contains an exhaustive
* list (ie, enum) of all possible log types, which should in all cases be tied
* to string resource ids.
*
*/
public class OperationResultParcel implements Parcelable {
/** Holds the overall result. A value of 0 is considered a success, all
* other values may represent failure or varying degrees of success. */
final int mResult;
/// A list of log entries tied to the operation result.
final ArrayList<LogEntryParcel> mLog;
public OperationResultParcel(int result, ArrayList<LogEntryParcel> log) {
mResult = result;
mLog = log;
}
public OperationResultParcel(Parcel source) {
mResult = source.readInt();
mLog = source.createTypedArrayList(LogEntryParcel.CREATOR);
}
public boolean isSuccessful() {
return mResult == 0;
}
/** One entry in the log. */
public static class LogEntryParcel implements Parcelable {
final LogLevel mLevel;
final LogType mType;
final String[] mParameters;
final int mIndent;
public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) {
mLevel = level;
mType = type;
mParameters = parameters;
mIndent = indent;
}
public LogEntryParcel(LogLevel level, LogType type, String[] parameters) {
this(level, type, parameters, 0);
}
public LogEntryParcel(Parcel source) {
mLevel = LogLevel.values()[source.readInt()];
mType = LogType.values()[source.readInt()];
mParameters = source.createStringArray();
mIndent = source.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mLevel.ordinal());
dest.writeInt(mType.ordinal());
dest.writeStringArray(mParameters);
dest.writeInt(mIndent);
}
public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() {
public LogEntryParcel createFromParcel(final Parcel source) {
return new LogEntryParcel(source);
}
public LogEntryParcel[] newArray(final int size) {
return new LogEntryParcel[size];
}
};
}
public static enum LogType {
MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch),
MSG_IP_BAD_TYPE_SECRET (R.string.msg_ip_bad_type_secret),
MSG_IP_DELETE_OLD_FAIL (R.string.msg_ip_delete_old_fail),
MSG_IP_DELETE_OLD_OK (R.string.msg_ip_delete_old_ok),
MSG_IP_ENCODE_FAIL (R.string.msg_ip_encode_fail),
MSG_IP_FAIL_IO_EXC (R.string.msg_ip_fail_io_exc),
MSG_IP_FAIL_OP_EX (R.string.msg_ip_fail_op_ex),
MSG_IP_FAIL_REMOTE_EX (R.string.msg_ip_fail_remote_ex),
MSG_IP_IMPORTING (R.string.msg_ip_importing),
MSG_IP_INSERT_KEYRING (R.string.msg_ip_insert_keyring),
MSG_IP_INSERT_SUBKEY (R.string.msg_ip_insert_subkey),
MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_subkeys),
MSG_IP_PRESERVING_SECRET (R.string.msg_ip_preserving_secret),
MSG_IP_REINSERT_SECRET (R.string.msg_ip_reinsert_secret),
MSG_IP_SUCCESS (R.string.msg_ip_success),
MSG_IP_TRUST_RETRIEVE (R.string.msg_ip_trust_retrieve),
MSG_IP_TRUST_USING (R.string.msg_ip_trust_using),
MSG_IP_TRUST_USING_SEC (R.string.msg_ip_trust_using_sec),
MSG_IP_UID_CERT_BAD (R.string.msg_ip_uid_cert_bad),
MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error),
MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good),
MSG_IP_UID_CERTS_UNKNOWN (R.string.msg_ip_uid_certs_unknown),
MSG_IP_UID_CLASSIFYING (R.string.msg_ip_uid_classifying),
MSG_IP_UID_INSERT (R.string.msg_ip_uid_insert),
MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing),
MSG_IP_UID_SELF_BAD (R.string.msg_ip_uid_self_bad),
MSG_IP_UID_SELF_GOOD (R.string.msg_ip_uid_self_good),
MSG_IP_UID_SELF_IGNORING_OLD (R.string.msg_ip_uid_self_ignoring_old),
MSG_IP_UID_SELF_NEWER (R.string.msg_ip_uid_self_newer),
MSG_IS_BAD_TYPE_PUBLIC (R.string.msg_is_bad_type_public),
MSG_IS_IMPORTING (R.string.msg_is_importing),
MSG_IS_IMPORTING_SUBKEYS (R.string.msg_is_importing_subkeys),
MSG_IS_IO_EXCPTION (R.string.msg_is_io_excption),
MSG_IS_SUBKEY_NONEXISTENT (R.string.msg_is_subkey_nonexistent),
MSG_IS_SUBKEY_OK (R.string.msg_is_subkey_ok),
MSG_IS_SUBKEY_STRIPPED (R.string.msg_is_subkey_stripped),
MSG_IS_SUCCESS (R.string.msg_is_success),
;
private final int mMsgId;
LogType(int msgId) {
mMsgId = msgId;
}
public int getMsgId() {
return mMsgId;
}
}
/** Enumeration of possible log levels. */
public static enum LogLevel {
DEBUG,
INFO,
WARN,
/** If any ERROR log entry is included in the result, the overall operation should have failed. */
ERROR,
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mResult);
dest.writeTypedList(mLog);
}
public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() {
public OperationResultParcel createFromParcel(final Parcel source) {
return new OperationResultParcel(source);
}
public OperationResultParcel[] newArray(final int size) {
return new OperationResultParcel[size];
}
};
}

View File

@ -33,7 +33,11 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -55,10 +59,6 @@ public class PgpImportExport {
private ProviderHelper mProviderHelper;
public static final int RETURN_OK = 0;
public static final int RETURN_BAD = -2;
public static final int RETURN_UPDATED = 1;
public PgpImportExport(Context context, Progressable progressable) {
super();
this.mContext = context;
@ -115,28 +115,23 @@ public class PgpImportExport {
if (aos != null) {
aos.close();
}
if (bos != null) {
bos.close();
}
} catch (IOException e) {
// this is just a finally thing, no matter if it doesn't work out.
}
}
}
/**
* Imports keys from given data. If keyIds is given only those are imported
*/
public Bundle importKeyRings(List<ParcelableKeyRing> entries)
/** Imports keys from given data. If keyIds is given only those are imported */
public ImportResult importKeyRings(List<ParcelableKeyRing> entries)
throws PgpGeneralException, PGPException, IOException {
Bundle returnData = new Bundle();
updateProgress(R.string.progress_importing, 0, 100);
int newKeys = 0;
int oldKeys = 0;
int badKeys = 0;
int newKeys = 0, oldKeys = 0, badKeys = 0;
int position = 0;
int progSteps = 100 / entries.size();
for (ParcelableKeyRing entry : entries) {
try {
UncachedKeyRing key = UncachedKeyRing.decodeFromData(entry.getBytes());
@ -152,15 +147,21 @@ public class PgpImportExport {
}
}
mProviderHelper.resetLog();
OperationResultParcel result = mProviderHelper.savePublicKeyRing(key);
for(OperationResultParcel.LogEntryParcel loge : result.mLog) {
Log.d(Constants.TAG,
loge.mIndent
+ new String(new char[loge.mIndent]).replace("\0", " ")
+ mContext.getString(loge.mType.getMsgId(), (Object[]) loge.mParameters));
SaveKeyringResult result;
if (key.isSecret()) {
result = mProviderHelper.saveSecretKeyRing(key,
new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100));
} else {
result = mProviderHelper.savePublicKeyRing(key,
new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100));
}
if (!result.success()) {
badKeys += 1;
} else if (result.updated()) {
oldKeys += 1;
} else {
newKeys += 1;
}
} catch (PgpGeneralException e) {
Log.e(Constants.TAG, "Encountered bad key on import!", e);
@ -168,14 +169,33 @@ public class PgpImportExport {
}
// update progress
position++;
updateProgress(position / entries.size() * 100, 100);
}
returnData.putInt(KeychainIntentService.RESULT_IMPORT_ADDED, newKeys);
returnData.putInt(KeychainIntentService.RESULT_IMPORT_UPDATED, oldKeys);
returnData.putInt(KeychainIntentService.RESULT_IMPORT_BAD, badKeys);
OperationLog log = mProviderHelper.getLog();
int resultType = 0;
// special return case: no new keys at all
if (badKeys == 0 && newKeys == 0 && oldKeys == 0) {
resultType = ImportResult.RESULT_FAIL_NOTHING;
} else {
if (newKeys > 0) {
resultType |= ImportResult.RESULT_OK_NEWKEYS;
}
if (oldKeys > 0) {
resultType |= ImportResult.RESULT_OK_UPDATED;
}
if (badKeys > 0) {
resultType |= ImportResult.RESULT_WITH_ERRORS;
if (newKeys == 0 && oldKeys == 0) {
resultType |= ImportResult.RESULT_ERROR;
}
}
if (log.containsWarnings()) {
resultType |= ImportResult.RESULT_WITH_WARNINGS;
}
}
return new ImportResult(resultType, log, newKeys, oldKeys, badKeys);
return returnData;
}
public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds,

View File

@ -26,10 +26,8 @@ import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyPair;
import org.spongycastle.openpgp.PGPKeyRingGenerator;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
@ -49,7 +47,9 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.service.OldSaveKeyringParcel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Primes;
@ -62,11 +62,9 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
@ -103,33 +101,11 @@ public class PgpKeyOperation {
}
}
void updateProgress(int current, int total) {
if (mProgress != null) {
mProgress.setProgress(current, total);
}
}
/**
* Creates new secret key.
*
* @param algorithmChoice
* @param keySize
* @param passphrase
* @param isMasterKey
* @return A newly created PGPSecretKey
* @throws NoSuchAlgorithmException
* @throws PGPException
* @throws NoSuchProviderException
* @throws PgpGeneralMsgIdException
* @throws InvalidAlgorithmParameterException
*/
// TODO: key flags?
public byte[] createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey)
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
PgpGeneralMsgIdException, InvalidAlgorithmParameterException {
/** Creates new secret key. */
private PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
boolean isMasterKey) throws PgpGeneralMsgIdException {
try {
if (keySize < 512) {
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
}
@ -189,649 +165,178 @@ public class PgpKeyOperation {
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
try {
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, isMasterKey, keyEncryptor).getEncoded();
} catch(IOException e) {
throw new PgpGeneralMsgIdException(R.string.error_encoding);
sha1Calc, isMasterKey, keyEncryptor);
} catch(NoSuchProviderException e) {
throw new RuntimeException(e);
} catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch(InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
} catch(PGPException e) {
throw new PgpGeneralMsgIdException(R.string.msg_mf_error_pgp, e);
}
}
public Pair<UncachedKeyRing,UncachedKeyRing> buildNewSecretKey(
OldSaveKeyringParcel saveParcel)
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
int usageId = saveParcel.keysUsages.get(0);
boolean canSign;
String mainUserId = saveParcel.userIds.get(0);
PGPSecretKey masterKey = saveParcel.keys.get(0).getSecretKeyExternal();
// this removes all userIds and certifications previously attached to the masterPublicKey
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(saveParcel.oldPassphrase.toCharArray());
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
updateProgress(R.string.progress_certifying_master_key, 20, 100);
for (String userId : saveParcel.userIds) {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
}
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setKeyFlags(true, usageId);
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
if (saveParcel.keysExpiryDates.get(0) != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(masterPublicKey.getCreationTime());
Calendar expiryDate = saveParcel.keysExpiryDates.get(0);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
}
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0);
// do this explicitly, although since we're rebuilding,
// this happens anyway
}
updateProgress(R.string.progress_building_master_key, 30, 100);
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
HashAlgorithmTags.SHA1);
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
// Build key encrypter based on passphrase
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.newPassphrase.toCharArray());
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
updateProgress(R.string.progress_adding_sub_keys, 40, 100);
for (int i = 1; i < saveParcel.keys.size(); ++i) {
updateProgress(40 + 40 * (i - 1) / (saveParcel.keys.size() - 1), 100);
PGPSecretKey subKey = saveParcel.keys.get(i).getSecretKeyExternal();
PGPPublicKey subPublicKey = subKey.getPublicKey();
PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.oldPassphrase.toCharArray());
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
// TODO: now used without algorithm and creation time?! (APG 1)
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
usageId = saveParcel.keysUsages.get(i);
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
if (canSign) {
Date todayDate = new Date(); //both sig times the same
// cross-certify signing keys
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
subPublicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = sGen.generateCertification(masterPublicKey,
subPublicKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification);
}
hashedPacketsGen.setKeyFlags(false, usageId);
if (saveParcel.keysExpiryDates.get(i) != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(subPublicKey.getCreationTime());
Calendar expiryDate = saveParcel.keysExpiryDates.get(i);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
}
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0);
// do this explicitly, although since we're rebuilding,
// this happens anyway
}
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
}
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
return new Pair(new UncachedKeyRing(secretKeyRing), new UncachedKeyRing(publicKeyRing));
}
public Pair<UncachedKeyRing, UncachedKeyRing> buildSecretKey(WrappedSecretKeyRing wmKR,
WrappedPublicKeyRing wpKR,
OldSaveKeyringParcel saveParcel)
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
PGPSecretKeyRing mKR = wmKR.getRing();
PGPPublicKeyRing pKR = wpKR.getRing();
updateProgress(R.string.progress_building_key, 0, 100);
if (saveParcel.oldPassphrase == null) {
saveParcel.oldPassphrase = "";
}
if (saveParcel.newPassphrase == null) {
saveParcel.newPassphrase = "";
}
/*
IDs - NB This might not need to happen later, if we change the way the primary ID is chosen
remove deleted ids
if the primary ID changed we need to:
remove all of the IDs from the keyring, saving their certifications
add them all in again, updating certs of IDs which have changed
else
remove changed IDs and add in with new certs
if the master key changed, we need to remove the primary ID certification, so we can add
the new one when it is generated, and they don't conflict
Keys
remove deleted keys
if a key is modified, re-sign it
do we need to remove and add in?
Todo
identify more things which need to be preserved - e.g. trust levels?
user attributes
*/
if (saveParcel.deletedKeys != null) {
for (UncachedSecretKey dKey : saveParcel.deletedKeys) {
mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey.getSecretKeyExternal());
}
}
PGPSecretKey masterKey = mKR.getSecretKey();
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
int usageId = saveParcel.keysUsages.get(0);
boolean canSign;
String mainUserId = saveParcel.userIds.get(0);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(saveParcel.oldPassphrase.toCharArray());
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
updateProgress(R.string.progress_certifying_master_key, 20, 100);
boolean anyIDChanged = false;
for (String delID : saveParcel.deletedIDs) {
anyIDChanged = true;
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, delID);
}
int userIDIndex = 0;
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
hashedPacketsGen.setKeyFlags(true, usageId);
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
if (saveParcel.keysExpiryDates.get(0) != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(masterPublicKey.getCreationTime());
Calendar expiryDate = saveParcel.keysExpiryDates.get(0);
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
//here we purposefully ignore partial days in each date - long type has no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
}
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0);
// do this explicitly, although since we're rebuilding,
// this happens anyway
}
if (saveParcel.primaryIDChanged ||
!saveParcel.originalIDs.get(0).equals(saveParcel.userIds.get(0))) {
anyIDChanged = true;
ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
for (String userId : saveParcel.userIds) {
String origID = saveParcel.originalIDs.get(userIDIndex);
if (origID.equals(userId) && !saveParcel.newIDs[userIDIndex] &&
!userId.equals(saveParcel.originalPrimaryID) && userIDIndex != 0) {
Iterator<PGPSignature> origSigs = masterPublicKey.getSignaturesForID(origID);
// TODO: make sure this iterator only has signatures we are interested in
while (origSigs.hasNext()) {
PGPSignature origSig = origSigs.next();
sigList.add(new Pair<String, PGPSignature>(origID, origSig));
}
} else {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
if (userIDIndex == 0) {
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
}
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
sigList.add(new Pair<String, PGPSignature>(userId, certification));
}
if (!saveParcel.newIDs[userIDIndex]) {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
}
userIDIndex++;
}
for (Pair<String, PGPSignature> toAdd : sigList) {
masterPublicKey =
PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
}
} else {
for (String userId : saveParcel.userIds) {
String origID = saveParcel.originalIDs.get(userIDIndex);
if (!origID.equals(userId) || saveParcel.newIDs[userIDIndex]) {
anyIDChanged = true;
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
if (userIDIndex == 0) {
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
}
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
if (!saveParcel.newIDs[userIDIndex]) {
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
}
masterPublicKey =
PGPPublicKey.addCertification(masterPublicKey, userId, certification);
}
userIDIndex++;
}
}
ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
if (saveParcel.moddedKeys[0]) {
userIDIndex = 0;
for (String userId : saveParcel.userIds) {
String origID = saveParcel.originalIDs.get(userIDIndex);
if (!(origID.equals(saveParcel.originalPrimaryID) && !saveParcel.primaryIDChanged)) {
Iterator<PGPSignature> sigs = masterPublicKey.getSignaturesForID(userId);
// TODO: make sure this iterator only has signatures we are interested in
while (sigs.hasNext()) {
PGPSignature sig = sigs.next();
sigList.add(new Pair<String, PGPSignature>(userId, sig));
}
}
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, userId);
userIDIndex++;
}
anyIDChanged = true;
}
//update the keyring with the new ID information
if (anyIDChanged) {
pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
}
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
updateProgress(R.string.progress_building_master_key, 30, 100);
// define hashing and signing algos
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
HashAlgorithmTags.SHA1);
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
// Build key encryptor based on old passphrase, as some keys may be unchanged
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.oldPassphrase.toCharArray());
//this generates one more signature than necessary...
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
for (int i = 1; i < saveParcel.keys.size(); ++i) {
updateProgress(40 + 50 * i / saveParcel.keys.size(), 100);
if (saveParcel.moddedKeys[i]) {
PGPSecretKey subKey = saveParcel.keys.get(i).getSecretKeyExternal();
PGPPublicKey subPublicKey = subKey.getPublicKey();
PBESecretKeyDecryptor keyDecryptor2;
if (saveParcel.newKeys[i]) {
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
"".toCharArray());
} else {
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.oldPassphrase.toCharArray());
}
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
usageId = saveParcel.keysUsages.get(i);
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
if (canSign) {
Date todayDate = new Date(); //both sig times the same
// cross-certify signing keys
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
subPublicKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = sGen.generateCertification(masterPublicKey,
subPublicKey);
unhashedPacketsGen.setEmbeddedSignature(false, certification);
}
hashedPacketsGen.setKeyFlags(false, usageId);
if (saveParcel.keysExpiryDates.get(i) != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(subPublicKey.getCreationTime());
Calendar expiryDate = saveParcel.keysExpiryDates.get(i);
// note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
// here we purposefully ignore partial days in each date - long type has
// no fractional part!
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
}
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
} else {
hashedPacketsGen.setKeyExpirationTime(false, 0);
// do this explicitly, although since we're rebuilding,
// this happens anyway
}
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
// certifications will be discarded if the key is changed, because I think, for a start,
// they will be invalid. Binding certs are regenerated anyway, and other certs which
// need to be kept are on IDs and attributes
// TODO: don't let revoked keys be edited, other than removed - changing one would
// result in the revocation being wrong?
}
}
PGPSecretKeyRing updatedSecretKeyRing = keyGen.generateSecretKeyRing();
//finally, update the keyrings
Iterator<PGPSecretKey> itr = updatedSecretKeyRing.getSecretKeys();
while (itr.hasNext()) {
PGPSecretKey theNextKey = itr.next();
if ((theNextKey.isMasterKey() && saveParcel.moddedKeys[0]) || !theNextKey.isMasterKey()) {
mKR = PGPSecretKeyRing.insertSecretKey(mKR, theNextKey);
pKR = PGPPublicKeyRing.insertPublicKey(pKR, theNextKey.getPublicKey());
}
}
//replace lost IDs
if (saveParcel.moddedKeys[0]) {
masterPublicKey = mKR.getPublicKey();
for (Pair<String, PGPSignature> toAdd : sigList) {
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
}
pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
}
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
PGPEncryptedData.CAST5, sha1Calc)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
saveParcel.newPassphrase.toCharArray());
//update the passphrase
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
/* additional handy debug info
Log.d(Constants.TAG, " ------- in private key -------");
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(
secretKeyRing.getPublicKey().getSignaturesForId(uid))) {
Log.d(Constants.TAG, "sig: " +
PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
Log.d(Constants.TAG, " ------- in public key -------");
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
for(PGPSignature sig : new IterableIterator<PGPSignature>(
publicKeyRing.getPublicKey().getSignaturesForId(uid))) {
Log.d(Constants.TAG, "sig: " +
PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
}
}
*/
return new Pair<UncachedKeyRing,UncachedKeyRing>(new UncachedKeyRing(pKR),
new UncachedKeyRing(mKR));
}
public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildSecretKey(PGPSecretKeyRing sKR,
PGPPublicKeyRing pKR,
SaveKeyringParcel saveParcel,
String passphrase)
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
updateProgress(R.string.progress_building_key, 0, 100);
// sort these, so we can use binarySearch later on
Arrays.sort(saveParcel.revokeSubKeys);
Arrays.sort(saveParcel.revokeUserIds);
/*
* What's gonna happen here:
/** This method introduces a list of modifications specified by a SaveKeyringParcel to a
* WrappedSecretKeyRing.
*
* This method relies on WrappedSecretKeyRing's canonicalization property!
*
* Note that PGPPublicKeyRings can not be directly modified. Instead, the corresponding
* PGPSecretKeyRing must be modified and consequently consolidated with its public counterpart.
* This is a natural workflow since pgp keyrings are immutable data structures: Old semantics
* are changed by adding new certificates, which implicitly override older certificates.
*
*/
public UncachedKeyRing modifySecretKeyRing(WrappedSecretKeyRing wsKR, SaveKeyringParcel saveParcel,
String passphrase, OperationLog log, int indent) {
/*
* 1. Unlock private key
*
* 2. Create new secret key ring
*
* 3. Copy subkeys
* - Generate revocation if requested
* - Copy old cert, or generate new if change requested
*
* 4. Generate and add new subkeys
*
* 5. Copy user ids
* - Generate revocation if requested
* - Copy old cert, or generate new if primary user id status changed
*
* 6. Add new user ids
*
* 7. Generate PublicKeyRing from SecretKeyRing
*
* 8. Return pair (PublicKeyRing,SecretKeyRing)
*
* 2a. Add certificates for new user ids
* 2b. Add revocations for revoked user ids
* 3. If primary user id changed, generate new certificates for both old and new
* 4a. For each subkey change, generate new subkey binding certificate
* 4b. For each subkey revocation, generate new subkey revocation certificate
* 5. Generate and add new subkeys
* 6. If requested, change passphrase
*/
log.add(LogLevel.START, LogType.MSG_MF, indent);
indent += 1;
updateProgress(R.string.progress_building_key, 0, 100);
// We work on bouncycastle object level here
PGPSecretKeyRing sKR = wsKR.getRing();
PGPPublicKey masterPublicKey = sKR.getPublicKey();
PGPSecretKey masterSecretKey = sKR.getSecretKey();
// 1. Unlock private key
updateProgress(R.string.progress_building_key, 0, 100);
PGPPublicKey masterPublicKey = sKR.getPublicKey();
log.add(LogLevel.DEBUG, LogType.MSG_MF_UNLOCK, indent);
PGPPrivateKey masterPrivateKey; {
PGPSecretKey masterKey = sKR.getSecretKey();
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor);
} catch (PGPException e) {
log.add(LogLevel.ERROR, LogType.MSG_MF_UNLOCK_ERROR, indent+1);
return null;
}
}
if (!Arrays.equals(saveParcel.mFingerprint, sKR.getPublicKey().getFingerprint())) {
return null;
}
// 2. Create new secret key ring
updateProgress(R.string.progress_certifying_master_key, 20, 100);
// Note we do NOT use PGPKeyRingGeneraor, it's just one level too high and does stuff
// we want to do manually. Instead, we simply use a list of secret keys.
ArrayList<PGPSecretKey> secretKeys = new ArrayList<PGPSecretKey>();
ArrayList<PGPPublicKey> publicKeys = new ArrayList<PGPPublicKey>();
// work on master secret key
try {
// 3. Copy subkeys
// - Generate revocation if requested
// - Copy old cert, or generate new if change requested
for (PGPSecretKey sKey : new IterableIterator<PGPSecretKey>(sKR.getSecretKeys())) {
PGPPublicKey modifiedPublicKey = masterPublicKey;
// 2a. Add certificates for new user ids
for (String userId : saveParcel.addUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_ADD, indent);
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
masterPublicKey, userId, false);
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
// 2b. Add revocations for revoked user ids
for (String userId : saveParcel.revokeUserIds) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_REVOKE, indent);
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
masterPublicKey, userId);
modifiedPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
// 3. If primary user id changed, generate new certificates for both old and new
if (saveParcel.changePrimaryUserId != null) {
log.add(LogLevel.INFO, LogType.MSG_MF_UID_PRIMARY, indent);
// todo
}
// Update the secret key ring
if (modifiedPublicKey != masterPublicKey) {
masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, modifiedPublicKey);
masterPublicKey = modifiedPublicKey;
sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey);
}
// 4a. For each subkey change, generate new subkey binding certificate
for (SaveKeyringParcel.SubkeyChange change : saveParcel.changeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_CHANGE,
new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent);
PGPSecretKey sKey = sKR.getSecretKey(change.mKeyId);
if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent + 1);
return null;
}
PGPPublicKey pKey = sKey.getPublicKey();
if (Arrays.binarySearch(saveParcel.revokeSubKeys, sKey.getKeyID()) >= 0) {
// add revocation signature to key, if there is none yet
if (!pKey.getSignaturesOfType(PGPSignature.SUBKEY_REVOCATION).hasNext()) {
// generate revocation signature
}
}
if (saveParcel.changeSubKeys.containsKey(sKey.getKeyID())) {
// change subkey flags?
SaveKeyringParcel.SubkeyChange change = saveParcel.changeSubKeys.get(sKey.getKeyID());
// remove old subkey binding signature(s?)
for (PGPSignature sig : new IterableIterator<PGPSignature>(
pKey.getSignaturesOfType(PGPSignature.SUBKEY_BINDING))) {
pKey = PGPPublicKey.removeCertification(pKey, sig);
if (change.mExpiry != null && new Date(change.mExpiry).before(new Date())) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY,
new String[]{PgpKeyHelper.convertKeyIdToHex(change.mKeyId)}, indent + 1);
return null;
}
// generate and add new signature
PGPSignature sig = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, change.mFlags, change.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, sig);
}
secretKeys.add(PGPSecretKey.replacePublicKey(sKey, pKey));
publicKeys.add(pKey);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
// 4. Generate and add new subkeys
// TODO
// 4b. For each subkey revocation, generate new subkey revocation certificate
for (long revocation : saveParcel.revokeSubKeys) {
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_REVOKE,
new String[] { PgpKeyHelper.convertKeyIdToHex(revocation) }, indent);
PGPSecretKey sKey = sKR.getSecretKey(revocation);
if (sKey == null) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_MISSING,
new String[] { PgpKeyHelper.convertKeyIdToHex(revocation) }, indent+1);
return null;
}
PGPPublicKey pKey = sKey.getPublicKey();
// 5. Copy user ids
for (String userId : new IterableIterator<String>(masterPublicKey.getUserIDs())) {
// - Copy old cert, or generate new if primary user id status changed
boolean certified = false, revoked = false;
for (PGPSignature sig : new IterableIterator<PGPSignature>(
masterPublicKey.getSignaturesForID(userId))) {
// We know there are only revocation and certification types in here.
switch(sig.getSignatureType()) {
case PGPSignature.CERTIFICATION_REVOCATION:
revoked = true;
continue;
// generate and add new signature
PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey);
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
// Already got one? Remove this one, then.
if (certified) {
masterPublicKey = PGPPublicKey.removeCertification(
masterPublicKey, userId, sig);
continue;
pKey = PGPPublicKey.addCertification(pKey, sig);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
boolean primary = userId.equals(saveParcel.changePrimaryUserId);
// Generate a new one under certain circumstances
if (saveParcel.changePrimaryUserId != null &&
sig.getHashedSubPackets().isPrimaryUserID() != primary) {
PGPSignature cert = generateUserIdSignature(
masterPrivateKey, masterPublicKey, userId, primary);
PGPPublicKey.addCertification(masterPublicKey, userId, cert);
// 5. Generate and add new subkeys
for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) {
try {
if (add.mExpiry != null && new Date(add.mExpiry).before(new Date())) {
log.add(LogLevel.ERROR, LogType.MSG_MF_SUBKEY_PAST_EXPIRY, indent +1);
return null;
}
certified = true;
}
}
// - Generate revocation if requested
if (!revoked && Arrays.binarySearch(saveParcel.revokeUserIds, userId) >= 0) {
PGPSignature cert = generateRevocationSignature(masterPrivateKey,
masterPublicKey, userId);
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
log.add(LogLevel.INFO, LogType.MSG_MF_SUBKEY_NEW, indent);
PGPSecretKey sKey = createKey(add.mAlgorithm, add.mKeysize, passphrase, false);
log.add(LogLevel.DEBUG, LogType.MSG_MF_SUBKEY_NEW_ID,
new String[] { PgpKeyHelper.convertKeyIdToHex(sKey.getKeyID()) }, indent+1);
PGPPublicKey pKey = sKey.getPublicKey();
PGPSignature cert = generateSubkeyBindingSignature(masterPublicKey, masterPrivateKey,
sKey, pKey, add.mFlags, add.mExpiry, passphrase);
pKey = PGPPublicKey.addCertification(pKey, cert);
sKey = PGPSecretKey.replacePublicKey(sKey, pKey);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
} catch (PgpGeneralMsgIdException e) {
return null;
}
}
// 6. Add new user ids
for(String userId : saveParcel.addUserIds) {
PGPSignature cert = generateUserIdSignature(masterPrivateKey,
masterPublicKey, userId, userId.equals(saveParcel.changePrimaryUserId));
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert);
}
// 7. Generate PublicKeyRing from SecretKeyRing
updateProgress(R.string.progress_building_master_key, 30, 100);
PGPSecretKeyRing ring = new PGPSecretKeyRing(secretKeys);
// Copy all non-self uid certificates
for (String userId : new IterableIterator<String>(masterPublicKey.getUserIDs())) {
// - Copy old cert, or generate new if primary user id status changed
boolean certified = false, revoked = false;
for (PGPSignature sig : new IterableIterator<PGPSignature>(
masterPublicKey.getSignaturesForID(userId))) {
}
}
for (PGPPublicKey newKey : publicKeys) {
PGPPublicKey oldKey = pKR.getPublicKey(newKey.getKeyID());
for (PGPSignature sig : new IterableIterator<PGPSignature>(
oldKey.getSignatures())) {
}
}
// If requested, set new passphrase
// 6. If requested, change passphrase
if (saveParcel.newPassphrase != null) {
log.add(LogLevel.INFO, LogType.MSG_MF_PASSPHRASE, indent);
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build()
.get(HashAlgorithmTags.SHA1);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
@ -845,9 +350,20 @@ public class PgpKeyOperation {
sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew);
}
// 8. Return pair (PublicKeyRing,SecretKeyRing)
// This one must only be thrown by
} catch (IOException e) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_ENCODE, indent+1);
return null;
} catch (PGPException e) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_PGP, indent+1);
return null;
} catch (SignatureException e) {
log.add(LogLevel.ERROR, LogType.MSG_MF_ERROR_SIG, indent+1);
return null;
}
return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(sKR, pKR);
log.add(LogLevel.OK, LogType.MSG_MF_SUCCESS, indent);
return new UncachedKeyRing(sKR);
}
@ -883,11 +399,30 @@ public class PgpKeyOperation {
return sGen.generateCertification(userId, pKey);
}
private static PGPSignature generateRevocationSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey, PGPPublicKey pKey)
throws IOException, PGPException, SignatureException {
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
pKey.getAlgorithm(), PGPUtil.SHA1)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, new Date());
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
// Generate key revocation or subkey revocation, depending on master/subkey-ness
if (masterPublicKey.getKeyID() == pKey.getKeyID()) {
sGen.init(PGPSignature.KEY_REVOCATION, masterPrivateKey);
return sGen.generateCertification(masterPublicKey);
} else {
sGen.init(PGPSignature.SUBKEY_REVOCATION, masterPrivateKey);
return sGen.generateCertification(masterPublicKey, pKey);
}
}
private static PGPSignature generateSubkeyBindingSignature(
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
PGPSecretKey sKey, PGPPublicKey pKey,
int flags, Long expiry, String passphrase)
throws PgpGeneralMsgIdException, IOException, PGPException, SignatureException {
PGPSecretKey sKey, PGPPublicKey pKey, int flags, Long expiry, String passphrase)
throws IOException, PGPException, SignatureException {
// date for signing
Date todayDate = new Date();
@ -924,13 +459,10 @@ public class PgpKeyOperation {
if (expiry != null) {
Calendar creationDate = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
creationDate.setTime(pKey.getCreationTime());
// note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
// here we purposefully ignore partial days in each date - long type has
// no fractional part!
long numDays = (expiry / 86400000) -
(creationDate.getTimeInMillis() / 86400000);
if (numDays <= 0) {
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
// (Just making sure there's no programming error here, this MUST have been checked above!)
if (new Date(expiry).before(todayDate)) {
throw new RuntimeException("Bad subkey creation date, this is a bug!");
}
hashedPacketsGen.setKeyExpirationTime(false, expiry - creationDate.getTimeInMillis());
} else {
@ -1003,19 +535,4 @@ public class PgpKeyOperation {
return publicKey;
}
/**
* Simple static subclass that stores two values.
* <p/>
* This is only used to return a pair of values in one function above. We specifically don't use
* com.android.Pair to keep this class free from android dependencies.
*/
public static class Pair<K, V> {
public final K first;
public final V second;
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
}
}

View File

@ -2,14 +2,23 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyFlags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureList;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@ -19,9 +28,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;
/** Wrapper around PGPKeyRing class, to be constructed from bytes.
@ -41,26 +54,29 @@ import java.util.Vector;
* @see org.sufficientlysecure.keychain.pgp.UncachedSecretKey
*
*/
@SuppressWarnings("unchecked")
public class UncachedKeyRing {
final PGPKeyRing mRing;
final boolean mIsSecret;
final boolean mIsCanonicalized;
UncachedKeyRing(PGPKeyRing ring) {
mRing = ring;
mIsSecret = ring instanceof PGPSecretKeyRing;
mIsCanonicalized = false;
}
private UncachedKeyRing(PGPKeyRing ring, boolean canonicalized) {
mRing = ring;
mIsSecret = ring instanceof PGPSecretKeyRing;
mIsCanonicalized = canonicalized;
}
public long getMasterKeyId() {
return mRing.getPublicKey().getKeyID();
}
/* TODO don't use this */
@Deprecated
public PGPKeyRing getRing() {
return mRing;
}
public UncachedPublicKey getPublicKey() {
return new UncachedPublicKey(mRing.getPublicKey());
}
@ -85,6 +101,10 @@ public class UncachedKeyRing {
return mIsSecret;
}
public boolean isCanonicalized() {
return mIsCanonicalized;
}
public byte[] getEncoded() throws IOException {
return mRing.getEncoded();
}
@ -93,15 +113,6 @@ public class UncachedKeyRing {
return mRing.getPublicKey().getFingerprint();
}
public static UncachedKeyRing decodePublicFromData(byte[] data)
throws PgpGeneralException, IOException {
UncachedKeyRing ring = decodeFromData(data);
if(ring.isSecret()) {
throw new PgpGeneralException("Object not recognized as PGPPublicKeyRing!");
}
return ring;
}
public static UncachedKeyRing decodeFromData(byte[] data)
throws PgpGeneralException, IOException {
BufferedInputStream bufferedInput =
@ -169,4 +180,620 @@ public class UncachedKeyRing {
return result;
}
/** "Canonicalizes" a public key, removing inconsistencies in the process. This variant can be
* applied to public keyrings only.
*
* More specifically:
* - Remove all non-verifying self-certificates
* - Remove all "future" self-certificates
* - Remove all certificates flagged as "local"
* - Remove all certificates which are superseded by a newer one on the same target,
* including revocations with later re-certifications.
* - Remove all certificates of unknown type:
* - key revocation signatures on the master key
* - subkey binding signatures for subkeys
* - certifications and certification revocations for user ids
* - If a subkey retains no valid subkey binding certificate, remove it
* - If a user id retains no valid self certificate, remove it
* - If the key is a secret key, remove all certificates by foreign keys
* - If no valid user id remains, log an error and return null
*
* This operation writes an OperationLog which can be used as part of a OperationResultParcel.
*
* @return A canonicalized key, or null on fatal error
*
*/
@SuppressWarnings("ConstantConditions")
public UncachedKeyRing canonicalize(OperationLog log, int indent) {
log.add(LogLevel.START, isSecret() ? LogType.MSG_KC_SECRET : LogType.MSG_KC_PUBLIC,
new String[]{PgpKeyHelper.convertKeyIdToHex(getMasterKeyId())}, indent);
indent += 1;
final Date now = new Date();
int redundantCerts = 0, badCerts = 0;
PGPKeyRing ring = mRing;
PGPPublicKey masterKey = mRing.getPublicKey();
final long masterKeyId = masterKey.getKeyID();
{
log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER,
new String[]{PgpKeyHelper.convertKeyIdToHex(masterKey.getKeyID())}, indent);
indent += 1;
PGPPublicKey modified = masterKey;
PGPSignature revocation = null;
for (PGPSignature zert : new IterableIterator<PGPSignature>(masterKey.getSignatures())) {
int type = zert.getSignatureType();
// Disregard certifications on user ids, we will deal with those later
if (type == PGPSignature.NO_CERTIFICATION
|| type == PGPSignature.DEFAULT_CERTIFICATION
|| type == PGPSignature.CASUAL_CERTIFICATION
|| type == PGPSignature.POSITIVE_CERTIFICATION
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
continue;
}
WrappedSignature cert = new WrappedSignature(zert);
if (type != PGPSignature.KEY_REVOCATION) {
// Unknown type, just remove
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TYPE, new String[]{
"0x" + Integer.toString(type, 16)
}, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
if (cert.getCreationTime().after(now)) {
// Creation date in the future? No way!
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
try {
cert.init(masterKey);
if (!cert.verifySignature(masterKey)) {
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, null, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, null, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
// first revocation? fine then.
if (revocation == null) {
revocation = zert;
// more revocations? at least one is superfluous, then.
} else if (revocation.getCreationTime().before(zert.getCreationTime())) {
modified = PGPPublicKey.removeCertification(modified, revocation);
redundantCerts += 1;
log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent);
revocation = zert;
} else {
modified = PGPPublicKey.removeCertification(modified, zert);
redundantCerts += 1;
log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent);
}
}
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
PGPSignature selfCert = null;
revocation = null;
// look through signatures for this specific key
for (PGPSignature zert : new IterableIterator<PGPSignature>(
masterKey.getSignaturesForID(userId))) {
WrappedSignature cert = new WrappedSignature(zert);
long certId = cert.getKeyId();
int type = zert.getSignatureType();
if (type != PGPSignature.DEFAULT_CERTIFICATION
&& type != PGPSignature.NO_CERTIFICATION
&& type != PGPSignature.CASUAL_CERTIFICATION
&& type != PGPSignature.POSITIVE_CERTIFICATION
&& type != PGPSignature.CERTIFICATION_REVOCATION) {
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_TYPE,
new String[] {
"0x" + Integer.toString(zert.getSignatureType(), 16)
}, indent);
modified = PGPPublicKey.removeCertification(modified, userId, zert);
badCerts += 1;
}
if (cert.getCreationTime().after(now)) {
// Creation date in the future? No way!
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent);
modified = PGPPublicKey.removeCertification(modified, zert);
badCerts += 1;
continue;
}
// If this is a foreign signature, ...
if (certId != masterKeyId) {
// never mind any further for public keys, but remove them from secret ones
if (isSecret()) {
log.add(LogLevel.WARN, LogType.MSG_KC_UID_FOREIGN,
new String[] { PgpKeyHelper.convertKeyIdToHex(certId) }, indent);
modified = PGPPublicKey.removeCertification(modified, userId, zert);
badCerts += 1;
}
continue;
}
// Otherwise, first make sure it checks out
try {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, userId)) {
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD,
new String[] { userId }, indent);
modified = PGPPublicKey.removeCertification(modified, userId, zert);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD_ERR,
new String[] { userId }, indent);
modified = PGPPublicKey.removeCertification(modified, userId, zert);
badCerts += 1;
continue;
}
switch (type) {
case PGPSignature.DEFAULT_CERTIFICATION:
case PGPSignature.NO_CERTIFICATION:
case PGPSignature.CASUAL_CERTIFICATION:
case PGPSignature.POSITIVE_CERTIFICATION:
if (selfCert == null) {
selfCert = zert;
} else if (selfCert.getCreationTime().before(cert.getCreationTime())) {
modified = PGPPublicKey.removeCertification(modified, userId, selfCert);
redundantCerts += 1;
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
new String[] { userId }, indent);
selfCert = zert;
} else {
modified = PGPPublicKey.removeCertification(modified, userId, zert);
redundantCerts += 1;
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP,
new String[] { userId }, indent);
}
// If there is a revocation certificate, and it's older than this, drop it
if (revocation != null
&& revocation.getCreationTime().before(selfCert.getCreationTime())) {
modified = PGPPublicKey.removeCertification(modified, userId, revocation);
revocation = null;
redundantCerts += 1;
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
new String[] { userId }, indent);
}
break;
case PGPSignature.CERTIFICATION_REVOCATION:
// If this is older than the (latest) self cert, drop it
if (selfCert != null && selfCert.getCreationTime().after(zert.getCreationTime())) {
modified = PGPPublicKey.removeCertification(modified, userId, zert);
redundantCerts += 1;
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD,
new String[] { userId }, indent);
continue;
}
// first revocation? remember it.
if (revocation == null) {
revocation = zert;
// more revocations? at least one is superfluous, then.
} else if (revocation.getCreationTime().before(cert.getCreationTime())) {
modified = PGPPublicKey.removeCertification(modified, userId, revocation);
redundantCerts += 1;
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
new String[] { userId }, indent);
revocation = zert;
} else {
modified = PGPPublicKey.removeCertification(modified, userId, zert);
redundantCerts += 1;
log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP,
new String[] { userId }, indent);
}
break;
}
}
// If no valid certificate (if only a revocation) remains, drop it
if (selfCert == null && revocation == null) {
modified = PGPPublicKey.removeCertification(modified, userId);
log.add(LogLevel.ERROR, LogType.MSG_KC_UID_REVOKE_DUP,
new String[] { userId }, indent);
}
}
// If NO user ids remain, error out!
if (!modified.getUserIDs().hasNext()) {
log.add(LogLevel.ERROR, LogType.MSG_KC_FATAL_NO_UID, null, indent);
return null;
}
// Replace modified key in the keyring
ring = replacePublicKey(ring, modified);
indent -= 1;
}
// Process all keys
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(ring.getPublicKeys())) {
// Don't care about the master key here, that one gets special treatment above
if (key.isMasterKey()) {
continue;
}
log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB,
new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent);
indent += 1;
// A subkey needs exactly one subkey binding certificate, and optionally one revocation
// certificate.
PGPPublicKey modified = key;
PGPSignature selfCert = null, revocation = null;
uids: for (PGPSignature zert : new IterableIterator<PGPSignature>(key.getSignatures())) {
// remove from keyring (for now)
modified = PGPPublicKey.removeCertification(modified, zert);
WrappedSignature cert = new WrappedSignature(zert);
int type = cert.getSignatureType();
// filter out bad key types...
if (cert.getKeyId() != masterKey.getKeyID()) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_KEYID, null, indent);
badCerts += 1;
continue;
}
if (type != PGPSignature.SUBKEY_BINDING && type != PGPSignature.SUBKEY_REVOCATION) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TYPE, new String[]{
"0x" + Integer.toString(type, 16)
}, indent);
badCerts += 1;
continue;
}
if (cert.getCreationTime().after(now)) {
// Creation date in the future? No way!
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_TIME, null, indent);
badCerts += 1;
continue;
}
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_LOCAL, null, indent);
badCerts += 1;
continue;
}
if (type == PGPSignature.SUBKEY_BINDING) {
// make sure the certificate checks out
try {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, key)) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD, null, indent);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_BAD_ERR, null, indent);
badCerts += 1;
continue;
}
if (zert.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) {
int flags = ((KeyFlags) zert.getHashedSubPackets()
.getSubpacket(SignatureSubpacketTags.KEY_FLAGS)).getFlags();
// If this subkey is allowed to sign data,
if ((flags & PGPKeyFlags.CAN_SIGN) == PGPKeyFlags.CAN_SIGN) {
try {
PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures();
boolean ok = false;
for (int i = 0; i < list.size(); i++) {
WrappedSignature subsig = new WrappedSignature(list.get(i));
if (subsig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) {
subsig.init(key);
if (subsig.verifySignature(masterKey, key)) {
ok = true;
} else {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD, null, indent);
badCerts += 1;
continue uids;
}
}
}
if (!ok) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_NONE, null, indent);
badCerts += 1;
continue;
}
} catch (Exception e) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_PRIMARY_BAD_ERR, null, indent);
badCerts += 1;
continue;
}
}
}
// if we already have a cert, and this one is not newer: skip it
if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) {
redundantCerts += 1;
continue;
}
selfCert = zert;
// if this is newer than a possibly existing revocation, drop that one
if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) {
revocation = null;
}
// it must be a revocation, then (we made sure above)
} else {
// make sure the certificate checks out
try {
cert.init(masterKey);
if (!cert.verifySignature(key)) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD, null, indent);
badCerts += 1;
continue;
}
} catch (PgpGeneralException e) {
log.add(LogLevel.WARN, LogType.MSG_KC_SUB_REVOKE_BAD_ERR, null, indent);
badCerts += 1;
continue;
}
// if there is no binding (yet), or the revocation is newer than the binding: keep it
if (selfCert != null && selfCert.getCreationTime().after(cert.getCreationTime())) {
redundantCerts += 1;
continue;
}
revocation = zert;
}
}
// it is not properly bound? error!
if (selfCert == null) {
ring = replacePublicKey(ring, modified);
log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT,
new String[]{ PgpKeyHelper.convertKeyIdToHex(key.getKeyID()) }, indent);
indent -= 1;
continue;
}
// re-add certification
modified = PGPPublicKey.addCertification(modified, selfCert);
// add revocation, if any
if (revocation != null) {
modified = PGPPublicKey.addCertification(modified, revocation);
}
// replace pubkey in keyring
ring = replacePublicKey(ring, modified);
indent -= 1;
}
if (badCerts > 0 && redundantCerts > 0) {
log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_BAD_AND_RED,
new String[] { Integer.toString(badCerts),
Integer.toString(redundantCerts) }, indent);
} else if (badCerts > 0) {
log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_BAD,
new String[] { Integer.toString(badCerts) }, indent);
} else if (redundantCerts > 0) {
log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_REDUNDANT,
new String[] { Integer.toString(redundantCerts) }, indent);
} else {
log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS, null, indent);
}
return new UncachedKeyRing(ring, true);
}
/** This operation merges information from a different keyring, returning a combined
* UncachedKeyRing.
*
* The combined keyring contains the subkeys and user ids of both input keyrings, but it does
* not necessarily have the canonicalized property.
*
* @param other The UncachedKeyRing to merge. Must not be empty, and of the same masterKeyId
* @return A consolidated UncachedKeyRing with the data of both input keyrings. Same type as
* this object, or null on error.
*
*/
public UncachedKeyRing merge(UncachedKeyRing other, OperationLog log, int indent) {
log.add(LogLevel.DEBUG, isSecret() ? LogType.MSG_MG_SECRET : LogType.MSG_MG_PUBLIC,
new String[]{ PgpKeyHelper.convertKeyIdToHex(getMasterKeyId()) }, indent);
indent += 1;
long masterKeyId = other.getMasterKeyId();
if (getMasterKeyId() != masterKeyId) {
log.add(LogLevel.ERROR, LogType.MSG_MG_HETEROGENEOUS, null, indent);
return null;
}
// remember which certs we already added. this is cheaper than semantic deduplication
Set<byte[]> certs = new TreeSet<byte[]>(new Comparator<byte[]>() {
public int compare(byte[] left, byte[] right) {
// check for length equality
if (left.length != right.length) {
return left.length - right.length;
}
// compare byte-by-byte
for (int i = 0; i < left.length && i < right.length; i++) {
if (left[i] != right[i]) {
return (left[i] & 0xff) - (right[i] & 0xff);
}
}
// ok they're the same
return 0;
}});
try {
PGPKeyRing result = mRing;
PGPKeyRing candidate = other.mRing;
// Pre-load all existing certificates
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(result.getPublicKeys())) {
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
certs.add(cert.getEncoded());
}
}
// keep track of the number of new certs we add
int newCerts = 0;
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(candidate.getPublicKeys())) {
final PGPPublicKey resultKey = result.getPublicKey(key.getKeyID());
if (resultKey == null) {
log.add(LogLevel.DEBUG, LogType.MSG_MG_NEW_SUBKEY, null, indent);
result = replacePublicKey(result, key);
continue;
}
// Modifiable version of the old key, which we merge stuff into (keep old for comparison)
PGPPublicKey modified = resultKey;
// Iterate certifications
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignatures())) {
int type = cert.getSignatureType();
// Disregard certifications on user ids, we will deal with those later
if (type == PGPSignature.NO_CERTIFICATION
|| type == PGPSignature.DEFAULT_CERTIFICATION
|| type == PGPSignature.CASUAL_CERTIFICATION
|| type == PGPSignature.POSITIVE_CERTIFICATION
|| type == PGPSignature.CERTIFICATION_REVOCATION) {
continue;
}
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
}
byte[] encoded = cert.getEncoded();
// Known cert, skip it
if (certs.contains(encoded)) {
continue;
}
certs.add(encoded);
modified = PGPPublicKey.addCertification(modified, cert);
newCerts += 1;
}
// If this is a subkey, merge it in and stop here
if (!key.isMasterKey()) {
if (modified != resultKey) {
result = replacePublicKey(result, modified);
}
continue;
}
// Copy over all user id certificates
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
for (PGPSignature cert : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) {
// Don't merge foreign stuff into secret keys
if (cert.getKeyID() != masterKeyId && isSecret()) {
continue;
}
byte[] encoded = cert.getEncoded();
// Known cert, skip it
if (certs.contains(encoded)) {
continue;
}
newCerts += 1;
certs.add(encoded);
modified = PGPPublicKey.addCertification(modified, userId, cert);
}
}
// If anything changed, save the updated (sub)key
if (modified != resultKey) {
result = replacePublicKey(result, modified);
}
}
log.add(LogLevel.DEBUG, LogType.MSG_MG_FOUND_NEW,
new String[] { Integer.toString(newCerts) }, indent);
return new UncachedKeyRing(result);
} catch (IOException e) {
log.add(LogLevel.ERROR, LogType.MSG_MG_FATAL_ENCODE, null, indent);
return null;
}
}
public UncachedKeyRing extractPublicKeyRing() {
if(!isSecret()) {
throw new RuntimeException("Tried to extract public keyring from non-secret keyring. " +
"This is a programming error and should never happen!");
}
ArrayList<PGPPublicKey> keys = new ArrayList();
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
while (it.hasNext()) {
keys.add(it.next());
}
return new UncachedKeyRing(new PGPPublicKeyRing(keys));
}
/** This method replaces a public key in a keyring.
*
* This method essentially wraps PGP*KeyRing.insertPublicKey, where the keyring may be of either
* the secret or public subclass.
*
* @return the resulting PGPKeyRing of the same type as the input
*/
private static PGPKeyRing replacePublicKey(PGPKeyRing ring, PGPPublicKey key) {
if (ring instanceof PGPPublicKeyRing) {
return PGPPublicKeyRing.insertPublicKey((PGPPublicKeyRing) ring, key);
}
PGPSecretKeyRing secRing = (PGPSecretKeyRing) ring;
PGPSecretKey sKey = secRing.getSecretKey(key.getKeyID());
// TODO generate secret key with S2K dummy, if none exists! for now, just die.
if (sKey == null) {
throw new RuntimeException("dummy secret key generation not yet implemented");
}
sKey = PGPSecretKey.replacePublicKey(sKey, key);
return PGPSecretKeyRing.insertSecretKey(secRing, sKey);
}
}

View File

@ -2,6 +2,7 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
@ -9,6 +10,7 @@ import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProv
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.IterableIterator;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -28,8 +30,13 @@ public class UncachedPublicKey {
}
/** The revocation signature is NOT checked here, so this may be false! */
public boolean maybeRevoked() {
return mPublicKey.isRevoked();
public boolean isRevoked() {
for (PGPSignature sig : new IterableIterator<PGPSignature>(
mPublicKey.getSignaturesOfType(isMasterKey() ? PGPSignature.KEY_REVOCATION
: PGPSignature.SUBKEY_REVOCATION))) {
return true;
}
return false;
}
public Date getCreationTime() {
@ -193,4 +200,5 @@ public class UncachedPublicKey {
}
};
}
}

View File

@ -91,8 +91,18 @@ public abstract class WrappedKeyRing extends KeyRing {
getRing().encode(stream);
}
/** Returns an UncachedKeyRing which wraps the same data as this ring. This method should
* only be used */
public UncachedKeyRing getUncachedKeyRing() {
return new UncachedKeyRing(getRing());
}
abstract PGPKeyRing getRing();
abstract public IterableIterator<WrappedPublicKey> publicKeyIterator();
public UncachedKeyRing getUncached() {
return new UncachedKeyRing(getRing());
}
}

View File

@ -154,8 +154,4 @@ public class WrappedSecretKeyRing extends WrappedKeyRing {
});
}
public UncachedKeyRing getUncached() {
return new UncachedKeyRing(mRing);
}
}

View File

@ -35,7 +35,7 @@ public class WrappedSignature {
final PGPSignature mSig;
protected WrappedSignature(PGPSignature sig) {
WrappedSignature(PGPSignature sig) {
mSig = sig;
}
@ -88,7 +88,7 @@ public class WrappedSignature {
init(key.getPublicKey());
}
protected void init(PGPPublicKey key) throws PgpGeneralException {
void init(PGPPublicKey key) throws PgpGeneralException {
try {
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
new JcaPGPContentVerifierBuilderProvider()
@ -125,7 +125,27 @@ public class WrappedSignature {
}
}
protected boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException {
boolean verifySignature(PGPPublicKey key) throws PgpGeneralException {
try {
return mSig.verifyCertification(key);
} catch (SignatureException e) {
throw new PgpGeneralException("Sign!", e);
} catch (PGPException e) {
throw new PgpGeneralException("Error!", e);
}
}
boolean verifySignature(PGPPublicKey masterKey, PGPPublicKey subKey) throws PgpGeneralException {
try {
return mSig.verifyCertification(masterKey, subKey);
} catch (SignatureException e) {
throw new PgpGeneralException("Sign!", e);
} catch (PGPException e) {
throw new PgpGeneralException("Error!", e);
}
}
boolean verifySignature(PGPPublicKey key, String uid) throws PgpGeneralException {
try {
return mSig.verifyCertification(uid, key);
} catch (SignatureException e) {
@ -158,4 +178,12 @@ public class WrappedSignature {
return new WrappedSignature(signatures.get(0));
}
public boolean isLocal() {
if (!mSig.hasSubpackets()
|| !mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) {
return false;
}
SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.EXPORTABLE);
return p.getData()[0] == 0;
}
}

View File

@ -30,6 +30,11 @@ public class PgpGeneralMsgIdException extends Exception {
mMessageId = messageId;
}
public PgpGeneralMsgIdException(int messageId, Throwable cause) {
super("msg[" + messageId + "]", cause);
mMessageId = messageId;
}
public PgpGeneralException getContextualized(Context context) {
return new PgpGeneralException(context.getString(mMessageId), this);
}

View File

@ -29,9 +29,11 @@ import android.support.v4.util.LongSparseArray;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.OperationResultParcel;
import org.sufficientlysecure.keychain.pgp.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.pgp.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKey;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogType;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
@ -48,12 +50,14 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.remote.AccountSettings;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.service.OperationResults.SaveKeyringResult;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@ -61,18 +65,27 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** This class contains high level methods for database access. Despite its
* name, it is not only a helper but actually the main interface for all
* synchronous database operations.
*
* Operations in this class write logs. These can be obtained from the
* OperationResultParcel return values directly, but are also accumulated over
* the lifetime of the executing ProviderHelper object unless the resetLog()
* method is called to start a new one specifically.
*
*/
public class ProviderHelper {
private final Context mContext;
private final ContentResolver mContentResolver;
private final ArrayList<OperationResultParcel.LogEntryParcel> mLog;
private OperationLog mLog;
private int mIndent;
public ProviderHelper(Context context) {
this(context, new ArrayList<OperationResultParcel.LogEntryParcel>(), 0);
this(context, new OperationLog(), 0);
}
public ProviderHelper(Context context, ArrayList<OperationResultParcel.LogEntryParcel> log,
int indent) {
public ProviderHelper(Context context, OperationLog log, int indent) {
mContext = context;
mContentResolver = context.getContentResolver();
mLog = log;
@ -81,11 +94,16 @@ public class ProviderHelper {
public void resetLog() {
if(mLog != null) {
mLog.clear();
// Start a new log (leaving the old one intact)
mLog = new OperationLog();
mIndent = 0;
}
}
public OperationLog getLog() {
return mLog;
}
public static class NotFoundException extends Exception {
public NotFoundException() {
}
@ -97,12 +115,12 @@ public class ProviderHelper {
public void log(LogLevel level, LogType type) {
if(mLog != null) {
mLog.add(new OperationResultParcel.LogEntryParcel(level, type, null, mIndent));
mLog.add(level, type, null, mIndent);
}
}
public void log(LogLevel level, LogType type, String[] parameters) {
if(mLog != null) {
mLog.add(new OperationResultParcel.LogEntryParcel(level, type, parameters, mIndent));
mLog.add(level, type, parameters, mIndent);
}
}
@ -156,45 +174,42 @@ public class ProviderHelper {
}
}
public Object getUnifiedData(long masterKeyId, String column, int type)
throws NotFoundException {
return getUnifiedData(masterKeyId, new String[]{column}, new int[]{type}).get(column);
}
public HashMap<String, Object> getUnifiedData(long masterKeyId, String[] proj, int[] types)
throws NotFoundException {
return getGenericData(KeyRings.buildUnifiedKeyRingUri(masterKeyId), proj, types);
}
private LongSparseArray<UncachedPublicKey> getUncachedMasterKeys(Uri queryUri) {
Cursor cursor = mContentResolver.query(queryUri,
new String[]{KeyRingData.MASTER_KEY_ID, KeyRingData.KEY_RING_DATA},
null, null, null);
private LongSparseArray<WrappedPublicKey> getTrustedMasterKeys() {
Cursor cursor = mContentResolver.query(KeyRings.buildUnifiedKeyRingsUri(), new String[] {
KeyRings.MASTER_KEY_ID,
// we pick from cache only information that is not easily available from keyrings
KeyRings.HAS_ANY_SECRET, KeyRings.VERIFIED,
// and of course, ring data
KeyRings.PUBKEY_DATA
}, KeyRings.HAS_ANY_SECRET + " = 1", null, null);
LongSparseArray<UncachedPublicKey> result =
new LongSparseArray<UncachedPublicKey>(cursor.getCount());
try {
LongSparseArray<WrappedPublicKey> result = new LongSparseArray<WrappedPublicKey>();
if (cursor != null && cursor.moveToFirst()) do {
long masterKeyId = cursor.getLong(0);
byte[] data = cursor.getBlob(1);
if (data != null) {
try {
boolean hasAnySecret = cursor.getInt(1) > 0;
int verified = cursor.getInt(2);
byte[] blob = cursor.getBlob(3);
if (blob != null) {
result.put(masterKeyId,
UncachedKeyRing.decodeFromData(data).getPublicKey());
} catch(PgpGeneralException e) {
Log.e(Constants.TAG, "Error parsing keyring, skipping " + masterKeyId, e);
} catch(IOException e) {
Log.e(Constants.TAG, "IO error, skipping keyring" + masterKeyId, e);
}
new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey());
}
} while (cursor.moveToNext());
return result;
} finally {
if (cursor != null) {
cursor.close();
}
}
return result;
}
public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) {
@ -236,7 +251,7 @@ public class ProviderHelper {
throw new NotFoundException("Secret key not available!");
}
return secret
? new WrappedSecretKeyRing(blob, hasAnySecret, verified)
? new WrappedSecretKeyRing(blob, true, verified)
: new WrappedPublicKeyRing(blob, hasAnySecret, verified);
} else {
throw new NotFoundException("Key not found!");
@ -248,90 +263,150 @@ public class ProviderHelper {
}
}
/**
* Saves PGPPublicKeyRing with its keys and userIds in DB
/** Saves an UncachedKeyRing of the public variant into the db.
*
* This method will not delete all previous data for this masterKeyId from the database prior
* to inserting. All public data is effectively re-inserted, secret keyrings are left deleted
* and need to be saved externally to be preserved past the operation.
*/
@SuppressWarnings("unchecked")
public OperationResultParcel savePublicKeyRing(UncachedKeyRing keyRing) {
private int internalSavePublicKeyRing(UncachedKeyRing keyRing,
Progressable progress, boolean selfCertsAreTrusted) {
if (keyRing.isSecret()) {
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
return new OperationResultParcel(1, mLog);
return SaveKeyringResult.RESULT_ERROR;
}
if (!keyRing.isCanonicalized()) {
log(LogLevel.ERROR, LogType.MSG_IP_BAD_TYPE_SECRET);
return SaveKeyringResult.RESULT_ERROR;
}
// start with ok result
int result = SaveKeyringResult.SAVED_PUBLIC;
long masterKeyId = keyRing.getMasterKeyId();
UncachedPublicKey masterKey = keyRing.getPublicKey();
long masterKeyId = masterKey.getKeyId();
log(LogLevel.INFO, LogType.MSG_IP_IMPORTING,
new String[]{Long.toString(masterKeyId)});
ArrayList<ContentProviderOperation> operations;
try {
log(LogLevel.DEBUG, LogType.MSG_IP_PREPARE);
mIndent += 1;
// IF there is a secret key, preserve it!
UncachedKeyRing secretRing;
try {
secretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
log(LogLevel.DEBUG, LogType.MSG_IP_PRESERVING_SECRET);
} catch (NotFoundException e) {
secretRing = null;
}
// save all keys and userIds included in keyRing object in database
operations = new ArrayList<ContentProviderOperation>();
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
try {
mContentResolver.delete(KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK);
} catch (UnsupportedOperationException e) {
Log.e(Constants.TAG, "Key could not be deleted! Maybe we are creating a new one!", e);
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_FAIL);
}
// insert new version of this keyRing
log(LogLevel.INFO, LogType.MSG_IP_INSERT_KEYRING);
{ // insert keyring
ContentValues values = new ContentValues();
values.put(KeyRingData.MASTER_KEY_ID, masterKeyId);
try {
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IP_ENCODE_FAIL);
return new OperationResultParcel(1, mLog);
return SaveKeyringResult.RESULT_ERROR;
}
// save all keys and userIds included in keyRing object in database
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
try {
log(LogLevel.INFO, LogType.MSG_IP_INSERT_KEYRING);
Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId));
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
}
log(LogLevel.INFO, LogType.MSG_IP_INSERT_SUBKEYS);
progress.setProgress(LogType.MSG_IP_INSERT_SUBKEYS.getMsgId(), 40, 100);
mIndent += 1;
{ // insert subkeys
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
int rank = 0;
for (UncachedPublicKey key : new IterableIterator<UncachedPublicKey>(keyRing.getPublicKeys())) {
log(LogLevel.DEBUG, LogType.MSG_IP_INSERT_SUBKEY, new String[] {
PgpKeyHelper.convertKeyIdToHex(key.getKeyId())
long keyId = key.getKeyId();
log(LogLevel.DEBUG, keyId == masterKeyId ? LogType.MSG_IP_MASTER : LogType.MSG_IP_SUBKEY, new String[]{
PgpKeyHelper.convertKeyIdToHex(keyId)
});
operations.add(buildPublicKeyOperations(masterKeyId, key, rank));
mIndent += 1;
ContentValues values = new ContentValues();
values.put(Keys.MASTER_KEY_ID, masterKeyId);
values.put(Keys.RANK, rank);
values.put(Keys.KEY_ID, key.getKeyId());
values.put(Keys.KEY_SIZE, key.getBitStrength());
values.put(Keys.ALGORITHM, key.getAlgorithm());
values.put(Keys.FINGERPRINT, key.getFingerprint());
boolean c = key.canCertify(), e = key.canEncrypt(), s = key.canSign();
values.put(Keys.CAN_CERTIFY, c);
values.put(Keys.CAN_ENCRYPT, e);
values.put(Keys.CAN_SIGN, s);
values.put(Keys.IS_REVOKED, key.isRevoked());
if (masterKeyId == keyId) {
if (c) {
if (e) {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_CES
: LogType.MSG_IP_MASTER_FLAGS_CEX, null);
} else {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_CXS
: LogType.MSG_IP_MASTER_FLAGS_CXX, null);
}
} else {
if (e) {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_XES
: LogType.MSG_IP_MASTER_FLAGS_XEX, null);
} else {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_MASTER_FLAGS_XXS
: LogType.MSG_IP_MASTER_FLAGS_XXX, null);
}
}
} else {
if (c) {
if (e) {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CES
: LogType.MSG_IP_SUBKEY_FLAGS_CEX, null);
} else {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CXS
: LogType.MSG_IP_SUBKEY_FLAGS_CXX, null);
}
} else {
if (e) {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XES
: LogType.MSG_IP_SUBKEY_FLAGS_XEX, null);
} else {
log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XXS
: LogType.MSG_IP_SUBKEY_FLAGS_XXX, null);
}
}
}
Date creation = key.getCreationTime();
values.put(Keys.CREATION, creation.getTime() / 1000);
Date expiryDate = key.getExpiryTime();
if (expiryDate != null) {
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
if (key.isExpired()) {
log(LogLevel.DEBUG, keyId == masterKeyId ?
LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED,
new String[]{ expiryDate.toString() });
} else {
log(LogLevel.DEBUG, keyId == masterKeyId ?
LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES,
new String[] { expiryDate.toString() });
}
}
operations.add(ContentProviderOperation.newInsert(uri).withValues(values).build());
++rank;
mIndent -= 1;
}
}
mIndent -= 1;
log(LogLevel.DEBUG, LogType.MSG_IP_TRUST_RETRIEVE);
// get a list of owned secret keys, for verification filtering
LongSparseArray<UncachedPublicKey> trustedKeys =
getUncachedMasterKeys(KeyRingData.buildSecretKeyRingUri());
// special case: available secret keys verify themselves!
if (secretRing != null) {
trustedKeys.put(secretRing.getMasterKeyId(), secretRing.getPublicKey());
log(LogLevel.INFO, LogType.MSG_IP_TRUST_USING_SEC, new String[]{
Integer.toString(trustedKeys.size())
});
} else {
log(LogLevel.INFO, LogType.MSG_IP_TRUST_USING, new String[] {
Integer.toString(trustedKeys.size())
});
}
LongSparseArray<WrappedPublicKey> trustedKeys = getTrustedMasterKeys();
// classify and order user ids. primary are moved to the front, revoked to the back,
// otherwise the order in the keyfile is preserved.
log(LogLevel.DEBUG, LogType.MSG_IP_UID_CLASSIFYING);
log(LogLevel.INFO, LogType.MSG_IP_UID_CLASSIFYING, new String[]{
Integer.toString(trustedKeys.size())
});
mIndent += 1;
List<UserIdItem> uids = new ArrayList<UserIdItem>();
for (String userId : new IterableIterator<String>(
@ -342,7 +417,7 @@ public class ProviderHelper {
int unknownCerts = 0;
log(LogLevel.INFO, LogType.MSG_IP_UID_PROCESSING, new String[] { userId });
log(LogLevel.INFO, LogType.MSG_IP_UID_PROCESSING, new String[]{ userId });
mIndent += 1;
// look through signatures for this specific key
for (WrappedSignature cert : new IterableIterator<WrappedSignature>(
@ -351,41 +426,29 @@ public class ProviderHelper {
try {
// self signature
if (certId == masterKeyId) {
cert.init(masterKey);
if (!cert.verifySignature(masterKey, userId)) {
// Bad self certification? That's kinda bad...
log(LogLevel.ERROR, LogType.MSG_IP_UID_SELF_BAD);
return new OperationResultParcel(1, mLog);
}
// if we already have a cert..
if (item.selfCert != null) {
// ..is this perchance a more recent one?
if (item.selfCert.getCreationTime().before(cert.getCreationTime())) {
log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_NEWER);
} else {
log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_IGNORING_OLD);
continue;
}
} else {
log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_GOOD);
}
// save certificate as primary self-cert
// NOTE self-certificates are already verified during canonicalization,
// AND we know there is at most one cert plus at most one revocation
if (!cert.isRevocation()) {
item.selfCert = cert;
item.isPrimary = cert.isPrimaryUserId();
item.isRevoked = cert.isRevocation();
} else {
item.isRevoked = true;
log(LogLevel.INFO, LogType.MSG_IP_UID_REVOKED);
}
continue;
}
// verify signatures from known private keys
if (trustedKeys.indexOfKey(certId) >= 0) {
UncachedPublicKey trustedKey = trustedKeys.get(certId);
WrappedPublicKey trustedKey = trustedKeys.get(certId);
cert.init(trustedKey);
if (cert.verifySignature(masterKey, userId)) {
item.trustedCerts.add(cert);
log(LogLevel.INFO, LogType.MSG_IP_UID_CERT_GOOD, new String[] {
PgpKeyHelper.convertKeyIdToHex(trustedKey.getKeyId())
PgpKeyHelper.convertKeyIdToHexShort(trustedKey.getKeyId()),
trustedKey.getPrimaryUserId()
});
} else {
log(LogLevel.WARN, LogType.MSG_IP_UID_CERT_BAD);
@ -400,18 +463,19 @@ public class ProviderHelper {
});
}
}
mIndent -= 1;
if (unknownCerts > 0) {
log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[] {
log(LogLevel.DEBUG, LogType.MSG_IP_UID_CERTS_UNKNOWN, new String[]{
Integer.toString(unknownCerts)
});
}
mIndent -= 1;
}
mIndent -= 1;
log(LogLevel.INFO, LogType.MSG_IP_UID_INSERT);
progress.setProgress(LogType.MSG_IP_UID_REORDER.getMsgId(), 65, 100);
log(LogLevel.DEBUG, LogType.MSG_IP_UID_REORDER);
// primary before regular before revoked (see UserIdItem.compareTo)
// this is a stable sort, so the order of keys is otherwise preserved.
Collections.sort(uids);
@ -419,10 +483,9 @@ public class ProviderHelper {
for (int userIdRank = 0; userIdRank < uids.size(); userIdRank++) {
UserIdItem item = uids.get(userIdRank);
operations.add(buildUserIdOperations(masterKeyId, item, userIdRank));
// no self cert is bad, but allowed by the rfc...
if (item.selfCert != null) {
operations.add(buildCertOperations(
masterKeyId, userIdRank, item.selfCert, Certs.VERIFIED_SELF));
operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert,
selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF));
}
// don't bother with trusted certs if the uid is revoked, anyways
if (item.isRevoked) {
@ -434,37 +497,47 @@ public class ProviderHelper {
}
}
log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH);
mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
mIndent -= 1;
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC);
Log.e(Constants.TAG, "IOException during import", e);
mIndent -= 1;
return new OperationResultParcel(1, mLog);
return SaveKeyringResult.RESULT_ERROR;
}
try {
// delete old version of this keyRing, which also deletes all keys and userIds on cascade
int deleted = mContentResolver.delete(
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null);
if (deleted > 0) {
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_OK);
result |= SaveKeyringResult.UPDATED;
} else {
log(LogLevel.DEBUG, LogType.MSG_IP_DELETE_OLD_FAIL);
}
log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH);
progress.setProgress(LogType.MSG_IP_APPLY_BATCH.getMsgId(), 75, 100);
mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations);
log(LogLevel.OK, LogType.MSG_IP_SUCCESS);
mIndent -= 1;
progress.setProgress(LogType.MSG_IP_SUCCESS.getMsgId(), 90, 100);
return result;
} catch (RemoteException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_REMOTE_EX);
Log.e(Constants.TAG, "RemoteException during import", e);
mIndent -= 1;
return new OperationResultParcel(1, mLog);
return SaveKeyringResult.RESULT_ERROR;
} catch (OperationApplicationException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_OP_EX);
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_OP_EXC);
Log.e(Constants.TAG, "OperationApplicationException during import", e);
mIndent -= 1;
return new OperationResultParcel(1, mLog);
return SaveKeyringResult.RESULT_ERROR;
}
// Save the saved keyring (if any)
if (secretRing != null) {
log(LogLevel.DEBUG, LogType.MSG_IP_REINSERT_SECRET);
mIndent += 1;
saveSecretKeyRing(secretRing);
mIndent -= 1;
}
log(LogLevel.INFO, LogType.MSG_IP_SUCCESS);
mIndent -= 1;
return new OperationResultParcel(0, mLog);
}
private static class UserIdItem implements Comparable<UserIdItem> {
@ -488,19 +561,34 @@ public class ProviderHelper {
}
}
/**
* Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring
* is already in the database!
/** Saves an UncachedKeyRing of the secret variant into the db.
* This method will fail if no corresponding public keyring is in the database!
*/
public OperationResultParcel saveSecretKeyRing(UncachedKeyRing keyRing) {
private int internalSaveSecretKeyRing(UncachedKeyRing keyRing) {
if (!keyRing.isSecret()) {
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
return new OperationResultParcel(1, mLog);
return SaveKeyringResult.RESULT_ERROR;
}
if (!keyRing.isCanonicalized()) {
log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC);
return SaveKeyringResult.RESULT_ERROR;
}
long masterKeyId = keyRing.getMasterKeyId();
log(LogLevel.INFO, LogType.MSG_IS_IMPORTING,
new String[]{ Long.toString(masterKeyId) });
log(LogLevel.START, LogType.MSG_IS,
new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) });
mIndent += 1;
// Canonicalize this key, to assert a number of assumptions made about it.
keyRing = keyRing.canonicalize(mLog, mIndent);
if (keyRing == null) {
return SaveKeyringResult.RESULT_ERROR;
}
// IF this is successful, it's a secret key
int result = SaveKeyringResult.SAVED_SECRET;
// save secret keyring
try {
@ -509,11 +597,14 @@ public class ProviderHelper {
values.put(KeyRingData.KEY_RING_DATA, keyRing.getEncoded());
// insert new version of this keyRing
Uri uri = KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId));
mContentResolver.insert(uri, values);
if (mContentResolver.insert(uri, values) == null) {
log(LogLevel.ERROR, LogType.MSG_IS_DB_EXCEPTION);
return SaveKeyringResult.RESULT_ERROR;
}
} catch (IOException e) {
Log.e(Constants.TAG, "Failed to encode key!", e);
log(LogLevel.ERROR, LogType.MSG_IS_IO_EXCPTION);
return new OperationResultParcel(1, mLog);
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC);
return SaveKeyringResult.RESULT_ERROR;
}
{
@ -556,54 +647,220 @@ public class ProviderHelper {
// with has_secret = 0
}
log(LogLevel.INFO, LogType.MSG_IS_SUCCESS);
return new OperationResultParcel(0, mLog);
log(LogLevel.OK, LogType.MSG_IS_SUCCESS);
return result;
}
@Deprecated
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing keyRing) {
return savePublicKeyRing(keyRing, new Progressable() {
@Override
public void setProgress(String message, int current, int total) {
}
@Override
public void setProgress(int resourceId, int current, int total) {
}
@Override
public void setProgress(int current, int total) {
}
});
}
/** Save a public keyring into the database.
*
* This is a high level method, which takes care of merging all new information into the old and
* keep public and secret keyrings in sync.
*/
public SaveKeyringResult savePublicKeyRing(UncachedKeyRing publicRing, Progressable progress) {
try {
long masterKeyId = publicRing.getMasterKeyId();
log(LogLevel.START, LogType.MSG_IP,
new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) });
mIndent += 1;
// If there is an old keyring, merge it
try {
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
// Merge data from new public ring into the old one
publicRing = oldPublicRing.merge(publicRing, mLog, mIndent);
// If this is null, there is an error in the log so we can just return
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
// Canonicalize this keyring, to assert a number of assumptions made about it.
publicRing = publicRing.canonicalize(mLog, mIndent);
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
// Early breakout if nothing changed
if (Arrays.hashCode(publicRing.getEncoded())
== Arrays.hashCode(oldPublicRing.getEncoded())) {
log(LogLevel.OK, LogType.MSG_IP_SUCCESS_IDENTICAL, null);
return new SaveKeyringResult(SaveKeyringResult.RESULT_OK, mLog);
}
} catch (NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring.
// Canonicalize this keyring, to assert a number of assumptions made about it.
publicRing = publicRing.canonicalize(mLog, mIndent);
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
}
// If there is a secret key, merge new data (if any) and save the key for later
UncachedKeyRing secretRing;
try {
secretRing = getWrappedSecretKeyRing(publicRing.getMasterKeyId()).getUncached();
// Merge data from new public ring into secret one
secretRing = secretRing.merge(publicRing, mLog, mIndent);
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
secretRing = secretRing.canonicalize(mLog, mIndent);
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
} catch (NotFoundException e) {
// No secret key available (this is what happens most of the time)
secretRing = null;
}
int result = internalSavePublicKeyRing(publicRing, progress, secretRing != null);
// Save the saved keyring (if any)
if (secretRing != null) {
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
int secretResult = internalSaveSecretKeyRing(secretRing);
if ((secretResult & SaveKeyringResult.RESULT_ERROR) != SaveKeyringResult.RESULT_ERROR) {
result |= SaveKeyringResult.SAVED_SECRET;
}
}
mIndent -= 1;
return new SaveKeyringResult(result, mLog);
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IP_FAIL_IO_EXC);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
}
public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing secretRing, Progressable progress) {
try {
long masterKeyId = secretRing.getMasterKeyId();
log(LogLevel.START, LogType.MSG_IS,
new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) });
mIndent += 1;
// If there is an old secret key, merge it.
try {
UncachedKeyRing oldSecretRing = getWrappedSecretKeyRing(masterKeyId).getUncached();
// Merge data from new secret ring into old one
secretRing = oldSecretRing.merge(secretRing, mLog, mIndent);
// If this is null, there is an error in the log so we can just return
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
// Canonicalize this keyring, to assert a number of assumptions made about it.
secretRing = secretRing.canonicalize(mLog, mIndent);
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
// Early breakout if nothing changed
if (Arrays.hashCode(secretRing.getEncoded())
== Arrays.hashCode(oldSecretRing.getEncoded())) {
log(LogLevel.OK, LogType.MSG_IS_SUCCESS_IDENTICAL,
new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) });
return new SaveKeyringResult(SaveKeyringResult.RESULT_OK, mLog);
}
} catch (NotFoundException e) {
// Not an issue, just means we are dealing with a new keyring
// Canonicalize this keyring, to assert a number of assumptions made about it.
secretRing = secretRing.canonicalize(mLog, mIndent);
if (secretRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
}
// Merge new data into public keyring as well, if there is any
UncachedKeyRing publicRing;
try {
UncachedKeyRing oldPublicRing = getWrappedPublicKeyRing(masterKeyId).getUncached();
// Merge data from new public ring into secret one
publicRing = oldPublicRing.merge(secretRing, mLog, mIndent);
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
// If nothing changed, never mind
if (Arrays.hashCode(publicRing.getEncoded())
== Arrays.hashCode(oldPublicRing.getEncoded())) {
publicRing = null;
}
} catch (NotFoundException e) {
log(LogLevel.DEBUG, LogType.MSG_IS_PUBRING_GENERATE, null);
publicRing = secretRing.extractPublicKeyRing();
}
if (publicRing != null) {
publicRing = publicRing.canonicalize(mLog, mIndent);
if (publicRing == null) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
int result = internalSavePublicKeyRing(publicRing, progress, true);
if ((result & SaveKeyringResult.RESULT_ERROR) == SaveKeyringResult.RESULT_ERROR) {
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
}
progress.setProgress(LogType.MSG_IP_REINSERT_SECRET.getMsgId(), 90, 100);
int result = internalSaveSecretKeyRing(secretRing);
return new SaveKeyringResult(result, mLog);
} catch (IOException e) {
log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC, null);
return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog);
}
}
/**
* Saves (or updates) a pair of public and secret KeyRings in the database
*/
public void saveKeyRing(UncachedKeyRing pubRing, UncachedKeyRing secRing) throws IOException {
long masterKeyId = pubRing.getPublicKey().getKeyId();
@Deprecated // scheduled for deletion after merge with new-edit branch
public void savePairedKeyRing(UncachedKeyRing pubRing, UncachedKeyRing secRing) throws IOException {
long masterKeyId = pubRing.getMasterKeyId();
// delete secret keyring (so it isn't unnecessarily saved by public-savePublicKeyRing below)
mContentResolver.delete(KeyRingData.buildSecretKeyRingUri(Long.toString(masterKeyId)), null, null);
// save public keyring
savePublicKeyRing(pubRing);
saveSecretKeyRing(secRing);
}
/**
* Build ContentProviderOperation to add PGPPublicKey to database corresponding to a keyRing
*/
private ContentProviderOperation
buildPublicKeyOperations(long masterKeyId, UncachedPublicKey key, int rank) throws IOException {
ContentValues values = new ContentValues();
values.put(Keys.MASTER_KEY_ID, masterKeyId);
values.put(Keys.RANK, rank);
values.put(Keys.KEY_ID, key.getKeyId());
values.put(Keys.KEY_SIZE, key.getBitStrength());
values.put(Keys.ALGORITHM, key.getAlgorithm());
values.put(Keys.FINGERPRINT, key.getFingerprint());
values.put(Keys.CAN_CERTIFY, key.canCertify());
values.put(Keys.CAN_SIGN, key.canSign());
values.put(Keys.CAN_ENCRYPT, key.canEncrypt());
values.put(Keys.IS_REVOKED, key.maybeRevoked());
values.put(Keys.CREATION, key.getCreationTime().getTime() / 1000);
Date expiryDate = key.getExpiryTime();
if (expiryDate != null) {
values.put(Keys.EXPIRY, expiryDate.getTime() / 1000);
}
Uri uri = Keys.buildKeysUri(Long.toString(masterKeyId));
return ContentProviderOperation.newInsert(uri).withValues(values).build();
internalSavePublicKeyRing(pubRing, null, true);
internalSaveSecretKeyRing(secRing);
}
/**
@ -719,9 +976,6 @@ public class ProviderHelper {
/**
* Must be an uri pointing to an account
*
* @param uri
* @return
*/
public AppSettings getApiAppSettings(Uri uri) {
AppSettings settings = null;

View File

@ -30,10 +30,14 @@ import org.sufficientlysecure.keychain.helper.ContactHelper;
import org.sufficientlysecure.keychain.helper.EmailKeyHelper;
import org.sufficientlysecure.keychain.util.Log;
import java.util.concurrent.atomic.AtomicBoolean;
public class ContactSyncAdapterService extends Service {
private class ContactSyncAdapter extends AbstractThreadedSyncAdapter {
private final AtomicBoolean importDone = new AtomicBoolean(false);
public ContactSyncAdapter() {
super(ContactSyncAdapterService.this, true);
}
@ -41,6 +45,8 @@ public class ContactSyncAdapterService extends Service {
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
final SyncResult syncResult) {
importDone.set(false);
KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
EmailKeyHelper.importContacts(getContext(), new Messenger(new Handler(Looper.getMainLooper(),
new Handler.Callback() {
@Override
@ -48,11 +54,16 @@ public class ContactSyncAdapterService extends Service {
Bundle data = msg.getData();
switch (msg.arg1) {
case KeychainIntentServiceHandler.MESSAGE_OKAY:
Log.d(Constants.TAG, "Syncing... Done.");
synchronized (importDone) {
importDone.set(true);
importDone.notifyAll();
}
return true;
case KeychainIntentServiceHandler.MESSAGE_UPDATE_PROGRESS:
if (data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS) &&
data.containsKey(KeychainIntentServiceHandler.DATA_PROGRESS_MAX)) {
Log.d(Constants.TAG, "Progress: " +
Log.d(Constants.TAG, "Syncing... Progress: " +
data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS) + "/" +
data.getInt(KeychainIntentServiceHandler.DATA_PROGRESS_MAX));
return false;
@ -63,7 +74,14 @@ public class ContactSyncAdapterService extends Service {
}
}
})));
KeychainApplication.setupAccountAsNeeded(ContactSyncAdapterService.this);
synchronized (importDone) {
try {
if (!importDone.get()) importDone.wait();
} catch (InterruptedException e) {
Log.w(Constants.TAG, e);
return;
}
}
ContactHelper.writeKeysToContacts(ContactSyncAdapterService.this);
}
}

View File

@ -44,7 +44,6 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedPublicKeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKey;
import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;
@ -53,6 +52,7 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLog;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.ProgressScaler;
@ -87,9 +87,6 @@ public class KeychainIntentService extends IntentService
public static final String ACTION_DECRYPT_VERIFY = Constants.INTENT_PREFIX + "DECRYPT_VERIFY";
public static final String ACTION_SAVE_KEYRING = Constants.INTENT_PREFIX + "SAVE_KEYRING";
public static final String ACTION_GENERATE_KEY = Constants.INTENT_PREFIX + "GENERATE_KEY";
public static final String ACTION_GENERATE_DEFAULT_RSA_KEYS = Constants.INTENT_PREFIX
+ "GENERATE_DEFAULT_RSA_KEYS";
public static final String ACTION_DELETE_FILE_SECURELY = Constants.INTENT_PREFIX
+ "DELETE_FILE_SECURELY";
@ -131,14 +128,7 @@ public class KeychainIntentService extends IntentService
// save keyring
public static final String SAVE_KEYRING_PARCEL = "save_parcel";
public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
// generate key
public static final String GENERATE_KEY_ALGORITHM = "algorithm";
public static final String GENERATE_KEY_KEY_SIZE = "key_size";
public static final String GENERATE_KEY_SYMMETRIC_PASSPHRASE = "passphrase";
public static final String GENERATE_KEY_MASTER_KEY = "master_key";
public static final String SAVE_KEYRING_PASSPHRASE = "passphrase";
// delete file securely
public static final String DELETE_FILE = "deleteFile";
@ -168,9 +158,6 @@ public class KeychainIntentService extends IntentService
/*
* possible data keys as result send over messenger
*/
// keys
public static final String RESULT_NEW_KEY = "new_key";
public static final String RESULT_KEY_USAGES = "new_key_usages";
// encrypt
public static final String RESULT_BYTES = "encrypted_data";
@ -179,14 +166,11 @@ public class KeychainIntentService extends IntentService
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature";
// import
public static final String RESULT_IMPORT_ADDED = "added";
public static final String RESULT_IMPORT_UPDATED = "updated";
public static final String RESULT_IMPORT_BAD = "bad";
// export
public static final String RESULT_EXPORT = "exported";
public static final String RESULT = "result";
Messenger mMessenger;
private boolean mIsCanceled;
@ -335,136 +319,39 @@ public class KeychainIntentService extends IntentService
} else if (ACTION_SAVE_KEYRING.equals(action)) {
try {
/* Input */
OldSaveKeyringParcel saveParcel = data.getParcelable(SAVE_KEYRING_PARCEL);
String oldPassphrase = saveParcel.oldPassphrase;
String newPassphrase = saveParcel.newPassphrase;
boolean canSign = true;
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
}
if (newPassphrase == null) {
newPassphrase = oldPassphrase;
}
long masterKeyId = saveParcel.keys.get(0).getKeyId();
SaveKeyringParcel saveParcel = data.getParcelable(SAVE_KEYRING_PARCEL);
long masterKeyId = saveParcel.mMasterKeyId;
/* Operation */
ProviderHelper providerHelper = new ProviderHelper(this);
if (!canSign) {
setProgress(R.string.progress_building_key, 0, 100);
WrappedSecretKeyRing keyRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
UncachedKeyRing newKeyRing =
keyRing.changeSecretKeyPassphrase(oldPassphrase, newPassphrase);
setProgress(R.string.progress_saving_key_ring, 50, 100);
providerHelper.saveSecretKeyRing(newKeyRing);
setProgress(R.string.progress_done, 100, 100);
} else {
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100));
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 50, 100));
try {
WrappedSecretKeyRing seckey = providerHelper.getWrappedSecretKeyRing(masterKeyId);
WrappedPublicKeyRing pubkey = providerHelper.getWrappedPublicKeyRing(masterKeyId);
String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE);
WrappedSecretKeyRing secRing = providerHelper.getWrappedSecretKeyRing(masterKeyId);
PgpKeyOperation.Pair<UncachedKeyRing,UncachedKeyRing> pair =
keyOperations.buildSecretKey(seckey, pubkey, saveParcel); // edit existing
setProgress(R.string.progress_saving_key_ring, 90, 100);
providerHelper.saveKeyRing(pair.first, pair.second);
OperationLog log = new OperationLog();
UncachedKeyRing ring = keyOperations.modifySecretKeyRing(secRing, saveParcel,
passphrase, log, 0);
providerHelper.saveSecretKeyRing(ring, new ProgressScaler(this, 60, 95, 100));
} catch (ProviderHelper.NotFoundException e) {
PgpKeyOperation.Pair<UncachedKeyRing,UncachedKeyRing> pair =
keyOperations.buildNewSecretKey(saveParcel); //new Keyring
// UncachedKeyRing ring = keyOperations.(saveParcel); //new Keyring
// save the pair
setProgress(R.string.progress_saving_key_ring, 90, 100);
providerHelper.saveKeyRing(pair.first, pair.second);
setProgress(R.string.progress_saving_key_ring, 95, 100);
// providerHelper.saveSecretKeyRing(ring);
sendErrorToHandler(e);
}
setProgress(R.string.progress_done, 100, 100);
if (saveParcel.newPassphrase != null) {
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, saveParcel.newPassphrase);
}
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassphrase);
/* Output */
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
} catch (Exception e) {
sendErrorToHandler(e);
}
} else if (ACTION_GENERATE_KEY.equals(action)) {
try {
/* Input */
int algorithm = data.getInt(GENERATE_KEY_ALGORITHM);
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
int keysize = data.getInt(GENERATE_KEY_KEY_SIZE);
boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
/* Operation */
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
byte[] newKey = keyOperations.createKey(algorithm, keysize, passphrase, masterKey);
/* Output */
Bundle resultData = new Bundle();
resultData.putByteArray(RESULT_NEW_KEY, newKey);
OtherHelper.logDebugBundle(resultData, "resultData");
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
} else if (ACTION_GENERATE_DEFAULT_RSA_KEYS.equals(action)) {
// generate one RSA 4096 key for signing and one subkey for encrypting!
try {
/* Input */
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
ArrayList<Integer> keyUsageList = new ArrayList<Integer>();
/* Operation */
int keysTotal = 3;
int keysCreated = 0;
setProgress(
getApplicationContext().getResources().
getQuantityString(R.plurals.progress_generating, keysTotal),
keysCreated,
keysTotal);
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buf;
buf = keyOperations.createKey(Constants.choice.algorithm.rsa,
4096, passphrase, true);
os.write(buf);
keyUsageList.add(UncachedSecretKey.CERTIFY_OTHER);
keysCreated++;
setProgress(keysCreated, keysTotal);
buf = keyOperations.createKey(Constants.choice.algorithm.rsa,
4096, passphrase, false);
os.write(buf);
keyUsageList.add(UncachedSecretKey.ENCRYPT_COMMS | UncachedSecretKey.ENCRYPT_STORAGE);
keysCreated++;
setProgress(keysCreated, keysTotal);
buf = keyOperations.createKey(Constants.choice.algorithm.rsa,
4096, passphrase, false);
os.write(buf);
keyUsageList.add(UncachedSecretKey.SIGN_DATA);
keysCreated++;
setProgress(keysCreated, keysTotal);
// TODO: default to one master for cert, one sub for encrypt and one sub
// for sign
/* Output */
Bundle resultData = new Bundle();
resultData.putByteArray(RESULT_NEW_KEY, os.toByteArray());
resultData.putIntegerArrayList(RESULT_KEY_USAGES, keyUsageList);
OtherHelper.logDebugBundle(resultData, "resultData");
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {
sendErrorToHandler(e);
}
} else if (ACTION_DELETE_FILE_SECURELY.equals(action)) {
try {
/* Input */
@ -491,7 +378,10 @@ public class KeychainIntentService extends IntentService
List<ParcelableKeyRing> entries = data.getParcelableArrayList(IMPORT_KEY_LIST);
PgpImportExport pgpImportExport = new PgpImportExport(this, this);
Bundle resultData = pgpImportExport.importKeyRings(entries);
OperationResults.ImportResult result = pgpImportExport.importKeyRings(entries);
Bundle resultData = new Bundle();
resultData.putParcelable(RESULT, result);
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY, resultData);
} catch (Exception e) {

View File

@ -1,128 +0,0 @@
/*
* Copyright (C) 2014 Ash Hughes <ashes-iontach@hotmail.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
import org.sufficientlysecure.keychain.pgp.UncachedSecretKey;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
/** Class for parcelling data between ui and services.
* This class is outdated and scheduled for removal, pending a rewrite of the
* EditKeyActivity and save keyring routines.
*/
@Deprecated
public class OldSaveKeyringParcel implements Parcelable {
public ArrayList<String> userIds;
public ArrayList<String> originalIDs;
public ArrayList<String> deletedIDs;
public boolean[] newIDs;
public boolean primaryIDChanged;
public boolean[] moddedKeys;
public ArrayList<UncachedSecretKey> deletedKeys;
public ArrayList<Calendar> keysExpiryDates;
public ArrayList<Integer> keysUsages;
public String newPassphrase;
public String oldPassphrase;
public boolean[] newKeys;
public ArrayList<UncachedSecretKey> keys;
public String originalPrimaryID;
public OldSaveKeyringParcel() {}
private OldSaveKeyringParcel(Parcel source) {
userIds = (ArrayList<String>) source.readSerializable();
originalIDs = (ArrayList<String>) source.readSerializable();
deletedIDs = (ArrayList<String>) source.readSerializable();
newIDs = source.createBooleanArray();
primaryIDChanged = source.readByte() != 0;
moddedKeys = source.createBooleanArray();
byte[] tmp = source.createByteArray();
if (tmp == null) {
deletedKeys = null;
} else {
deletedKeys = PgpConversionHelper.BytesToPGPSecretKeyList(tmp);
}
keysExpiryDates = (ArrayList<Calendar>) source.readSerializable();
keysUsages = source.readArrayList(Integer.class.getClassLoader());
newPassphrase = source.readString();
oldPassphrase = source.readString();
newKeys = source.createBooleanArray();
keys = PgpConversionHelper.BytesToPGPSecretKeyList(source.createByteArray());
originalPrimaryID = source.readString();
}
@Override
public void writeToParcel(Parcel destination, int flags) {
destination.writeSerializable(userIds); //might not be the best method to store.
destination.writeSerializable(originalIDs);
destination.writeSerializable(deletedIDs);
destination.writeBooleanArray(newIDs);
destination.writeByte((byte) (primaryIDChanged ? 1 : 0));
destination.writeBooleanArray(moddedKeys);
destination.writeByteArray(encodeArrayList(deletedKeys));
destination.writeSerializable(keysExpiryDates);
destination.writeList(keysUsages);
destination.writeString(newPassphrase);
destination.writeString(oldPassphrase);
destination.writeBooleanArray(newKeys);
destination.writeByteArray(encodeArrayList(keys));
destination.writeString(originalPrimaryID);
}
public static final Creator<OldSaveKeyringParcel> CREATOR = new Creator<OldSaveKeyringParcel>() {
public OldSaveKeyringParcel createFromParcel(final Parcel source) {
return new OldSaveKeyringParcel(source);
}
public OldSaveKeyringParcel[] newArray(final int size) {
return new OldSaveKeyringParcel[size];
}
};
private static byte[] encodeArrayList(ArrayList<UncachedSecretKey> list) {
if(list.isEmpty()) {
return null;
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
for(UncachedSecretKey key : new IterableIterator<UncachedSecretKey>(list.iterator())) {
try {
key.encodeSecretKey(os);
} catch (IOException e) {
Log.e(Constants.TAG, "Error while converting ArrayList<UncachedSecretKey> to byte[]!", e);
}
}
return os.toByteArray();
}
@Override
public int describeContents() {
return 0;
}
}

View File

@ -0,0 +1,318 @@
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
import android.os.Parcelable;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
/** Represent the result of an operation.
*
* This class holds a result and the log of an operation. It can be subclassed
* to include typed additional information specific to the operation. To keep
* the class structure (somewhat) simple, this class contains an exhaustive
* list (ie, enum) of all possible log types, which should in all cases be tied
* to string resource ids.
*
*
*/
public class OperationResultParcel implements Parcelable {
/** Holds the overall result, the number specifying varying degrees of success. The first bit
* is 0 on overall success, 1 on overall failure. All other bits may be used for more specific
* conditions. */
final int mResult;
public static final int RESULT_OK = 0;
public static final int RESULT_ERROR = 1;
/// A list of log entries tied to the operation result.
final OperationLog mLog;
public OperationResultParcel(int result, OperationLog log) {
mResult = result;
mLog = log;
}
public OperationResultParcel(Parcel source) {
mResult = source.readInt();
mLog = new OperationLog();
mLog.addAll(source.createTypedArrayList(LogEntryParcel.CREATOR));
}
public int getResult() {
return mResult;
}
public boolean success() {
return (mResult & 1) == 0;
}
public OperationLog getLog() {
return mLog;
}
/** One entry in the log. */
public static class LogEntryParcel implements Parcelable {
public final LogLevel mLevel;
public final LogType mType;
public final String[] mParameters;
public final int mIndent;
public LogEntryParcel(LogLevel level, LogType type, String[] parameters, int indent) {
mLevel = level;
mType = type;
mParameters = parameters;
mIndent = indent;
}
public LogEntryParcel(LogLevel level, LogType type, String[] parameters) {
this(level, type, parameters, 0);
}
public LogEntryParcel(Parcel source) {
mLevel = LogLevel.values()[source.readInt()];
mType = LogType.values()[source.readInt()];
mParameters = source.createStringArray();
mIndent = source.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mLevel.ordinal());
dest.writeInt(mType.ordinal());
dest.writeStringArray(mParameters);
dest.writeInt(mIndent);
}
public static final Creator<LogEntryParcel> CREATOR = new Creator<LogEntryParcel>() {
public LogEntryParcel createFromParcel(final Parcel source) {
return new LogEntryParcel(source);
}
public LogEntryParcel[] newArray(final int size) {
return new LogEntryParcel[size];
}
};
}
/** This is an enum of all possible log events.
*
* Element names should generally be prefixed with MSG_XX_ where XX is an
* identifier based on the related activity.
*
* Log messages should occur for each distinguishable action group. For
* each such group, one message is displayed followed by warnings or
* errors, and optionally subactions. The granularity should generally be
* optimistic: No "success" messages are printed except for the outermost
* operations - the success of an action group is indicated by the
* beginning message of the next action group.
*
* Log messages should be in present tense, There should be no trailing
* punctuation, except for error messages which may end in an exclamation
* mark.
*
*/
public static enum LogType {
// import public
MSG_IP(R.string.msg_ip),
MSG_IP_APPLY_BATCH (R.string.msg_ip_apply_batch),
MSG_IP_BAD_TYPE_SECRET (R.string.msg_ip_bad_type_secret),
MSG_IP_DELETE_OLD_FAIL (R.string.msg_ip_delete_old_fail),
MSG_IP_DELETE_OLD_OK (R.string.msg_ip_delete_old_ok),
MSG_IP_ENCODE_FAIL (R.string.msg_ip_encode_fail),
MSG_IP_FAIL_IO_EXC (R.string.msg_ip_fail_io_exc),
MSG_IP_FAIL_OP_EXC (R.string.msg_ip_fail_op_exc),
MSG_IP_FAIL_REMOTE_EX (R.string.msg_ip_fail_remote_ex),
MSG_IP_INSERT_KEYRING (R.string.msg_ip_insert_keyring),
MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_keys),
MSG_IP_PREPARE (R.string.msg_ip_prepare),
MSG_IP_REINSERT_SECRET (R.string.msg_ip_reinsert_secret),
MSG_IP_MASTER (R.string.msg_ip_master),
MSG_IP_MASTER_EXPIRED (R.string.msg_ip_master_expired),
MSG_IP_MASTER_EXPIRES (R.string.msg_ip_master_expires),
MSG_IP_MASTER_FLAGS_CES (R.string.msg_ip_master_flags_ces),
MSG_IP_MASTER_FLAGS_CEX (R.string.msg_ip_master_flags_cex),
MSG_IP_MASTER_FLAGS_CXS (R.string.msg_ip_master_flags_cxs),
MSG_IP_MASTER_FLAGS_XES (R.string.msg_ip_master_flags_xes),
MSG_IP_MASTER_FLAGS_CXX (R.string.msg_ip_master_flags_cxx),
MSG_IP_MASTER_FLAGS_XEX (R.string.msg_ip_master_flags_xex),
MSG_IP_MASTER_FLAGS_XXS (R.string.msg_ip_master_flags_xxs),
MSG_IP_MASTER_FLAGS_XXX (R.string.msg_ip_master_flags_xxx),
MSG_IP_SUBKEY (R.string.msg_ip_subkey),
MSG_IP_SUBKEY_EXPIRED (R.string.msg_ip_subkey_expired),
MSG_IP_SUBKEY_EXPIRES (R.string.msg_ip_subkey_expires),
MSG_IP_SUBKEY_FLAGS_CES (R.string.msg_ip_subkey_flags_ces),
MSG_IP_SUBKEY_FLAGS_CEX (R.string.msg_ip_subkey_flags_cex),
MSG_IP_SUBKEY_FLAGS_CXS (R.string.msg_ip_subkey_flags_cxs),
MSG_IP_SUBKEY_FLAGS_XES (R.string.msg_ip_subkey_flags_xes),
MSG_IP_SUBKEY_FLAGS_CXX (R.string.msg_ip_subkey_flags_cxx),
MSG_IP_SUBKEY_FLAGS_XEX (R.string.msg_ip_subkey_flags_xex),
MSG_IP_SUBKEY_FLAGS_XXS (R.string.msg_ip_subkey_flags_xxs),
MSG_IP_SUBKEY_FLAGS_XXX (R.string.msg_ip_subkey_flags_xxx),
MSG_IP_SUCCESS (R.string.msg_ip_success),
MSG_IP_SUCCESS_IDENTICAL (R.string.msg_ip_success_identical),
MSG_IP_UID_CERT_BAD (R.string.msg_ip_uid_cert_bad),
MSG_IP_UID_CERT_ERROR (R.string.msg_ip_uid_cert_error),
MSG_IP_UID_CERT_GOOD (R.string.msg_ip_uid_cert_good),
MSG_IP_UID_CERTS_UNKNOWN (R.string.msg_ip_uid_certs_unknown),
MSG_IP_UID_CLASSIFYING (R.string.msg_ip_uid_classifying),
MSG_IP_UID_REORDER(R.string.msg_ip_uid_reorder),
MSG_IP_UID_PROCESSING (R.string.msg_ip_uid_processing),
MSG_IP_UID_REVOKED (R.string.msg_ip_uid_revoked),
// import secret
MSG_IS(R.string.msg_is),
MSG_IS_BAD_TYPE_PUBLIC (R.string.msg_is_bad_type_public),
MSG_IS_DB_EXCEPTION (R.string.msg_is_db_exception),
MSG_IS_FAIL_IO_EXC (R.string.msg_is_io_exc),
MSG_IS_IMPORTING_SUBKEYS (R.string.msg_is_importing_subkeys),
MSG_IS_PUBRING_GENERATE (R.string.msg_is_pubring_generate),
MSG_IS_SUBKEY_NONEXISTENT (R.string.msg_is_subkey_nonexistent),
MSG_IS_SUBKEY_OK (R.string.msg_is_subkey_ok),
MSG_IS_SUBKEY_STRIPPED (R.string.msg_is_subkey_stripped),
MSG_IS_SUCCESS_IDENTICAL (R.string.msg_is_success_identical),
MSG_IS_SUCCESS (R.string.msg_is_success),
// keyring canonicalization
MSG_KC_PUBLIC (R.string.msg_kc_public),
MSG_KC_SECRET (R.string.msg_kc_secret),
MSG_KC_FATAL_NO_UID (R.string.msg_kc_fatal_no_uid),
MSG_KC_MASTER (R.string.msg_kc_master),
MSG_KC_REVOKE_BAD_ERR (R.string.msg_kc_revoke_bad_err),
MSG_KC_REVOKE_BAD_LOCAL (R.string.msg_kc_revoke_bad_local),
MSG_KC_REVOKE_BAD_TIME (R.string.msg_kc_revoke_bad_time),
MSG_KC_REVOKE_BAD_TYPE (R.string.msg_kc_revoke_bad_type),
MSG_KC_REVOKE_BAD (R.string.msg_kc_revoke_bad),
MSG_KC_REVOKE_DUP (R.string.msg_kc_revoke_dup),
MSG_KC_SUB (R.string.msg_kc_sub),
MSG_KC_SUB_BAD(R.string.msg_kc_sub_bad),
MSG_KC_SUB_BAD_ERR(R.string.msg_kc_sub_bad_err),
MSG_KC_SUB_BAD_LOCAL(R.string.msg_kc_sub_bad_local),
MSG_KC_SUB_BAD_KEYID(R.string.msg_kc_sub_bad_keyid),
MSG_KC_SUB_BAD_TIME(R.string.msg_kc_sub_bad_time),
MSG_KC_SUB_BAD_TYPE(R.string.msg_kc_sub_bad_type),
MSG_KC_SUB_PRIMARY_BAD(R.string.msg_kc_sub_primary_bad),
MSG_KC_SUB_PRIMARY_BAD_ERR(R.string.msg_kc_sub_primary_bad_err),
MSG_KC_SUB_PRIMARY_NONE(R.string.msg_kc_sub_primary_none),
MSG_KC_SUB_NO_CERT(R.string.msg_kc_sub_no_cert),
MSG_KC_SUB_REVOKE_BAD_ERR (R.string.msg_kc_sub_revoke_bad_err),
MSG_KC_SUB_REVOKE_BAD (R.string.msg_kc_sub_revoke_bad),
MSG_KC_SUB_REVOKE_DUP (R.string.msg_kc_sub_revoke_dup),
MSG_KC_SUCCESS_BAD (R.string.msg_kc_success_bad),
MSG_KC_SUCCESS_BAD_AND_RED (R.string.msg_kc_success_bad_and_red),
MSG_KC_SUCCESS_REDUNDANT (R.string.msg_kc_success_redundant),
MSG_KC_SUCCESS (R.string.msg_kc_success),
MSG_KC_UID_BAD_ERR (R.string.msg_kc_uid_bad_err),
MSG_KC_UID_BAD_LOCAL (R.string.msg_kc_uid_bad_local),
MSG_KC_UID_BAD_TIME (R.string.msg_kc_uid_bad_time),
MSG_KC_UID_BAD_TYPE (R.string.msg_kc_uid_bad_type),
MSG_KC_UID_BAD (R.string.msg_kc_uid_bad),
MSG_KC_UID_DUP (R.string.msg_kc_uid_dup),
MSG_KC_UID_FOREIGN (R.string.msg_kc_uid_foreign),
MSG_KC_UID_NO_CERT (R.string.msg_kc_uid_no_cert),
MSG_KC_UID_REVOKE_DUP (R.string.msg_kc_uid_revoke_dup),
MSG_KC_UID_REVOKE_OLD (R.string.msg_kc_uid_revoke_old),
// keyring consolidation
MSG_MG_PUBLIC (R.string.msg_mg_public),
MSG_MG_SECRET (R.string.msg_mg_secret),
MSG_MG_FATAL_ENCODE (R.string.msg_mg_fatal_encode),
MSG_MG_HETEROGENEOUS (R.string.msg_mg_heterogeneous),
MSG_MG_NEW_SUBKEY (R.string.msg_mg_new_subkey),
MSG_MG_FOUND_NEW (R.string.msg_mg_found_new),
// secret key modify
MSG_MF (R.string.msg_mr),
MSG_MF_ERROR_ENCODE (R.string.msg_mf_error_encode),
MSG_MF_ERROR_PGP (R.string.msg_mf_error_pgp),
MSG_MF_ERROR_SIG (R.string.msg_mf_error_sig),
MSG_MF_PASSPHRASE (R.string.msg_mf_passphrase),
MSG_MF_SUBKEY_CHANGE (R.string.msg_mf_subkey_change),
MSG_MF_SUBKEY_MISSING (R.string.msg_mf_subkey_missing),
MSG_MF_SUBKEY_NEW_ID (R.string.msg_mf_subkey_new_id),
MSG_MF_SUBKEY_NEW (R.string.msg_mf_subkey_new),
MSG_MF_SUBKEY_PAST_EXPIRY (R.string.msg_mf_subkey_past_expiry),
MSG_MF_SUBKEY_REVOKE (R.string.msg_mf_subkey_revoke),
MSG_MF_SUCCESS (R.string.msg_mf_success),
MSG_MF_UID_ADD (R.string.msg_mf_uid_add),
MSG_MF_UID_PRIMARY (R.string.msg_mf_uid_primary),
MSG_MF_UID_REVOKE (R.string.msg_mf_uid_revoke),
MSG_MF_UNLOCK_ERROR (R.string.msg_mf_unlock_error),
MSG_MF_UNLOCK (R.string.msg_mf_unlock),
;
private final int mMsgId;
LogType(int msgId) {
mMsgId = msgId;
}
public int getMsgId() {
return mMsgId;
}
}
/** Enumeration of possible log levels. */
public static enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR, // should occur once at the end of a failed operation
START, // should occur once at the start of each independent operation
OK, // should occur once at the end of a successful operation
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mResult);
dest.writeTypedList(mLog);
}
public static final Creator<OperationResultParcel> CREATOR = new Creator<OperationResultParcel>() {
public OperationResultParcel createFromParcel(final Parcel source) {
return new OperationResultParcel(source);
}
public OperationResultParcel[] newArray(final int size) {
return new OperationResultParcel[size];
}
};
public static class OperationLog extends ArrayList<LogEntryParcel> {
/// Simple convenience method
public void add(LogLevel level, LogType type, String[] parameters, int indent) {
Log.d(Constants.TAG, type.toString());
add(new OperationResultParcel.LogEntryParcel(level, type, parameters, indent));
}
public void add(LogLevel level, LogType type, int indent) {
add(new OperationResultParcel.LogEntryParcel(level, type, null, indent));
}
public boolean containsWarnings() {
for(LogEntryParcel entry : new IterableIterator<LogEntryParcel>(iterator())) {
if (entry.mLevel == LogLevel.WARN || entry.mLevel == LogLevel.ERROR) {
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,92 @@
package org.sufficientlysecure.keychain.service;
import android.os.Parcel;
public abstract class OperationResults {
public static class ImportResult extends OperationResultParcel {
public final int mNewKeys, mUpdatedKeys, mBadKeys;
// At least one new key
public static final int RESULT_OK_NEWKEYS = 2;
// At least one updated key
public static final int RESULT_OK_UPDATED = 4;
// At least one key failed (might still be an overall success)
public static final int RESULT_WITH_ERRORS = 8;
// There are warnings in the log
public static final int RESULT_WITH_WARNINGS = 16;
// No keys to import...
public static final int RESULT_FAIL_NOTHING = 32 +1;
public boolean isOkBoth() {
return (mResult & (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED))
== (RESULT_OK_NEWKEYS | RESULT_OK_UPDATED);
}
public boolean isOkNew() {
return (mResult & RESULT_OK_NEWKEYS) == RESULT_OK_NEWKEYS;
}
public boolean isOkUpdated() {
return (mResult & RESULT_OK_UPDATED) == RESULT_OK_UPDATED;
}
public boolean isFailNothing() {
return (mResult & RESULT_FAIL_NOTHING) == RESULT_FAIL_NOTHING;
}
public ImportResult(Parcel source) {
super(source);
mNewKeys = source.readInt();
mUpdatedKeys = source.readInt();
mBadKeys = source.readInt();
}
public ImportResult(int result, OperationLog log,
int newKeys, int updatedKeys, int badKeys) {
super(result, log);
mNewKeys = newKeys;
mUpdatedKeys = updatedKeys;
mBadKeys = badKeys;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mNewKeys);
dest.writeInt(mUpdatedKeys);
dest.writeInt(mBadKeys);
}
public static Creator<ImportResult> CREATOR = new Creator<ImportResult>() {
public ImportResult createFromParcel(final Parcel source) {
return new ImportResult(source);
}
public ImportResult[] newArray(final int size) {
return new ImportResult[size];
}
};
}
public static class SaveKeyringResult extends OperationResultParcel {
public SaveKeyringResult(int result, OperationLog log) {
super(result, log);
}
// Some old key was updated
public static final int UPDATED = 2;
// Public key was saved
public static final int SAVED_PUBLIC = 8;
// Secret key was saved (not exclusive with public!)
public static final int SAVED_SECRET = 16;
public boolean updated() {
return (mResult & UPDATED) == UPDATED;
}
}
}

View File

@ -23,16 +23,16 @@ import java.util.HashMap;
public class SaveKeyringParcel implements Parcelable {
// the master key id to be edited
private final long mMasterKeyId;
public final long mMasterKeyId;
// the key fingerprint, for safety
private final byte[] mFingerprint;
public final byte[] mFingerprint;
public String newPassphrase;
public String[] addUserIds;
public SubkeyAdd[] addSubKeys;
public HashMap<Long, SubkeyChange> changeSubKeys;
public SubkeyChange[] changeSubKeys;
public String changePrimaryUserId;
public String[] revokeUserIds;
@ -76,7 +76,7 @@ public class SaveKeyringParcel implements Parcelable {
addUserIds = source.createStringArray();
addSubKeys = (SubkeyAdd[]) source.readSerializable();
changeSubKeys = (HashMap<Long,SubkeyChange>) source.readSerializable();
changeSubKeys = (SubkeyChange[]) source.readSerializable();
changePrimaryUserId = source.readString();
revokeUserIds = source.createStringArray();

View File

@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.util.Notify;
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
@ -128,7 +129,8 @@ public class DecryptFileFragment extends DecryptFragment {
}
if (mInputFilename.equals("")) {
AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
//AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
Notify.showNotify(getActivity(), R.string.no_file_selected, Notify.Style.ERROR);
return;
}

View File

@ -57,7 +57,6 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.OldSaveKeyringParcel;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
@ -199,13 +198,10 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// generate key
if (extras.containsKey(EXTRA_GENERATE_DEFAULT_KEYS)) {
/*
boolean generateDefaultKeys = extras.getBoolean(EXTRA_GENERATE_DEFAULT_KEYS);
if (generateDefaultKeys) {
// Send all information needed to service generate keys in other thread
final Intent serviceIntent = new Intent(this, KeychainIntentService.class);
serviceIntent.setAction(KeychainIntentService.ACTION_GENERATE_DEFAULT_RSA_KEYS);
// fill values for this action
Bundle data = new Bundle();
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE,
@ -265,6 +261,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
// start service with intent
startService(serviceIntent);
}
*/
}
} else {
buildLayout(false);
@ -547,6 +544,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
}
private void finallySaveClicked() {
/*
try {
// Send all information needed to service to edit key in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
@ -609,6 +607,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
AppMsg.STYLE_ALERT).show();
}
*/
}
private void cancelClicked() {

View File

@ -32,28 +32,39 @@ import android.os.Messenger;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import com.devspark.appmsg.AppMsg;
import com.github.johnpersano.supertoasts.SuperCardToast;
import com.github.johnpersano.supertoasts.SuperToast;
import com.github.johnpersano.supertoasts.util.OnClickWrapper;
import com.github.johnpersano.supertoasts.util.Style;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.OtherHelper;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyserver;
import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.dialog.BadImportKeyDialogFragment;
import org.sufficientlysecure.keychain.service.OperationResults.ImportResult;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.Locale;
public class ImportKeysActivity extends ActionBarActivity implements ActionBar.OnNavigationListener {
public class ImportKeysActivity extends ActionBarActivity {
public static final String ACTION_IMPORT_KEY = Constants.INTENT_PREFIX + "IMPORT_KEY";
public static final String ACTION_IMPORT_KEY_FROM_QR_CODE = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_QR_CODE";
@ -87,23 +98,18 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
private String[] mNavigationStrings;
private Fragment mCurrentFragment;
private View mImportButton;
private ViewPager mViewPager;
private SlidingTabLayout mSlidingTabLayout;
private PagerTabStripAdapter mTabsAdapter;
public static final int VIEW_PAGER_HEIGHT = 64; // dp
private static final Class[] NAVIGATION_CLASSES = new Class[]{
ImportKeysServerFragment.class,
ImportKeysFileFragment.class,
ImportKeysQrCodeFragment.class,
ImportKeysClipboardFragment.class,
ImportKeysNFCFragment.class,
ImportKeysKeybaseFragment.class
};
private static final int NAV_SERVER = 0;
private static final int NAV_FILE = 1;
private static final int NAV_QR_CODE = 2;
private static final int NAV_CLIPBOARD = 3;
private static final int NAV_NFC = 4;
private static final int NAV_KEYBASE = 5;
private static final int NAV_QR_CODE = 1;
private static final int NAV_FILE = 2;
private static final int NAV_KEYBASE = 3;
private int mCurrentNavPosition = -1;
private int mSwitchToTab = NAV_SERVER;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -111,6 +117,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
setContentView(R.layout.import_keys_activity);
mViewPager = (ViewPager) findViewById(R.id.import_pager);
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.import_sliding_tab_layout);
mImportButton = findViewById(R.id.import_import);
mImportButton.setOnClickListener(new OnClickListener() {
@Override
@ -124,19 +133,57 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
setTitle(R.string.nav_import);
} else {
getSupportActionBar().setDisplayShowTitleEnabled(false);
// set drop down navigation
Context context = getSupportActionBar().getThemedContext();
ArrayAdapter<CharSequence> navigationAdapter = ArrayAdapter.createFromResource(context,
R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item);
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this);
initTabs();
}
handleActions(savedInstanceState, getIntent());
}
private void initTabs() {
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
mSlidingTabLayout.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// resize view pager back to 64 if keyserver settings have been collapsed
if (getViewPagerHeight() > VIEW_PAGER_HEIGHT) {
resizeViewPager(VIEW_PAGER_HEIGHT);
}
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
Bundle serverBundle = new Bundle();
// serverBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ImportKeysServerFragment.class,
serverBundle, getString(R.string.import_tab_keyserver));
Bundle qrCodeBundle = new Bundle();
// importBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ImportKeysQrCodeFragment.class,
qrCodeBundle, getString(R.string.import_tab_qr_code));
Bundle fileBundle = new Bundle();
// importBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ImportKeysFileFragment.class,
fileBundle, getString(R.string.import_tab_direct));
Bundle keybaseBundle = new Bundle();
// keybaseBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ImportKeysKeybaseFragment.class,
keybaseBundle, getString(R.string.import_tab_keybase));
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
}
protected void handleActions(Bundle savedInstanceState, Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
@ -160,7 +207,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
/* Keychain's own Actions */
// display file fragment
loadNavFragment(NAV_FILE, null);
mViewPager.setCurrentItem(NAV_FILE);
if (dataUri != null) {
// action: directly load data
@ -195,7 +242,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
// display keyserver fragment with query
Bundle args = new Bundle();
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
loadNavFragment(NAV_SERVER, args);
// loadNavFragment(NAV_SERVER, args);
//TODO: load afterwards!
mSwitchToTab = NAV_SERVER;
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
@ -219,9 +268,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
return;
}
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
loadNavFragment(NAV_FILE, null);
mSwitchToTab = NAV_FILE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
@ -229,26 +277,28 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
// also exposed in AndroidManifest
// NOTE: this only displays the appropriate fragment, no actions are taken
loadNavFragment(NAV_QR_CODE, null);
mSwitchToTab = NAV_QR_CODE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else if (ACTION_IMPORT_KEY_FROM_NFC.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
loadNavFragment(NAV_NFC, null);
mSwitchToTab = NAV_QR_CODE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
loadNavFragment(NAV_KEYBASE, null);
mSwitchToTab = NAV_KEYBASE;
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else {
startListFragment(savedInstanceState, null, null, null);
}
mViewPager.setCurrentItem(mSwitchToTab);
}
private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) {
@ -271,54 +321,16 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
getSupportFragmentManager().executePendingTransactions();
}
/**
* "Basically, when using a list navigation, onNavigationItemSelected() is automatically
* called when your activity is created/re-created, whether you like it or not. To prevent
* your Fragment's onCreateView() from being called twice, this initial automatic call to
* onNavigationItemSelected() should check whether the Fragment is already in existence
* inside your Activity."
* <p/>
* from http://stackoverflow.com/a/14295474
* <p/>
* In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint,
* the fragment would be loaded twice resulting in the query being empty after the second load.
* <p/>
* Our solution:
* To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment
* checks against mCurrentNavPosition.
*
* @param itemPosition
* @param itemId
* @return
*/
@Override
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
Log.d(Constants.TAG, "onNavigationItemSelected");
loadNavFragment(itemPosition, null);
return true;
public void resizeViewPager(int dp) {
ViewGroup.LayoutParams params = mViewPager.getLayoutParams();
params.height = OtherHelper.dpToPx(this, dp);
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
}
private void loadNavFragment(int itemPosition, Bundle args) {
if (mCurrentNavPosition != itemPosition) {
if (ActionBar.NAVIGATION_MODE_LIST == getSupportActionBar().getNavigationMode()) {
getSupportActionBar().setSelectedNavigationItem(itemPosition);
}
loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[itemPosition]);
mCurrentNavPosition = itemPosition;
}
}
private void loadFragment(Class<?> clss, Bundle args, String tag) {
mCurrentFragment = Fragment.instantiate(this, clss.getName(), args);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment container with this fragment
// and give the fragment a tag name equal to the string at the position selected
ft.replace(R.id.import_navigation_fragment, mCurrentFragment, tag);
// Apply changes
ft.commit();
public int getViewPagerHeight() {
ViewGroup.LayoutParams params = mViewPager.getLayoutParams();
return OtherHelper.pxToDp(this, params.height);
}
public void loadFromFingerprintUri(Bundle savedInstanceState, Uri dataUri) {
@ -331,8 +343,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) {
if (fingerprint == null || fingerprint.length() < 40) {
AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint,
AppMsg.STYLE_ALERT).show();
SuperCardToast toast = SuperCardToast.create(this,
getString(R.string.import_qr_code_too_short_fingerprint),
SuperToast.Duration.LONG);
toast.setBackground(SuperToast.Background.RED);
toast.show();
return;
}
@ -342,7 +357,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
Bundle args = new Bundle();
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
loadNavFragment(NAV_SERVER, args);
// loadNavFragment(NAV_SERVER, args);
//TODO
// action: search directly
startListFragment(savedInstanceState, null, null, query);
@ -368,39 +385,94 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
// get returned data bundle
Bundle returnData = message.getData();
final ImportResult result =
returnData.<ImportResult>getParcelable(KeychainIntentService.RESULT);
int added = returnData.getInt(KeychainIntentService.RESULT_IMPORT_ADDED);
int updated = returnData
.getInt(KeychainIntentService.RESULT_IMPORT_UPDATED);
int bad = returnData.getInt(KeychainIntentService.RESULT_IMPORT_BAD);
String toastMessage;
if (added > 0 && updated > 0) {
String addedStr = getResources().getQuantityString(
R.plurals.keys_added_and_updated_1, added, added);
String updatedStr = getResources().getQuantityString(
R.plurals.keys_added_and_updated_2, updated, updated);
toastMessage = addedStr + updatedStr;
} else if (added > 0) {
toastMessage = getResources().getQuantityString(R.plurals.keys_added,
added, added);
} else if (updated > 0) {
toastMessage = getResources().getQuantityString(R.plurals.keys_updated,
updated, updated);
int resultType = result.getResult();
String str;
int duration, color;
// Not an overall failure
if ((resultType & ImportResult.RESULT_ERROR) == 0) {
String withWarnings;
// Any warnings?
if ((resultType & ImportResult.RESULT_WITH_WARNINGS) > 0) {
duration = 0;
color = Style.ORANGE;
withWarnings = getResources().getString(R.string.import_with_warnings);
} else {
toastMessage = getString(R.string.no_keys_added_or_updated);
duration = SuperToast.Duration.LONG;
color = Style.GREEN;
withWarnings = "";
}
AppMsg.makeText(ImportKeysActivity.this, toastMessage, AppMsg.STYLE_INFO)
.show();
// New and updated keys
if (result.isOkBoth()) {
str = getResources().getQuantityString(
R.plurals.import_keys_added_and_updated_1, result.mNewKeys, result.mNewKeys);
str += getResources().getQuantityString(
R.plurals.import_keys_added_and_updated_2, result.mUpdatedKeys, result.mUpdatedKeys, withWarnings);
} else if (result.isOkUpdated()) {
str = getResources().getQuantityString(
R.plurals.import_keys_updated, result.mUpdatedKeys, result.mUpdatedKeys, withWarnings);
} else if (result.isOkNew()) {
str = getResources().getQuantityString(
R.plurals.import_keys_added, result.mNewKeys, result.mNewKeys, withWarnings);
} else {
duration = 0;
color = Style.RED;
str = "internal error";
}
} else {
duration = 0;
color = Style.RED;
if (result.isFailNothing()) {
str = getString(R.string.import_error_nothing);
} else {
str = getString(R.string.import_error);
}
}
SuperCardToast toast = new SuperCardToast(ImportKeysActivity.this,
SuperToast.Type.BUTTON, Style.getStyle(color, SuperToast.Animations.POPUP));
toast.setText(str);
toast.setDuration(duration);
toast.setIndeterminate(duration == 0);
toast.setSwipeToDismiss(true);
toast.setButtonIcon(R.drawable.ic_action_view_as_list,
getResources().getString(R.string.import_view_log));
toast.setButtonTextColor(getResources().getColor(R.color.black));
toast.setTextColor(getResources().getColor(R.color.black));
toast.setOnClickWrapper(new OnClickWrapper("supercardtoast",
new SuperToast.OnClickListener() {
@Override
public void onClick(View view, Parcelable token) {
Intent intent = new Intent(
ImportKeysActivity.this, LogDisplayActivity.class);
intent.putExtra(LogDisplayFragment.EXTRA_RESULT, result);
startActivity(intent);
}
}
));
toast.show();
/*
if (bad > 0) {
BadImportKeyDialogFragment badImportKeyDialogFragment =
BadImportKeyDialogFragment.newInstance(bad);
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
}
*/
/*
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData);
finish();
}
*/
}
}
};
@ -483,7 +555,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
startService(intent);
} else {
AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show();
SuperCardToast toast = SuperCardToast.create(this,
getString(R.string.error_nothing_import),
SuperToast.Duration.LONG);
toast.setBackground(SuperToast.Background.RED);
toast.show();
}
}
@ -495,9 +571,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
super.onResume();
// Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
handleActionNdefDiscovered(getIntent());
} else {
Log.d(Constants.TAG, "NFC: No NDEF discovered!");
}
} else {
Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1");
}

View File

@ -1,88 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import java.util.Locale;
public class ImportKeysClipboardFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private BootstrapButton mButton;
/**
* Creates new instance of this fragment
*/
public static ImportKeysClipboardFragment newInstance() {
ImportKeysClipboardFragment frag = new ImportKeysClipboardFragment();
Bundle args = new Bundle();
frag.setArguments(args);
return frag;
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_clipboard_fragment, container, false);
mButton = (BootstrapButton) view.findViewById(R.id.import_clipboard_button);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
String sendText = "";
if (clipboardText != null) {
sendText = clipboardText.toString();
if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) {
mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText));
return;
}
}
mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mImportActivity = (ImportKeysActivity) getActivity();
}
}

View File

@ -19,21 +19,24 @@ package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.helper.FileHelper;
import java.util.Locale;
public class ImportKeysFileFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private BootstrapButton mBrowse;
private View mBrowse;
private View mClipboardButton;
public static final int REQUEST_CODE_FILE = 0x00007003;
@ -56,26 +59,45 @@ public class ImportKeysFileFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_file_fragment, container, false);
mBrowse = (BootstrapButton) view.findViewById(R.id.import_keys_file_browse);
mBrowse = view.findViewById(R.id.import_keys_file_browse);
mBrowse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// open .asc or .gpg files
// setting it to text/plain prevents Cynaogenmod's file manager from selecting asc
// setting it to text/plain prevents Cyanogenmod's file manager from selecting asc
// or gpg types!
FileHelper.openFile(ImportKeysFileFragment.this, Constants.Path.APP_DIR + "/",
"*/*", REQUEST_CODE_FILE);
}
});
mClipboardButton = view.findViewById(R.id.import_clipboard_button);
mClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
String sendText = "";
if (clipboardText != null) {
sendText = clipboardText.toString();
if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) {
mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText));
return;
}
}
mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null);
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onAttach(Activity activity) {
super.onAttach(activity);
mImportActivity = (ImportKeysActivity) getActivity();
mImportActivity = (ImportKeysActivity) activity;
}
@Override

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -29,8 +30,6 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.R;
/**
@ -40,7 +39,7 @@ import org.sufficientlysecure.keychain.R;
public class ImportKeysKeybaseFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private BootstrapButton mSearchButton;
private View mSearchButton;
private EditText mQueryEditText;
public static final String ARG_QUERY = "query";
@ -66,7 +65,7 @@ public class ImportKeysKeybaseFragment extends Fragment {
mQueryEditText = (EditText) view.findViewById(R.id.import_keybase_query);
mSearchButton = (BootstrapButton) view.findViewById(R.id.import_keybase_search);
mSearchButton = view.findViewById(R.id.import_keybase_search);
mSearchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -101,8 +100,6 @@ public class ImportKeysKeybaseFragment extends Fragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mImportActivity = (ImportKeysActivity) getActivity();
// set displayed values
if (getArguments() != null) {
if (getArguments().containsKey(ARG_QUERY)) {
@ -112,6 +109,13 @@ public class ImportKeysKeybaseFragment extends Fragment {
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mImportActivity = (ImportKeysActivity) activity;
}
private void search(String query) {
mImportActivity.loadCallback(null, null, null, null, query);
}

View File

@ -42,6 +42,7 @@ import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
@ -97,7 +98,7 @@ public class ImportKeysListFragment extends ListFragment implements
public ArrayList<ParcelableKeyRing> getSelectedData() {
ArrayList<ParcelableKeyRing> result = new ArrayList<ParcelableKeyRing>();
for(ImportKeysListEntry entry : getSelectedEntries()) {
for (ImportKeysListEntry entry : getSelectedEntries()) {
result.add(mCachedKeyData.get(entry.getKeyId()));
}
return result;
@ -273,17 +274,15 @@ public class ImportKeysListFragment extends ListFragment implements
// No error
mCachedKeyData = ((ImportKeysListLoader) loader).getParcelableRings();
} else if (error instanceof ImportKeysListLoader.FileHasNoContent) {
AppMsg.makeText(getActivity(), R.string.error_import_file_no_content,
AppMsg.STYLE_ALERT).show();
Notify.showNotify(getActivity(), R.string.error_import_file_no_content, Notify.Style.ERROR);
} else if (error instanceof ImportKeysListLoader.NonPgpPart) {
AppMsg.makeText(getActivity(),
Notify.showNotify(getActivity(),
((ImportKeysListLoader.NonPgpPart) error).getCount() + " " + getResources().
getQuantityString(R.plurals.error_import_non_pgp_part,
((ImportKeysListLoader.NonPgpPart) error).getCount()),
new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.confirm)).show();
Notify.Style.OK);
} else {
AppMsg.makeText(getActivity(), R.string.error_generic_report_bug,
new AppMsg.Style(AppMsg.LENGTH_LONG, R.color.alert)).show();
Notify.showNotify(getActivity(), R.string.error_generic_report_bug, Notify.Style.ERROR);
}
break;
@ -292,23 +291,17 @@ public class ImportKeysListFragment extends ListFragment implements
// TODO: possibly fine-tune message building for these two cases
if (error == null) {
AppMsg.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
mAdapter.getCount(), mAdapter.getCount()),
AppMsg.STYLE_INFO
).show();
// No error
} else if (error instanceof Keyserver.QueryTooShortException) {
AppMsg.makeText(getActivity(), R.string.error_keyserver_insufficient_query,
AppMsg.STYLE_ALERT).show();
Notify.showNotify(getActivity(), R.string.error_keyserver_insufficient_query, Notify.Style.ERROR);
} else if (error instanceof Keyserver.TooManyResponsesException) {
AppMsg.makeText(getActivity(), R.string.error_keyserver_too_many_responses,
AppMsg.STYLE_ALERT).show();
Notify.showNotify(getActivity(), R.string.error_keyserver_too_many_responses, Notify.Style.ERROR);
} else if (error instanceof Keyserver.QueryFailedException) {
Log.d(Constants.TAG,
"Unrecoverable keyserver query error: " + error.getLocalizedMessage());
String alert = getActivity().getString(R.string.error_searching_keys);
alert = alert + " (" + error.getLocalizedMessage() + ")";
AppMsg.makeText(getActivity(), alert, AppMsg.STYLE_ALERT).show();
Notify.showNotify(getActivity(), alert, Notify.Style.ERROR);
}
break;

View File

@ -1,70 +0,0 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.R;
public class ImportKeysNFCFragment extends Fragment {
private BootstrapButton mButton;
/**
* Creates new instance of this fragment
*/
public static ImportKeysNFCFragment newInstance() {
ImportKeysNFCFragment frag = new ImportKeysNFCFragment();
Bundle args = new Bundle();
frag.setArguments(args);
return frag;
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_nfc_fragment, container, false);
mButton = (BootstrapButton) view.findViewById(R.id.import_nfc_button);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// show nfc help
Intent intent = new Intent(getActivity(), HelpActivity.class);
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, HelpActivity.TAB_NFC);
startActivityForResult(intent, 0);
}
});
return view;
}
}

View File

@ -17,47 +17,47 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import com.google.zxing.integration.android.IntentResult;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Notify;
import java.util.ArrayList;
import java.util.Locale;
public class ImportKeysQrCodeFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private BootstrapButton mButton;
private TextView mText;
private ProgressBar mProgress;
private View mNfcButton;
private String[] mScannedContent;
private View mQrCodeButton;
private TextView mQrCodeText;
private ProgressBar mQrCodeProgress;
private String[] mQrCodeContent;
/**
* Creates new instance of this fragment
*/
public static ImportKeysQrCodeFragment newInstance() {
ImportKeysQrCodeFragment frag = new ImportKeysQrCodeFragment();
public static ImportKeysFileFragment newInstance() {
ImportKeysFileFragment frag = new ImportKeysFileFragment();
Bundle args = new Bundle();
frag.setArguments(args);
frag.setArguments(args);
return frag;
}
@ -68,11 +68,23 @@ public class ImportKeysQrCodeFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_qr_code_fragment, container, false);
mButton = (BootstrapButton) view.findViewById(R.id.import_qrcode_button);
mText = (TextView) view.findViewById(R.id.import_qrcode_text);
mProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress);
mNfcButton = view.findViewById(R.id.import_nfc_button);
mNfcButton.setOnClickListener(new View.OnClickListener() {
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// show nfc help
Intent intent = new Intent(getActivity(), HelpActivity.class);
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, HelpActivity.TAB_NFC);
startActivityForResult(intent, 0);
}
});
mQrCodeButton = view.findViewById(R.id.import_qrcode_button);
mQrCodeText = (TextView) view.findViewById(R.id.import_qrcode_text);
mQrCodeProgress = (ProgressBar) view.findViewById(R.id.import_qrcode_progress);
mQrCodeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -85,10 +97,10 @@ public class ImportKeysQrCodeFragment extends Fragment {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onAttach(Activity activity) {
super.onAttach(activity);
mImportActivity = (ImportKeysActivity) getActivity();
mImportActivity = (ImportKeysActivity) activity;
}
@Override
@ -122,8 +134,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
}
// fail...
AppMsg.makeText(getActivity(), R.string.import_qr_code_wrong, AppMsg.STYLE_ALERT)
.show();
Notify.showNotify(getActivity(), R.string.import_qr_code_wrong, Notify.Style.ERROR);
}
break;
@ -136,6 +147,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
}
}
public void importFingerprint(Uri dataUri) {
mImportActivity.loadFromFingerprintUri(null, dataUri);
}
@ -151,32 +163,31 @@ public class ImportKeysQrCodeFragment extends Fragment {
// first qr code -> setup
if (counter == 0) {
mScannedContent = new String[size];
mProgress.setMax(size);
mProgress.setVisibility(View.VISIBLE);
mText.setVisibility(View.VISIBLE);
mQrCodeContent = new String[size];
mQrCodeProgress.setMax(size);
mQrCodeProgress.setVisibility(View.VISIBLE);
mQrCodeText.setVisibility(View.VISIBLE);
}
if (mScannedContent == null || counter > mScannedContent.length) {
AppMsg.makeText(getActivity(), R.string.import_qr_code_start_with_one, AppMsg.STYLE_ALERT)
.show();
if (mQrCodeContent == null || counter > mQrCodeContent.length) {
Notify.showNotify(getActivity(), R.string.import_qr_code_start_with_one, Notify.Style.ERROR);
return;
}
// save scanned content
mScannedContent[counter] = content;
mQrCodeContent[counter] = content;
// get missing numbers
ArrayList<Integer> missing = new ArrayList<Integer>();
for (int i = 0; i < mScannedContent.length; i++) {
if (mScannedContent[i] == null) {
for (int i = 0; i < mQrCodeContent.length; i++) {
if (mQrCodeContent[i] == null) {
missing.add(i);
}
}
// update progress and text
int alreadyScanned = mScannedContent.length - missing.size();
mProgress.setProgress(alreadyScanned);
int alreadyScanned = mQrCodeContent.length - missing.size();
mQrCodeProgress.setProgress(alreadyScanned);
String missingString = "";
for (int m : missing) {
@ -188,17 +199,16 @@ public class ImportKeysQrCodeFragment extends Fragment {
String missingText = getResources().getQuantityString(R.plurals.import_qr_code_missing,
missing.size(), missingString);
mText.setText(missingText);
mQrCodeText.setText(missingText);
// finished!
if (missing.size() == 0) {
mText.setText(R.string.import_qr_code_finished);
mQrCodeText.setText(R.string.import_qr_code_finished);
String result = "";
for (String in : mScannedContent) {
for (String in : mQrCodeContent) {
result += in;
}
mImportActivity.loadCallback(result.getBytes(), null, null, null, null);
}
}
}

View File

@ -17,6 +17,7 @@
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
@ -32,8 +33,6 @@ import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
@ -46,8 +45,10 @@ public class ImportKeysServerFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private BootstrapButton mSearchButton;
private View mSearchButton;
private EditText mQueryEditText;
private View mConfigButton;
private View mConfigLayout;
private Spinner mServerSpinner;
private ArrayAdapter<String> mServerAdapter;
@ -73,14 +74,17 @@ public class ImportKeysServerFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_server_fragment, container, false);
mSearchButton = (BootstrapButton) view.findViewById(R.id.import_server_search);
mSearchButton = view.findViewById(R.id.import_server_search);
mQueryEditText = (EditText) view.findViewById(R.id.import_server_query);
mConfigButton = view.findViewById(R.id.import_server_config_button);
mConfigLayout = view.findViewById(R.id.import_server_config);
mServerSpinner = (Spinner) view.findViewById(R.id.import_server_spinner);
// add keyservers to spinner
mServerAdapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_spinner_item, Preferences.getPreferences(getActivity())
.getKeyServers());
.getKeyServers()
);
mServerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mServerSpinner.setAdapter(mServerAdapter);
if (mServerAdapter.getCount() > 0) {
@ -118,6 +122,17 @@ public class ImportKeysServerFragment extends Fragment {
}
});
mConfigButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mImportActivity.getViewPagerHeight() > ImportKeysActivity.VIEW_PAGER_HEIGHT) {
mImportActivity.resizeViewPager(ImportKeysActivity.VIEW_PAGER_HEIGHT);
} else {
mImportActivity.resizeViewPager(ImportKeysActivity.VIEW_PAGER_HEIGHT + 41);
}
}
});
return view;
}
@ -125,8 +140,6 @@ public class ImportKeysServerFragment extends Fragment {
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mImportActivity = (ImportKeysActivity) getActivity();
// set displayed values
if (getArguments() != null) {
if (getArguments().containsKey(ARG_QUERY)) {
@ -150,6 +163,13 @@ public class ImportKeysServerFragment extends Fragment {
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mImportActivity = (ImportKeysActivity) activity;
}
private void search(String query, String keyServer) {
mImportActivity.loadCallback(null, null, query, keyServer, null);
}

View File

@ -0,0 +1,21 @@
package org.sufficientlysecure.keychain.ui;
import android.os.Bundle;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v7.app.ActionBarActivity;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import org.sufficientlysecure.keychain.R;
public class LogDisplayActivity extends ActionBarActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.log_display_activity);
}
}

View File

@ -0,0 +1,175 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.service.OperationResultParcel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogEntryParcel;
import org.sufficientlysecure.keychain.service.OperationResultParcel.LogLevel;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
public class LogDisplayFragment extends ListFragment implements OnTouchListener {
HashMap<LogLevel,LogAdapter> mAdapters = new HashMap<LogLevel, LogAdapter>();
LogAdapter mAdapter;
LogLevel mLevel = LogLevel.DEBUG;
OperationResultParcel mResult;
GestureDetector mDetector;
public static final String EXTRA_RESULT = "log";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Intent intent = getActivity().getIntent();
if (intent.getExtras() == null || !intent.getExtras().containsKey(EXTRA_RESULT)) {
getActivity().finish();
return;
}
mResult = intent.<OperationResultParcel>getParcelableExtra(EXTRA_RESULT);
if (mResult == null) {
getActivity().finish();
return;
}
mAdapter = new LogAdapter(getActivity(), mResult.getLog(), LogLevel.DEBUG);
mAdapters.put(LogLevel.DEBUG, mAdapter);
setListAdapter(mAdapter);
mDetector = new GestureDetector(getActivity(), new SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float vx, float vy) {
Log.d(Constants.TAG, "x: " + vx + ", y: " + vy);
if (vx < -2000) {
decreaseLogLevel();
} else if (vx > 2000) {
increaseLogLevel();
}
return true;
}
});
}
public void decreaseLogLevel() {
switch (mLevel) {
case DEBUG: mLevel = LogLevel.INFO; break;
case INFO: mLevel = LogLevel.WARN; break;
}
refreshLevel();
}
public void increaseLogLevel() {
switch (mLevel) {
case INFO: mLevel = LogLevel.DEBUG; break;
case WARN: mLevel = LogLevel.INFO; break;
}
refreshLevel();
}
private void refreshLevel() {
/* TODO not sure if this is a good idea
if (!mAdapters.containsKey(mLevel)) {
mAdapters.put(mLevel, new LogAdapter(getActivity(), mResult.getLog(), mLevel));
}
mAdapter = mAdapters.get(mLevel);
setListAdapter(mAdapter);
*/
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setDividerHeight(0);
getListView().setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
mDetector.onTouchEvent(event);
return false;
}
private class LogAdapter extends ArrayAdapter<LogEntryParcel> {
private LayoutInflater mInflater;
private int dipFactor;
public LogAdapter(Context context, ArrayList<LogEntryParcel> log, LogLevel level) {
super(context, R.layout.log_display_item);
mInflater = LayoutInflater.from(getContext());
dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
(float) 8, getResources().getDisplayMetrics());
// we can't use addAll for a LogLevel.DEBUG shortcut here, unfortunately :(
for (LogEntryParcel e : log) {
if (e.mLevel.ordinal() >= level.ordinal()) {
add(e);
}
}
notifyDataSetChanged();
}
private class ItemHolder {
final TextView mText;
final ImageView mImg;
public ItemHolder(TextView text, ImageView image) {
mText = text;
mImg = image;
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LogEntryParcel entry = getItem(position);
ItemHolder ih;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.log_display_item, parent, false);
ih = new ItemHolder(
(TextView) convertView.findViewById(R.id.log_text),
(ImageView) convertView.findViewById(R.id.log_img)
);
convertView.setTag(ih);
} else {
ih = (ItemHolder) convertView.getTag();
}
ih.mText.setText(getResources().getString(entry.mType.getMsgId(), (Object[]) entry.mParameters));
ih.mText.setTextColor(entry.mLevel == LogLevel.DEBUG ? Color.GRAY : Color.BLACK);
convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0);
switch (entry.mLevel) {
case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break;
case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break;
case WARN: ih.mImg.setBackgroundColor(Color.YELLOW); break;
case ERROR: ih.mImg.setBackgroundColor(Color.RED); break;
case START: ih.mImg.setBackgroundColor(Color.GREEN); break;
case OK: ih.mImg.setBackgroundColor(Color.GREEN); break;
}
return convertView;
}
}
}

View File

@ -56,6 +56,7 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout.TabColorizer;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout;
@ -121,6 +122,18 @@ public class ViewKeyActivity extends ActionBarActivity implements
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout);
mSlidingTabLayout.setCustomTabColorizer(new TabColorizer() {
@Override
public int getIndicatorColor(int position) {
return position == TAB_CERTS || position == TAB_KEYS ? 0xFFFF4444 : 0xFFAA66CC;
}
@Override
public int getDividerColor(int position) {
return 0;
}
});
int switchToTab = TAB_MAIN;
Intent intent = getIntent();
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
@ -158,7 +171,7 @@ public class ViewKeyActivity extends ActionBarActivity implements
Bundle shareBundle = new Bundle();
shareBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyShareFragment.class,
mainBundle, getString(R.string.key_view_tab_share));
shareBundle, getString(R.string.key_view_tab_share));
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);

View File

@ -133,7 +133,7 @@ public class ImportKeysListLoader
// read all available blocks... (asc files can contain many blocks with BEGIN END)
while (bufferedInput.available() > 0) {
// todo deal with non-keyring objects?
// TODO: deal with non-keyring objects?
List<UncachedKeyRing> rings = UncachedKeyRing.fromStream(bufferedInput);
for(UncachedKeyRing key : rings) {
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), key);

View File

@ -346,13 +346,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
}
private void createKey() {
// Send all information needed to service to edit key in other thread
final Intent intent = new Intent(mActivity, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_GENERATE_KEY);
// fill values for this action
Bundle data = new Bundle();
Boolean isMasterKey;
String passphrase;
@ -365,6 +360,7 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
passphrase = "";
isMasterKey = true;
}
/*
data.putBoolean(KeychainIntentService.GENERATE_KEY_MASTER_KEY, isMasterKey);
data.putString(KeychainIntentService.GENERATE_KEY_SYMMETRIC_PASSPHRASE, passphrase);
data.putInt(KeychainIntentService.GENERATE_KEY_ALGORITHM, mNewKeyAlgorithmChoice.getId());
@ -410,6 +406,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor
// start service with intent
mActivity.startService(intent);
*/
}
private void addGeneratedKeyToView(UncachedSecretKey newKey) {

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.util;
import android.app.Activity;
import android.content.res.Resources;
import com.github.johnpersano.supertoasts.SuperCardToast;
import com.github.johnpersano.supertoasts.SuperToast;
/**
* @author danielhass
* Notify wrapper which allows a more easy use of different notification libraries
*/
public class Notify {
public static enum Style {OK, WARN, ERROR}
/**
* Shows a simple in-layout notification with the CharSequence given as parameter
* @param activity
* @param text Text to show
* @param style Notification styling
*/
public static void showNotify(Activity activity, CharSequence text, Style style) {
SuperCardToast st = new SuperCardToast(activity);
st.setText(text);
st.setDuration(SuperToast.Duration.MEDIUM);
switch (style){
case OK:
st.setBackground(SuperToast.Background.GREEN);
break;
case WARN:
st.setBackground(SuperToast.Background.ORANGE);
break;
case ERROR:
st.setBackground(SuperToast.Background.RED);
break;
}
st.show();
}
/**
* Shows a simple in-layout notification with the resource text from given id
* @param activity
* @param resId ResourceId of notification text
* @param style Notification styling
* @throws Resources.NotFoundException
*/
public static void showNotify(Activity activity, int resId, Style style) throws Resources.NotFoundException {
showNotify(activity, activity.getResources().getText(resId), style);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

View File

@ -6,6 +6,8 @@
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/notification_area"/>
<android.support.v4.view.ViewPager
android:id="@+id/decrypt_pager"
android:layout_width="match_parent"

View File

@ -1,25 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content_frame"
android:layout_marginLeft="@dimen/drawer_content_padding"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/import_navigation_fragment"
<LinearLayout
android:id="@+id/card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="vertical" />
<org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout
android:id="@+id/import_sliding_tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v4.view.ViewPager
android:id="@+id/import_pager"
android:layout_width="match_parent"
android:layout_height="64dp"
android:background="@android:color/white" />
<View
android:layout_width="match_parent"
android:layout_height="2dip"
android:background="?android:attr/listDivider" />
<View
android:layout_width="match_parent"
android:layout_height="16dp" />
<View
android:layout_width="match_parent"
android:layout_height="2dip"
android:background="?android:attr/listDivider" />
<FrameLayout
android:id="@+id/import_keys_list_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
android:layout_weight="1"
android:background="@android:color/white" />
<LinearLayout
android:id="@+id/import_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
android:paddingRight="16dp"
android:background="@android:color/white">
<View
android:layout_width="match_parent"
@ -43,16 +76,4 @@
style="@style/SelectableItem" />
</LinearLayout>
<FrameLayout
android:id="@+id/import_keys_list_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/import_footer"
android:layout_alignParentLeft="true"
android:layout_below="@+id/import_navigation_fragment"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</RelativeLayout>
</LinearLayout>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="horizontal" >
<com.beardedhen.androidbootstrap.BootstrapButton
android:id="@+id/import_clipboard_button"
android:layout_width="match_parent"
android:layout_height="70dp"
android:text="@string/import_clipboard_button"
bootstrapbutton:bb_icon_left="fa-clipboard"
bootstrapbutton:bb_size="default"
bootstrapbutton:bb_type="default" />
</LinearLayout>

View File

@ -1,21 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<com.beardedhen.androidbootstrap.BootstrapButton
<LinearLayout
android:id="@+id/import_keys_file_browse"
android:paddingLeft="8dp"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal">
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/filemanager_title_open"
android:contentDescription="@string/filemanager_title_open"
bootstrapbutton:bb_icon_left="fa-folder-open"
bootstrapbutton:bb_size="default"
bootstrapbutton:bb_type="default" />
android:layout_weight="1"
android:drawableRight="@drawable/ic_action_collection"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/import_clipboard_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:src="@drawable/ic_action_paste"
android:layout_gravity="center_vertical"
style="@style/SelectableItem" />
</LinearLayout>
<TextView
android:id="@+id/import_qrcode_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:visibility="gone" />
<ProgressBar
android:id="@+id/import_qrcode_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:progress="0"
android:visibility="gone" />
</LinearLayout>

View File

@ -1,16 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="horizontal">
<EditText
android:id="@+id/import_keybase_query"
android:layout_marginLeft="8dp"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
@ -22,17 +18,24 @@
android:lines="1"
android:maxLines="1"
android:minLines="1"
android:layout_marginRight="8dp"
android:layout_gravity="center_vertical" />
<com.beardedhen.androidbootstrap.BootstrapButton
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/import_keybase_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:padding="16dp"
android:src="@drawable/ic_action_search"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
bootstrapbutton:bb_icon_left="fa-search"
bootstrapbutton:bb_roundedCorners="true"
bootstrapbutton:bb_size="default"
bootstrapbutton:bb_type="default" />
style="@style/SelectableItem" />
</LinearLayout>

View File

@ -17,9 +17,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingRight="?android:attr/scrollbarSize"
android:singleLine="true">
android:singleLine="true"
android:paddingLeft="8dp"
android:paddingRight="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<CheckBox
android:id="@+id/selected"

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="horizontal">
<com.beardedhen.androidbootstrap.BootstrapButton
android:id="@+id/import_nfc_button"
android:layout_width="wrap_content"
android:layout_height="70dp"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="8dp"
android:text="@string/import_nfc_help_button"
bootstrapbutton:bb_icon_left="fa-question"
bootstrapbutton:bb_size="default"
bootstrapbutton:bb_type="default" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toLeftOf="@+id/import_nfc_button"
android:text="@string/import_nfc_text" />
</RelativeLayout>

View File

@ -1,21 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<com.beardedhen.androidbootstrap.BootstrapButton
<LinearLayout
android:id="@+id/import_qrcode_button"
android:paddingLeft="8dp"
android:layout_width="match_parent"
android:layout_height="70dp"
android:text="@string/import_qr_scan_button"
bootstrapbutton:bb_icon_left="fa-barcode"
bootstrapbutton:bb_size="default"
bootstrapbutton:bb_type="default" />
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal">
<TextView
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="0dip"
android:layout_height="match_parent"
android:text="@string/import_qr_code_button"
android:layout_weight="1"
android:drawableRight="@drawable/ic_action_qr_code"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<Button
android:id="@+id/import_nfc_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:text="NFC?"
android:layout_gravity="center_vertical"
style="@style/SelectableItem" />
</LinearLayout>
<TextView
android:id="@+id/import_qrcode_text"

View File

@ -1,20 +1,13 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:layout_height="wrap_content"
android:orientation="vertical">
<Spinner
android:id="@+id/import_server_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:layout_height="?android:attr/listPreferredItemHeight"
android:orientation="horizontal">
<EditText
@ -30,18 +23,63 @@
android:lines="1"
android:maxLines="1"
android:minLines="1"
android:layout_marginRight="8dp"
android:layout_gravity="center_vertical" />
<com.beardedhen.androidbootstrap.BootstrapButton
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/import_server_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:padding="16dp"
android:src="@drawable/ic_action_search"
android:layout_gravity="center_vertical"
style="@style/SelectableItem" />
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<ImageButton
android:id="@+id/import_server_config_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:src="@drawable/ic_action_settings"
android:layout_gravity="center_vertical"
style="@style/SelectableItem" />
</LinearLayout>
<LinearLayout
android:id="@+id/import_server_config"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<Spinner
android:id="@+id/import_server_spinner"
android:layout_marginLeft="8dp"
bootstrapbutton:bb_icon_left="fa-search"
bootstrapbutton:bb_roundedCorners="true"
bootstrapbutton:bb_size="default"
bootstrapbutton:bb_type="default" />
android:layout_marginRight="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/list"
android:name="org.sufficientlysecure.keychain.ui.LogDisplayFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.9"
android:layout_marginRight="8dp"
android:layout_marginLeft="8dp" />
<LinearLayout
android:id="@+id/import_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/import_import"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_marginBottom="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Close"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:clickable="true"
style="@style/SelectableItem" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/log_text" />
</LinearLayout>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/log_img"
android:minWidth="10dp"
android:background="@color/bg_gray" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Log Entry Text"
android:id="@+id/log_text"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp" />
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="@+id/card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/emphasis"
android:orientation="vertical" />
</merge>

View File

@ -24,8 +24,7 @@
<LinearLayout
android:id="@+id/view_key_action_fingerprint_share"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal">
@ -63,7 +62,6 @@
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
@ -90,8 +88,7 @@
<LinearLayout
android:id="@+id/view_key_action_key_share"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal">
@ -135,8 +132,7 @@
<LinearLayout
android:id="@+id/view_key_action_nfc_help"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
style="@style/SelectableItem"
android:orientation="horizontal"

View File

@ -206,23 +206,23 @@
<string name="ask_empty_id_ok">Es wurde eine leere Identität hinzugefügt. Wirklich fortfahren?</string>
<string name="public_key_deletetion_confirmation">Soll der öffentliche Schlüssel \'%s\' wirklich gelöscht werden?\nDies kann nicht rückgängig gemacht werden! </string>
<string name="also_export_secret_keys">Private Schlüssel auch exportieren</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">%d Schlüssel erfolgreich hinzugefügt</item>
<item quantity="other">%d Schlüssel erfolgreich hinzugefügt</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">und %d Schlüssel erfolgreich aktualisiert.</item>
<item quantity="other">und %d Schlüssel erfolgreich aktualisiert.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">%d Schlüssel erfolgreich hinzugefügt.</item>
<item quantity="other">%d Schlüssel erfolgreich hinzugefügt.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">%d Schlüssel erfolgreich aktualisiert.</item>
<item quantity="other">%d Schlüssel erfolgreich aktualisiert.</item>
</plurals>
<string name="no_keys_added_or_updated">Keine Schlüssel hinzugefügt oder aktualisiert.</string>
<string name="import_error_nothing">Keine Schlüssel hinzugefügt oder aktualisiert.</string>
<string name="key_exported">1 Schlüssel erfolgreich exportiert.</string>
<string name="keys_exported">%d Schlüssel erfolgreich exportiert.</string>
<string name="no_keys_exported">Keine Schlüssel exportiert.</string>
@ -324,10 +324,8 @@
<string name="progress_verifying_integrity">Integrität wird überprüft…</string>
<string name="progress_deleting_securely">\'%s\' wird sicher gelöscht…</string>
<!--action strings-->
<string name="hint_public_keys">Öffentliche Schlüssel suchen</string>
<string name="hint_secret_keys">Private Schlüssel suchen</string>
<string name="action_share_key_with">Teile Schlüssel über…</string>
<string name="hint_keybase_search">Durchsuche Keybase.io</string>
<!--key bit length selections-->
<string name="key_size_512">512</string>
<string name="key_size_768">768</string>

View File

@ -206,23 +206,23 @@
<string name="ask_empty_id_ok">Ha añadido una identidad vacía, ¿está seguro de que quiere continuar?</string>
<string name="public_key_deletetion_confirmation">¿De veras quiere borrar la clave pública \'%s\'?\n¡No puede deshacer esto!</string>
<string name="also_export_secret_keys">¿Exportar también las claves secretas?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">%d clave añadida satisfactoriamente</item>
<item quantity="other">%d claves añadidas satisfactoriamente</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">y actualizada %d clave.</item>
<item quantity="other">y actualizadas %d claves.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">%d clave añadida satisfactoriamente.</item>
<item quantity="other">%d claves añadidas satisfactoriamente.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">%d clave actualizada satisfactoriamente.</item>
<item quantity="other">%d claves actualizadas satisfactoriamente.</item>
</plurals>
<string name="no_keys_added_or_updated">No se han añadido o actualizado claves.</string>
<string name="import_error_nothing">No se han añadido o actualizado claves.</string>
<string name="key_exported">Se ha exportado 1 clave satisfactoriamente.</string>
<string name="keys_exported">%d claves exportadas satisfactoriamente.</string>
<string name="no_keys_exported">No se han exportado claves.</string>

View File

@ -206,23 +206,23 @@
<string name="ask_empty_id_ok">Vous avez ajouté une identité vide, êtes-vous certain de vouloir continuer ?</string>
<string name="public_key_deletetion_confirmation">Voulez-vous vraiment supprimer la clef publique %s ?\nCeci est irréversible !</string>
<string name="also_export_secret_keys">Exporter aussi les clefs secrètes ?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">%d clef ajoutée avec succès</item>
<item quantity="other">%d clefs ajoutées avec succès</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">et %d clef mise à jour.</item>
<item quantity="other">et %d clefs mises à jour.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">%d clef ajoutée avec succès.</item>
<item quantity="other">%d clefs ajoutées avec succès.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">%d clef mise à jour avec succès.</item>
<item quantity="other">%d clefs mises à jour avec succès.</item>
</plurals>
<string name="no_keys_added_or_updated">Aucune clef ajoutée ou mise à jour.</string>
<string name="import_error_nothing">Aucune clef ajoutée ou mise à jour.</string>
<string name="key_exported">1 clef exportée avec succès.</string>
<string name="keys_exported">%d clefs exportées avec succès.</string>
<string name="no_keys_exported">Aucune clef exportée.</string>

View File

@ -206,23 +206,23 @@
<string name="ask_empty_id_ok">Hai aggiunto una identità vuota, sei sicuro di voler continuare?</string>
<string name="public_key_deletetion_confirmation">Vuoi veramente eliminare la chiave pubblica \'%s\'?\nNon potrai annullare!</string>
<string name="also_export_secret_keys">Esportare anche le chiavi segrete?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">%d chiave aggiunta correttamente</item>
<item quantity="other">%d chiavi aggiunte correttamente</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">e %d chiave aggiornata.</item>
<item quantity="other">e %d chiavi aggiornate.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">%d chiave aggiunta correttamente.</item>
<item quantity="other">%d chiavi aggiunte correttamente.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">%d chiave aggiornata correttamente.</item>
<item quantity="other">%d chiavi aggiornate correttamente.</item>
</plurals>
<string name="no_keys_added_or_updated">Nessuna chiave aggiunta o aggiornata.</string>
<string name="import_error_nothing">Nessuna chiave aggiunta o aggiornata.</string>
<string name="key_exported">1 chiave esportata correttamente.</string>
<string name="keys_exported">%d chiavi esportate correttamente.</string>
<string name="no_keys_exported">Nessuna chiave esportata.</string>

View File

@ -203,19 +203,19 @@
<string name="ask_empty_id_ok">あなたは空のユーザIDを追加しました、このまま続けますか?</string>
<string name="public_key_deletetion_confirmation">公開鍵\'%s\'を本当に削除してもよいですか?\nこれは元に戻せません!</string>
<string name="also_export_secret_keys">秘密鍵もエクスポートしますか?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="other">%d の鍵を追加しました</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="other">そして %d の鍵をアップロードしました。</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="other">%d の鍵を追加しました。</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="other">%d の鍵をアップロードしました。</item>
</plurals>
<string name="no_keys_added_or_updated">鍵の追加もしくは更新はありませんでした。</string>
<string name="import_error_nothing">鍵の追加もしくは更新はありませんでした。</string>
<string name="key_exported">1つの鍵をエクスポートしました。</string>
<string name="keys_exported">%d の鍵をエクスポートしました。</string>
<string name="no_keys_exported">鍵をエクスポートしていません。</string>

View File

@ -206,23 +206,23 @@
<string name="ask_empty_id_ok">U heeft een lege identiteit toegevoegd, weet u zeker dat u wilt doorgaan?</string>
<string name="public_key_deletetion_confirmation">Wilt u echt de publieke sleutel \'%s\' verwijderen?\nDit kunt u niet ongedaan maken!</string>
<string name="also_export_secret_keys">Ook geheime sleutels exporteren?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">Succesvol %d sleutel toegevoegd</item>
<item quantity="other">Succesvol %d sleutels toegevoegd</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">en %d sleutel bijgewerkt.</item>
<item quantity="other">en %d sleutels bijgewerkt.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">Succesvol %d sleutel toegevoegd.</item>
<item quantity="other">Succesvol %d sleutels toegevoegd.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">Succesvol %d sleutel bijgewerkt.</item>
<item quantity="other">Succesvol %d sleutels bijgewerkt.</item>
</plurals>
<string name="no_keys_added_or_updated">Geen sleutels toegevoegd of bijgewerkt.</string>
<string name="import_error_nothing">Geen sleutels toegevoegd of bijgewerkt.</string>
<string name="key_exported">1 sleutel succesvol geëxporteerd.</string>
<string name="keys_exported">Succesvol %d sleutels geëxporteerd.</string>
<string name="no_keys_exported">Geen sleutels geëxporteerd.</string>

View File

@ -191,27 +191,27 @@
<string name="ask_save_changed_key">Zostały dokonane zmiany w pęku kluczy, czy chcesz je zachować?</string>
<string name="public_key_deletetion_confirmation">Czy na pewno chcesz usunąć klucz publiczny \'%s\'?\nNie można cofnąć tej operacji!</string>
<string name="also_export_secret_keys">Czy wyeksportować również klucze prywatne?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">Pomyślnie dodano %d klucz</item>
<item quantity="few">Pomyślnie dodano %d kluczy</item>
<item quantity="other">Pomyślnie dodano %d kluczy</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">i zaktualizowano %d klucz.</item>
<item quantity="few">i zaktualizowano %d kluczy.</item>
<item quantity="other">i zaktualizowano %d kluczy.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">Pomyślnie dodano %d klucz.</item>
<item quantity="few">Pomyślnie dodano %d kluczy.</item>
<item quantity="other">Pomyślnie dodano %d kluczy.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">Pomyślnie zaktualizowano %d klucz.</item>
<item quantity="few">Pomyślnie zaktualizowano %d kluczy.</item>
<item quantity="other">Pomyślnie zaktualizowano %d kluczy.</item>
</plurals>
<string name="no_keys_added_or_updated">Nie dodano ani zaktualizowano żadnych kluczy.</string>
<string name="import_error_nothing">Nie dodano ani zaktualizowano żadnych kluczy.</string>
<string name="key_exported">Pomyślnie wyeksportowano 1 klucz.</string>
<string name="keys_exported">Pomyślnie wyeksportowano %d kluczy.</string>
<string name="no_keys_exported">Nie wyeksportowano żadnych kluczy.</string>

View File

@ -206,27 +206,27 @@
<string name="ask_empty_id_ok">Вы добавили пустой идентификатор. Вы уверены, что хотите продолжить?</string>
<string name="public_key_deletetion_confirmation">Вы правда хотите удалить публичный ключ \'%s\'?\nЭто действие нельзя отменить!</string>
<string name="also_export_secret_keys">Экспортировать секретные ключи?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">Успешно добавлено %d ключ</item>
<item quantity="few">Успешно добавлено %d ключей</item>
<item quantity="other">Успешно добавлено %d ключей</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">и обновлен %d ключ.</item>
<item quantity="few">и обновлено %d ключей.</item>
<item quantity="other">и обновлено %d ключей.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">Добавлен %d ключ</item>
<item quantity="few">Добавлено %d ключей</item>
<item quantity="other">Добавлено %d ключей</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">Обновлен %d ключ.</item>
<item quantity="few">Обновлено %d ключей.</item>
<item quantity="other">Обновлено %d ключей.</item>
</plurals>
<string name="no_keys_added_or_updated">Нет обновленных или добавленных ключей</string>
<string name="import_error_nothing">Нет обновленных или добавленных ключей</string>
<string name="key_exported">Успешный экспорт 1 ключа.</string>
<string name="keys_exported">Экспортировано %d ключей.</string>
<string name="no_keys_exported">Ключи не были экспортированы.</string>

View File

@ -212,31 +212,31 @@
<string name="ask_empty_id_ok">Dodali ste prazno identiteto, ali res želite nadaljevati?</string>
<string name="public_key_deletetion_confirmation">Ali res želite izbrisati javni ključ \'%s\'?\nTega koraka ne boste mogli preklicati!</string>
<string name="also_export_secret_keys">Želite izvoziti tudi zasebne ključe?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">Uspešno dodan %d ključ</item>
<item quantity="two">Uspešno dodana %d ključa</item>
<item quantity="few">Uspešno dodani %d ključi</item>
<item quantity="other">Uspešno dodanih %d ključev</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">in posodbljen %d.</item>
<item quantity="two">in posodobljena %d.</item>
<item quantity="few">in posodobljeni %d.</item>
<item quantity="other">in posodobljenih %d.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">Uspešno dodan %d ključ.</item>
<item quantity="two">Uspešno dodana %d ključa.</item>
<item quantity="few">Uspešno dodani %d ključi.</item>
<item quantity="other">Uspešno dodanih %d ključev.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">Uspešno posodobljen %d ključ.</item>
<item quantity="two">Uspešno posodobljena %d ključa.</item>
<item quantity="few">Uspešno posodobljeni %d ključi.</item>
<item quantity="other">Uspešno posodobljenih %d ključev.</item>
</plurals>
<string name="no_keys_added_or_updated">Noben ključ ni bil dodan ali posodobljen.</string>
<string name="import_error_nothing">Noben ključ ni bil dodan ali posodobljen.</string>
<string name="key_exported">Uspešno izvožen 1 ključ.</string>
<string name="keys_exported">Uspešno izvoženih ključev: %d</string>
<string name="no_keys_exported">Noben ključ ni bil izvožen.</string>

View File

@ -209,27 +209,27 @@
<string name="ask_empty_id_ok">Ви вже додали порожню сутність. Ви справді хочете продовжити?</string>
<string name="public_key_deletetion_confirmation">Ви справді хочете вилучити відкритий ключ \'%s\'?\nВи не зможете це відмінити!</string>
<string name="also_export_secret_keys">Також експортувати секретні ключі?</string>
<plurals name="keys_added_and_updated_1">
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">Успішно додано %d ключ</item>
<item quantity="few">Успішно додано %d ключі</item>
<item quantity="other">Успішно додано %d ключів</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<plurals name="import_keys_added_and_updated_2">
<item quantity="one">і оновлено %d ключ.</item>
<item quantity="few">і оновлено %d ключі.</item>
<item quantity="other">і оновлено %d ключів.</item>
</plurals>
<plurals name="keys_added">
<plurals name="import_keys_added">
<item quantity="one">Успішно додано %d ключ.</item>
<item quantity="few">Успішно додано %d ключі.</item>
<item quantity="other">Успішно додано %d ключів.</item>
</plurals>
<plurals name="keys_updated">
<plurals name="import_keys_updated">
<item quantity="one">Успішно оновлено %d ключ.</item>
<item quantity="few">Успішно оновлено %d ключі.</item>
<item quantity="other">Успішно оновлено %d ключів.</item>
</plurals>
<string name="no_keys_added_or_updated">Жодного ключа не додано та не оновлено.</string>
<string name="import_error_nothing">Жодного ключа не додано та не оновлено.</string>
<string name="key_exported">Успішно експортовано 1 ключ.</string>
<string name="keys_exported">Успішно експортовано %d ключів.</string>
<string name="no_keys_exported">Жодного ключа не експортовано.</string>

View File

@ -28,6 +28,7 @@
<string name="title_certify_key">Certify Identities</string>
<string name="title_key_details">Key Details</string>
<string name="title_help">Help</string>
<string name="title_log_display">Log</string>
<!-- section -->
<string name="section_user_ids">Identities</string>
@ -74,16 +75,11 @@
<!-- menu -->
<string name="menu_preferences">Settings</string>
<string name="menu_help">Help</string>
<string name="menu_import_from_file">Import from file</string>
<string name="menu_import_from_qr_code">Import from QR Code</string>
<string name="menu_import_from_nfc">Import from NFC</string>
<string name="menu_export_key">Export to file</string>
<string name="menu_delete_key">Delete key</string>
<string name="menu_create_key">Create key</string>
<string name="menu_create_key_expert">Create key (expert)</string>
<string name="menu_search">Search</string>
<string name="menu_import_from_key_server">Keyserver</string>
<string name="menu_import_from_keybase">Import from Keybase.io</string>
<string name="menu_key_server">Keyserver…</string>
<string name="menu_update_key">Update from keyserver</string>
<string name="menu_export_key_to_server">Upload to key server</string>
@ -221,24 +217,6 @@
<string name="public_key_deletetion_confirmation">Do you really want to delete the public key \'%s\'?\nYou can\'t undo this!</string>
<string name="also_export_secret_keys">Also export secret keys?</string>
<plurals name="keys_added_and_updated_1">
<item quantity="one">Successfully added %d key</item>
<item quantity="other">Successfully added %d keys</item>
</plurals>
<plurals name="keys_added_and_updated_2">
<item quantity="one"> and updated %d key.</item>
<item quantity="other"> and updated %d keys.</item>
</plurals>
<plurals name="keys_added">
<item quantity="one">Successfully added %d key.</item>
<item quantity="other">Successfully added %d keys.</item>
</plurals>
<plurals name="keys_updated">
<item quantity="one">Successfully updated %d key.</item>
<item quantity="other">Successfully updated %d keys.</item>
</plurals>
<string name="no_keys_added_or_updated">No keys added or updated.</string>
<string name="key_exported">Successfully exported 1 key.</string>
<string name="keys_exported">Successfully exported %d keys.</string>
<string name="no_keys_exported">No keys exported.</string>
@ -246,11 +224,6 @@
<string name="key_creation_weak_rsa_info">Note: generating RSA key with length 1024-bit and less is considered unsafe and it\'s disabled for generating new keys.</string>
<string name="key_not_found">Couldn\'t find key %08X.</string>
<plurals name="keys_found">
<item quantity="one">Found %d key.</item>
<item quantity="other">Found %d keys.</item>
</plurals>
<plurals name="bad_keys_encountered">
<item quantity="one">%d bad secret key ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</item>
<item quantity="other">%d bad secret keys ignored. Perhaps you exported with the option\n --export-secret-subkeys\nMake sure you export with\n --export-secret-keys\ninstead.</item>
@ -295,11 +268,11 @@
<string name="error_only_files_are_supported">Direct binary data without actual file in filesystem is not supported.</string>
<string name="error_jelly_bean_needed">You need Android 4.1 to use Android\'s NFC Beam feature!</string>
<string name="error_nfc_needed">NFC is not available on your device!</string>
<string name="error_nothing_import">Nothing to import!</string>
<string name="error_nothing_import">No keys found!</string>
<string name="error_keyserver_insufficient_query">Key search query too short</string>
<string name="error_searching_keys">Unrecoverable error searching for keys at server</string>
<string name="error_keyserver_too_many_responses">Key search query returned too many candidates; Please refine query</string>
<string name="error_import_file_no_content">File has no content</string>
<string name="error_import_file_no_content">File/Clipboard is empty</string>
<string name="error_generic_report_bug">A generic error occurred, please create a new bug report for OpenKeychain.</string>
<plurals name="error_import_non_pgp_part">
<item quantity="one">part of the loaded file is a valid OpenPGP object but not a OpenPGP key</item>
@ -356,10 +329,10 @@
<string name="progress_deleting_securely">deleting \'%s\' securely…</string>
<!-- action strings -->
<string name="hint_public_keys">Search Public Keys</string>
<string name="hint_public_keys">Name/Email/Key ID…</string>
<string name="hint_secret_keys">Search Secret Keys</string>
<string name="action_share_key_with">Share Key with…</string>
<string name="hint_keybase_search">Search Keybase.io</string>
<string name="hint_keybase_search">Name/Keybase.io username…</string>
<!-- key bit length selections -->
<string name="key_size_512">512</string>
@ -389,6 +362,10 @@
<string name="help_about_version">Version:</string>
<!-- Import -->
<string name="import_tab_keyserver">Keyserver</string>
<string name="import_tab_direct">File/Clipboard</string>
<string name="import_tab_qr_code">QR Code/NFC</string>
<string name="import_tab_keybase">Keybase.io</string>
<string name="import_import">Import selected keys</string>
<string name="import_from_clipboard">Import from clipboard</string>
@ -404,9 +381,32 @@
<string name="import_qr_scan_button">Scan QR Code with \'Barcode Scanner\'</string>
<string name="import_nfc_text">To receive keys via NFC, the device needs to be unlocked.</string>
<string name="import_nfc_help_button">Help</string>
<string name="import_qr_code_button">Scan QR Code…</string>
<string name="import_clipboard_button">Get key from clipboard</string>
<string name="import_keybase_button">Get key from Keybase.io</string>
<!-- Import result toast -->
<plurals name="import_keys_added_and_updated_1">
<item quantity="one">Successfully added %1$d key</item>
<item quantity="other">Successfully added %1$d keys</item>
</plurals>
<plurals name="import_keys_added_and_updated_2">
<item quantity="one"> and updated %1$d key%2$s.</item>
<item quantity="other"> and updated %1$d keys%2$s.</item>
</plurals>
<plurals name="import_keys_added">
<item quantity="one">Successfully added %1$d key%2$s.</item>
<item quantity="other">Successfully added %1$d keys%2$s.</item>
</plurals>
<plurals name="import_keys_updated">
<item quantity="one">Successfully updated %1$d key%2$s.</item>
<item quantity="other">Successfully updated %1$d keys%2$s.</item>
</plurals>
<string name="import_view_log">View Log</string>
<string name="import_error_nothing">Nothing to import.</string>
<string name="import_error">Error importing keys!</string>
<string name="import_with_warnings">, with warnings</string>
<!-- Intent labels -->
<string name="intent_decrypt_file">Decrypt File with OpenKeychain</string>
<string name="intent_import_key">Import Key with OpenKeychain</string>
@ -500,6 +500,133 @@
<string name="cert_verify_error">error!</string>
<string name="cert_verify_unavailable">key unavailable</string>
<!-- Import Public log entries -->
<string name="msg_ip_apply_batch">Applying insert batch operation.</string>
<string name="msg_ip_bad_type_secret">Tried to import secret keyring as public. This is a bug, please file a report!</string>
<string name="msg_ip_delete_old_fail">No old key deleted (creating a new one?)</string>
<string name="msg_ip_delete_old_ok">Deleted old key from database</string>
<string name="msg_ip_encode_fail">Operation failed due to encoding error</string>
<string name="msg_ip_fail_io_exc">Operation failed due to i/o error</string>
<string name="msg_ip_fail_op_exc">Operation failed due to database error</string>
<string name="msg_ip_fail_remote_ex">Operation failed due to internal error</string>
<string name="msg_ip">Importing public keyring %s</string>
<string name="msg_ip_insert_keyring">Encoding keyring data</string>
<string name="msg_ip_insert_keys">Parsing keys</string>
<string name="msg_ip_prepare">Preparing database operations</string>
<string name="msg_ip_master">Processing master key %s</string>
<string name="msg_ip_master_expired">Keyring expired on %s</string>
<string name="msg_ip_master_expires">Keyring expires on %s</string>
<string name="msg_ip_master_flags_ces">Master key flags: certify, encrypt, sign</string>
<string name="msg_ip_master_flags_cex">Master key flags: certify, encrypt</string>
<string name="msg_ip_master_flags_cxs">Master key flags: certify, sign</string>
<string name="msg_ip_master_flags_xes">Master key flags: encrypt, sign</string>
<string name="msg_ip_master_flags_cxx">Master key flags: certify</string>
<string name="msg_ip_master_flags_xex">Master key flags: encrypt</string>
<string name="msg_ip_master_flags_xxs">Master key flags: sign</string>
<string name="msg_ip_master_flags_xxx">Master key flags: none</string>
<string name="msg_ip_subkey">Processing subkey %s</string>
<string name="msg_ip_subkey_expired">Subkey expired on %s</string>
<string name="msg_ip_subkey_expires">Subkey expires on %s</string>
<string name="msg_ip_subkey_flags_ces">Subkey flags: certify, encrypt, sign</string>
<string name="msg_ip_subkey_flags_cex">Subkey flags: certify, encrypt</string>
<string name="msg_ip_subkey_flags_cxs">Subkey flags: certify, sign</string>
<string name="msg_ip_subkey_flags_xes">Subkey flags: encrypt, sign</string>
<string name="msg_ip_subkey_flags_cxx">Subkey flags: certify</string>
<string name="msg_ip_subkey_flags_xex">Subkey flags: encrypt</string>
<string name="msg_ip_subkey_flags_xxs">Subkey flags: sign</string>
<string name="msg_ip_subkey_flags_xxx">Subkey flags: none</string>
<string name="msg_ip_success">Successfully imported public keyring</string>
<string name="msg_ip_success_identical">Keyring contains no new data, nothing to do</string>
<string name="msg_ip_reinsert_secret">Re-inserting secret key</string>
<string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string>
<string name="msg_ip_uid_cert_error">Error processing certificate!</string>
<string name="msg_ip_uid_cert_good">User id is certified by %1$s (%2$s)</string>
<string name="msg_ip_uid_certs_unknown">Ignoring %s certificates from unknown pubkeys</string>
<string name="msg_ip_uid_classifying">Classifying user ids, using %s trusted signatures</string>
<string name="msg_ip_uid_reorder">Re-ordering user ids</string>
<string name="msg_ip_uid_processing">Processing user id %s</string>
<string name="msg_ip_uid_revoked">User id is revoked</string>
<string name="msg_is_bad_type_public">Tried to import public keyring as secret. This is a bug, please file a report!</string>
<!-- Import Secret log entries -->
<string name="msg_is">Importing secret key %s</string>
<string name="msg_is_db_exception">Database error!</string>
<string name="msg_is_importing_subkeys">Processing secret subkeys</string>
<string name="msg_is_io_exc">Error encoding keyring</string>
<string name="msg_is_pubring_generate">Generating public keyring from secret keyring</string>
<string name="msg_is_subkey_nonexistent">Subkey %s unavailable in public key</string>
<string name="msg_is_subkey_ok">Marked %s as available</string>
<string name="msg_is_subkey_stripped">Marked %s as stripped</string>
<string name="msg_is_success_identical">Keyring contains no new data, nothing to do</string>
<string name="msg_is_success">Successfully imported secret keyring</string>
<!-- Keyring Canonicalization log entries -->
<string name="msg_kc_public">Canonicalizing public keyring %s</string>
<string name="msg_kc_secret">Canonicalizing secret keyring %s</string>
<string name="msg_kc_fatal_no_uid">Keyring canonicalization failed: Keyring has no valid user ids</string>
<string name="msg_kc_master">Processing master key</string>
<string name="msg_kc_revoke_bad_err">Removing bad keyring revocation certificate</string>
<string name="msg_kc_revoke_bad_local">Removing keyring revocation certificate with "local" flag</string>
<string name="msg_kc_revoke_bad_time">Removing keyring revocation certificate with future timestamp</string>
<string name="msg_kc_revoke_bad_type">Removing master key certificate of unknown type (%s)</string>
<string name="msg_kc_revoke_bad">Removing bad keyring revocation certificate</string>
<string name="msg_kc_revoke_dup">Removing redundant keyring revocation certificate</string>
<string name="msg_kc_sub">Processing subkey %s</string>
<string name="msg_kc_sub_bad">Removing invalid subkey binding certificate</string>
<string name="msg_kc_sub_bad_err">Removing bad subkey binding certificate</string>
<string name="msg_kc_sub_bad_local">Removing subkey binding certificate with "local" flag</string>
<string name="msg_kc_sub_bad_keyid">Subkey binding issuer id mismatch</string>
<string name="msg_kc_sub_bad_time">Removing subkey binding certificate with future timestamp</string>
<string name="msg_kc_sub_bad_type">Unknown subkey certificate type: %s</string>
<string name="msg_kc_sub_primary_bad">Removing subkey binding certificate due to invalid primary binding certificate</string>
<string name="msg_kc_sub_primary_bad_err">Removing subkey binding certificate due to bad primary binding certificate</string>
<string name="msg_kc_sub_primary_none">Removing subkey binding certificate due to missing primary binding certificate</string>
<string name="msg_kc_sub_no_cert">No valid certificate found for %s, removing from ring</string>
<string name="msg_kc_sub_revoke_bad_err">Removing bad subkey revocation key</string>
<string name="msg_kc_sub_revoke_bad">Removing bad subkey revocation key</string>
<string name="msg_kc_sub_revoke_dup">Removing redundant keyring revocation key</string>
<string name="msg_kc_success">Keyring canonicalization successful, no changes</string>
<string name="msg_kc_success_bad">Keyring canonicalization successful, removed %s erroneous certificates</string>
<string name="msg_kc_success_bad_and_red">Keyring canonicalization successful, removed %1$s erroneous and %2$s redundant certificates</string>
<string name="msg_kc_success_redundant">Keyring canonicalization successful, removed %s redundant certificates</string>
<string name="msg_kc_uid_bad_err">Removing bad self certificate for user id %s</string>
<string name="msg_kc_uid_bad_local">Removing user id certificate with "local" flag</string>
<string name="msg_kc_uid_bad_time">Removing user id with future timestamp</string>
<string name="msg_kc_uid_bad_type">Removing user id certificate of unknown type (%s)</string>
<string name="msg_kc_uid_bad">Removing bad self certificate for user id "%s"</string>
<string name="msg_kc_uid_dup">Removing outdated self certificate for user id "%s"</string>
<string name="msg_kc_uid_foreign">Removing foreign user id certificate by %s</string>
<string name="msg_kc_uid_revoke_dup">Removing redundant revocation certificate for user id "%s"</string>
<string name="msg_kc_uid_revoke_old">Removing outdated revocation certificate for user id "%s"</string>
<string name="msg_kc_uid_no_cert">No valid self-certificate found for user id %s, removing from ring</string>
<!-- Keyring merging log entries -->
<string name="msg_mg_public">Merging into public keyring %s</string>
<string name="msg_mg_secret">Merging into secret keyring %s</string>
<string name="msg_mg_fatal_encode">Fatal error encoding signature</string>
<string name="msg_mg_heterogeneous">Tried to consolidate heterogeneous keyrings</string>
<string name="msg_mg_new_subkey">Adding new subkey %s</string>
<string name="msg_mg_found_new">Found %s new certificates in keyring</string>
<!-- modifySecretKeyRing -->
<string name="msg_mr">Modifying keyring %s</string>
<string name="msg_mf_error_encode">Encoding exception!</string>
<string name="msg_mf_error_pgp">PGP internal exception!</string>
<string name="msg_mf_error_sig">Signature exception!</string>
<string name="msg_mf_passphrase">Changing passphrase</string>
<string name="msg_mf_subkey_change">Modifying subkey %s</string>
<string name="msg_mf_subkey_missing">Tried to operate on missing subkey %s!</string>
<string name="msg_mf_subkey_new">Generating new %1$s bit %2$s subkey</string>
<string name="msg_mf_subkey_new_id">New subkey id: %s</string>
<string name="msg_mf_subkey_past_expiry">Expiry date cannot be in the past!</string>
<string name="msg_mf_subkey_revoke">Revoking subkey %s</string>
<string name="msg_mf_success">Keyring successfully modified</string>
<string name="msg_mf_uid_add">Adding user id %s</string>
<string name="msg_mf_uid_primary">Changing primary uid to %s</string>
<string name="msg_mf_uid_revoke">Revoking user id %s</string>
<string name="msg_mf_unlock_error">Error unlocking keyring!</string>
<string name="msg_mf_unlock">Unlocking keyring</string>
<!-- unsorted -->
<string name="section_certifier_id">Certifier</string>
<string name="section_cert">Certificate Details</string>
@ -523,43 +650,4 @@
<string name="info_no_manual_account_creation">Do not create OpenKeychain-Accounts manually.\nFor more information, see Help.</string>
<string name="contact_show_key">Show key (%s)</string>
<!-- Import log entries -->
<string name="msg_ip_apply_batch">Applying insert batch operation.</string>
<string name="msg_ip_bad_type_secret">Tried to import secret keyring as public. This is a bug, please file a report!</string>
<string name="msg_ip_delete_old_fail">No old key deleted (creating a new one?)</string>
<string name="msg_ip_delete_old_ok">Deleted old key from database</string>
<string name="msg_ip_encode_fail">Operation failed due to encoding error</string>
<string name="msg_ip_fail_io_exc">Operation failed due to i/o error</string>
<string name="msg_ip_fail_op_ex">Operation failed due to database error</string>
<string name="msg_ip_fail_remote_ex">Operation failed due to internal error</string>
<string name="msg_ip_importing">Importing public keyring</string>
<string name="msg_ip_insert_keyring">Inserting keyring data</string>
<string name="msg_ip_insert_subkey">Inserting subkey %s</string>
<string name="msg_ip_insert_subkeys">Inserting subkeys</string>
<string name="msg_ip_preserving_secret">Preserving available secret key</string>
<string name="msg_ip_reinsert_secret">Re-inserting secret key</string>
<string name="msg_ip_success">Successfully inserted public keyring</string>
<string name="msg_ip_trust_retrieve">Retrieving trusted keys</string>
<string name="msg_ip_trust_using">Using %s trusted keys</string>
<string name="msg_ip_trust_using_sec">Secret key available, self certificates are trusted</string>
<string name="msg_ip_uid_cert_bad">Encountered bad certificate!</string>
<string name="msg_ip_uid_cert_error">Error processing certificate!</string>
<string name="msg_ip_uid_cert_good">Found good certificate from %s</string>
<string name="msg_ip_uid_certs_unknown">Ignored %s certificates from unknown pubkeys</string>
<string name="msg_ip_uid_classifying">Classifying user ids</string>
<string name="msg_ip_uid_insert">Inserting user ids</string>
<string name="msg_ip_uid_processing">Processing user id %s</string>
<string name="msg_ip_uid_self_bad">Bad self certificate encountered!</string>
<string name="msg_ip_uid_self_good">Found good self certificate</string>
<string name="msg_ip_uid_self_ignoring_old">Ignoring older self certificate</string>
<string name="msg_ip_uid_self_newer">Using more recent good self certificate</string>
<string name="msg_is_bad_type_public">Tried to import public keyring as secret. This is a bug, please file a report!</string>
<string name="msg_is_importing">Importing secret key %s</string>
<string name="msg_is_importing_subkeys">Processing secret subkeys</string>
<string name="msg_is_io_excption">Error encoding keyring</string>
<string name="msg_is_subkey_nonexistent">Subkey %s unavailable in public key</string>
<string name="msg_is_subkey_ok">Marked %s as available</string>
<string name="msg_is_subkey_stripped">Marked %s as stripped</string>
<string name="msg_is_success">Successfully inserted secret keyring</string>
</resources>

View File

@ -33,7 +33,7 @@ Development mailinglist at http://groups.google.com/d/forum/openpgp-keychain-dev
1. Get all external submodules with ``git submodule update --init --recursive``
2. Have Android SDK "tools", "platform-tools", and "build-tools" directories in your PATH (http://developer.android.com/sdk/index.html)
3. Open the Android SDK Manager (shell command: ``android``).
Expand the Tools directory and select "Android SDK Build-tools (Version 19.0.3)".
Expand the Tools directory and select "Android SDK Build-tools (Version 19.1)".
Expand the Extras directory and install "Android Support Repository"
Select everything for the newest SDK Platform (API-Level 19)
4. Export ANDROID_HOME pointing to your Android SDK
@ -42,8 +42,8 @@ Select everything for the newest SDK Platform (API-Level 19)
### Build API Demo with Gradle
1. Follow 1-3 from above
2. Change to API Demo directory ``cd OpenKeychain-API``
1. Follow 1-4 from above
2. The example code is available at https://github.com/open-keychain/api-example
3. Execute ``./gradlew build``
### Development with Android Studio
@ -227,8 +227,11 @@ Some parts and some libraries are Apache License v2, MIT X11 License (see below)
* icon.svg
modified version of kgpg_key2_kopete.svgz
* Menu icons
* Actionbar icons
http://developer.android.com/design/downloads/index.html#action-bar-icon-pack
* QR Code Actionbar icon
https://github.com/openintents/openintents/blob/master/extensions/qrcode_ext/icons/ic_menu_qr_code/ic_menu_qr_code_holo_light/ic_menu_qr_code.svg
* Purple color scheme
http://android-holo-colors.com/

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,753 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="48px"
height="48px"
viewBox="0 0 48 48"
enable-background="new 0 0 48 48"
xml:space="preserve"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="ic_menu_qr_code.svg"><metadata
id="metadata231"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs229">
</defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1179"
id="namedview227"
showgrid="false"
inkscape:zoom="8.4558186"
inkscape:cx="-17.080652"
inkscape:cy="14.446251"
inkscape:window-x="0"
inkscape:window-y="19"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect25"
height="5.0662165"
width="1.6889851"
y="9.6451044"
x="9.6451044" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect39"
height="5.0662165"
width="1.6889851"
y="9.6451044"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect55"
height="5.0662165"
width="1.6882463"
y="9.6451044"
x="13.023813" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect33"
height="5.0662165"
width="1.6889851"
y="33.287201"
x="9.6451044" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect49"
height="5.0662165"
width="1.6889851"
y="33.287201"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect61"
height="5.0662165"
width="1.6882463"
y="33.287201"
x="13.023813" /><rect
y="6.2678733"
x="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect5"
height="11.821418"
width="1.6882463" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect13"
height="1.6882463"
width="1.6889851"
x="7.9561195" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect15"
height="1.6889851"
width="1.6889851"
y="16.400307"
x="7.9561195" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect23"
height="1.6882463"
width="1.6889851"
x="9.6451044" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect27"
height="1.6889851"
width="1.6889851"
y="16.400307"
x="9.6451044" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect37"
height="1.6882463"
width="1.6889851"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect41"
height="1.6889851"
width="1.6889851"
y="16.400307"
x="11.334089" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect53"
height="1.6882463"
width="1.6882463"
x="13.023813" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect57"
height="1.6889851"
width="1.6882463"
y="16.400307"
x="13.023813" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect65"
height="1.6882463"
width="1.6882463"
x="14.712059" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect67"
height="1.6889851"
width="1.6882463"
y="16.400307"
x="14.712059" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect75"
height="11.821418"
width="1.6889851"
x="16.400307" /><rect
x="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect11"
height="11.821418"
width="1.6882463"
y="29.910709" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect19"
height="1.6882463"
width="1.6889851"
y="29.910709"
x="7.9561195" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect21"
height="1.6889851"
width="1.6889851"
y="40.04314"
x="7.9561195" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect31"
height="1.6882463"
width="1.6889851"
y="29.910709"
x="9.6451044" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect35"
height="1.6889851"
width="1.6889851"
y="40.04314"
x="9.6451044" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect47"
height="1.6882463"
width="1.6889851"
y="29.910709"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect51"
height="1.6889851"
width="1.6889851"
y="40.04314"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect59"
height="1.6882463"
width="1.6882463"
y="29.910709"
x="13.023813" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect63"
height="1.6889851"
width="1.6882463"
y="40.04314"
x="13.023813" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect71"
height="1.6882463"
width="1.6882463"
y="29.910709"
x="14.712059" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect73"
height="1.6889851"
width="1.6882463"
y="40.04314"
x="14.712059" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect83"
height="11.821418"
width="1.6889851"
y="29.910709"
x="16.400307" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect163"
height="5.0662165"
width="1.6897238"
y="9.6451044"
x="33.287201" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect177"
height="5.0662165"
width="1.6882463"
y="9.6451044"
x="34.976925" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect191"
height="5.0662165"
width="1.6897238"
y="9.6451044"
x="36.665913" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect141"
height="11.821418"
width="1.6882463"
x="29.910709" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect151"
height="1.6882463"
width="1.6882463"
x="31.598955" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect153"
height="1.6889851"
width="1.6882463"
y="16.400307"
x="31.598955" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect161"
height="1.6882463"
width="1.6897238"
x="33.287201" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect165"
height="1.6889851"
width="1.6897238"
y="16.400307"
x="33.287201" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect175"
height="1.6882463"
width="1.6882463"
x="34.976925" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect179"
height="1.6889851"
width="1.6882463"
y="16.400307"
x="34.976925" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect189"
height="1.6882463"
width="1.6897238"
x="36.665913" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect193"
height="1.6889851"
width="1.6897238"
y="16.400307"
x="36.665913" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect203"
height="1.6882463"
width="1.6875074"
x="38.355633" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect205"
height="1.6889851"
width="1.6875074"
y="16.400307"
x="38.355633" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect217"
height="11.821418"
width="1.6889851"
x="40.04314" /><rect
x="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect7"
height="3.3779702"
width="1.6882463"
y="19.777536" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect17"
height="1.6897238"
width="1.6889851"
y="26.53274"
x="7.9561195" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect29"
height="5.0654774"
width="1.6889851"
y="21.466522"
x="9.6451044" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect43"
height="1.6889851"
width="1.6889851"
y="21.466522"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect45"
height="3.3779702"
width="1.6889851"
y="24.844492"
x="11.334089" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect69"
height="1.6889851"
width="1.6882463"
y="23.155508"
x="14.712059" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect77"
height="1.6889851"
width="1.6889851"
y="19.777536"
x="16.400307" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect79"
height="1.6889851"
width="1.6889851"
y="23.155508"
x="16.400307" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect81"
height="1.6897238"
width="1.6889851"
y="26.53274"
x="16.400307" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect85"
height="3.3779702"
width="1.6889851"
y="21.466522"
x="18.089291" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect87"
height="5.0662165"
width="1.6889851"
y="7.9561195"
x="19.778276" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect89"
height="13.510403"
width="1.6889851"
y="16.400307"
x="19.778276" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect91"
height="1.6889851"
width="1.6889851"
y="40.04314"
x="19.778276" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect93"
height="1.6882463"
width="1.6882463"
x="21.467262" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect95"
height="1.6889851"
width="1.6882463"
y="13.023074"
x="21.467262" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect97"
height="5.0662165"
width="1.6882463"
y="24.844492"
x="21.467262" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect99"
height="1.6882463"
width="1.6882463"
y="31.598955"
x="21.467262" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect101"
height="6.7552013"
width="1.6882463"
y="34.976925"
x="21.467262" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect103"
height="1.6882463"
width="1.6889851"
y="11.334089"
x="23.155508" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect107"
height="5.0662165"
width="1.6889851"
y="19.777536"
x="23.155508" /><rect
y="6.2678733"
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect113"
height="1.6882463"
width="1.6882463"
x="24.844492" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect117"
height="1.6882463"
width="1.6882463"
y="18.089291"
x="24.844492" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect119"
height="1.6889851"
width="1.6882463"
y="23.155508"
x="24.844492" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect121"
height="1.6882463"
width="1.6882463"
y="28.222464"
x="24.844492" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect123"
height="1.6882463"
width="1.6882463"
y="31.598955"
x="24.844492" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect125"
height="1.6889851"
width="1.6882463"
y="40.04314"
x="24.844492" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect127"
height="3.3779702"
width="1.6889851"
y="7.9561195"
x="26.53274" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect129"
height="10.133172"
width="1.6889851"
y="13.023074"
x="26.53274" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect131"
height="8.4434481"
width="1.6889851"
y="28.222464"
x="26.53274" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect135"
height="3.3779702"
width="1.6889851"
y="26.53274"
x="28.221725" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect137"
height="1.6882463"
width="1.6889851"
y="31.598955"
x="28.221725" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect139"
height="1.6889851"
width="1.6889851"
y="40.04314"
x="28.221725" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect145"
height="1.6889851"
width="1.6882463"
y="23.155508"
x="29.910709" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect149"
height="1.6897238"
width="1.6882463"
y="36.665913"
x="29.910709" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect155"
height="5.0654774"
width="1.6882463"
y="21.466522"
x="31.598955" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect157"
height="3.3779702"
width="1.6882463"
y="31.598955"
x="31.598955" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect169"
height="1.6897238"
width="1.6897238"
y="26.53274"
x="33.287201" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect171"
height="3.3764925"
width="1.6897238"
y="29.910709"
x="33.287201" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect173"
height="6.7552013"
width="1.6897238"
y="34.976925"
x="33.287201" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect181"
height="1.6889851"
width="1.6882463"
y="19.777536"
x="34.976925" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect183"
height="3.3764925"
width="1.6882463"
y="23.155508"
x="34.976925" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect185"
height="1.6882463"
width="1.6882463"
y="31.598955"
x="34.976925" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect187"
height="1.6875074"
width="1.6882463"
y="38.355633"
x="34.976925" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect195"
height="6.7552013"
width="1.6897238"
y="19.777536"
x="36.665913" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect197"
height="1.6882463"
width="1.6897238"
y="28.222464"
x="36.665913" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect199"
height="1.6882463"
width="1.6897238"
y="31.598955"
x="36.665913" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect201"
height="5.0647388"
width="1.6897238"
y="34.976925"
x="36.665913" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect207"
height="1.6889851"
width="1.6875074"
y="19.777536"
x="38.355633" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect209"
height="5.0662165"
width="1.6875074"
y="23.155508"
x="38.355633" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect211"
height="1.6882463"
width="1.6875074"
y="29.910709"
x="38.355633" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect215"
height="1.6889851"
width="1.6875074"
y="40.04314"
x="38.355633" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect219"
height="1.6882463"
width="1.6889851"
y="24.844492"
x="40.04314" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect223"
height="3.3779702"
width="1.6889851"
y="31.598955"
x="40.04314" /><rect
style="opacity:0.6;fill:#333333;fill-opacity:1"
id="rect225"
height="1.6897238"
width="1.6889851"
y="36.665913"
x="40.04314" />
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,6 +1,6 @@
#!/bin/bash
APP_DIR=../../OpenPGP-Keychain/src/main
APP_DIR=../../OpenKeychain/src/main
LDPI_DIR=$APP_DIR/res/drawable-ldpi
MDPI_DIR=$APP_DIR/res/drawable-mdpi
HDPI_DIR=$APP_DIR/res/drawable-hdpi

View File

@ -5,8 +5,8 @@ buildscript {
dependencies {
// NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information
classpath 'com.android.tools.build:gradle:0.10.0'
classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0'
classpath 'com.android.tools.build:gradle:0.11.1'
//classpath 'org.robolectric.gradle:gradle-android-test-plugin:0.10.0'
}
}
@ -17,5 +17,5 @@ allprojects {
}
task wrapper(type: Wrapper) {
gradleVersion = '1.10'
gradleVersion = '1.12'
}

@ -1 +1 @@
Subproject commit bfa160c4ef3a1a53aebd68aca9c05b8e546219e0
Subproject commit 02f02391e3eee9331e07d7690d3b533a8b0f69e2

2
extern/AppMsg vendored

@ -1 +1 @@
Subproject commit ca714df97bfce67a7a9a1efefd2c49645b6f22e4
Subproject commit 61e74741909a712db2e0d31ddffa5b7cf37c21f2

@ -1 +1 @@
Subproject commit efe46c21143cc54a2394303a67822f14580d1d20
Subproject commit 706c0e447229226b6edc82ab10630d39fd0f6c38

1
extern/SuperToasts vendored Submodule

@ -0,0 +1 @@
Subproject commit 8578cfe6917cf16a9f123c1964e4bbff2a15be59

@ -1 +1 @@
Subproject commit c31ef2aff4282ad00af98e879e3e0a6000885b55
Subproject commit eedaa334e761273efbfc49ded2124df58c8a4d88

@ -1 +1 @@
Subproject commit 26497acb27e9f6349c0557b15cd24a5b0b735e74
Subproject commit 175a3cb772c88c9b50985abc98f81c9ea69c3659

@ -1 +1 @@
Subproject commit 650e1ebda82596cd4fbfaae406e6eccf189f4f63
Subproject commit a77887d32fae68171fcd0d2989bf537c0c11f0b9

2
extern/spongycastle vendored

@ -1 +1 @@
Subproject commit 2c47e5fca2a820a4fd584066871bed993f1c3919
Subproject commit 09d85b7d7a64b3003210d065c4210ff7fb7a8c6d

@ -1 +1 @@
Subproject commit 34029d4dcac81ec06137a046a189dac608e76efe
Subproject commit 1d787845663fd232f98f5e8e0923733c1a188f2a

@ -1 +1 @@
Subproject commit 50193905fd8cef92140ea242f77b04bb31391c9e
Subproject commit 9ef2f3b66ea7cc283e865ec39434d023a18d17f3

View File

@ -1,6 +1,6 @@
#Thu Mar 06 22:23:44 CET 2014
#Mon Jun 09 22:04:23 CEST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip

View File

@ -11,4 +11,5 @@ include ':extern:spongycastle:pg'
include ':extern:spongycastle:pkix'
include ':extern:spongycastle:prov'
include ':extern:AppMsg:library'
include ':extern:SuperToasts:supertoasts'
include ':extern:dnsjava'