diff options
86 files changed, 3709 insertions, 2257 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index b3a4d5960..48677431c 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -65,7 +65,7 @@ android:name=".KeychainApplication" android:allowBackup="false" android:hardwareAccelerated="true" - android:icon="@drawable/icon" + android:icon="@drawable/ic_launcher" android:theme="@style/KeychainTheme" android:label="@string/app_name"> <activity diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index c769da421..319ac2873 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -17,6 +17,7 @@ package org.sufficientlysecure.keychain; +import android.os.Build; import android.os.Environment; import org.spongycastle.bcpg.CompressionAlgorithmTags; @@ -48,6 +49,8 @@ public final class Constants { public static final String CUSTOM_CONTACT_DATA_MIME_TYPE = "vnd.android.cursor.item/vnd.org.sufficientlysecure.keychain.key"; + public static boolean KITKAT = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + public static final class Path { public static final String APP_DIR = Environment.getExternalStorageDirectory() + "/OpenKeychain"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java index f50ccf6f8..d8a7e8427 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/ContactHelper.java @@ -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 = + 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 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 final List<String> getMailAccounts(Context context) { + 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); - 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); - if (raw != null) { - if (raw.moveToNext()) { - rawContactId = raw.getInt(0); - } - raw.close(); - } + 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>(); - if (rawContactId == -1) { - 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()); + + // 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)}); } - 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()); + } 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); } - ops.add(ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) - .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) - .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.DATA2, masterKeyId) - .build()); - } - try { - resolver.applyBatch(ContactsContract.AUTHORITY, ops); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (OperationApplicationException e) { - e.printStackTrace(); } } 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, 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(); + } + 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()); + } + + /** + * 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, context.getString(R.string.contact_show_key, keyIdShort)) + .withValue(ContactsContract.Data.DATA2, masterKeyId) + .build()); + } + + /** + * 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()); + } + } + 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}); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java index d7d73cf3d..e0c94b947 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/FileHelper.java @@ -17,12 +17,14 @@ package org.sufficientlysecure.keychain.helper; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Environment; import android.support.v4.app.Fragment; import android.widget.Toast; @@ -82,6 +84,39 @@ public class FileHelper { } } + + /** + * Opens the storage browser on Android 4.4 or later for opening a file + * @param fragment + * @param last default selected file + * @param mimeType can be text/plain for example + * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void openDocument(Fragment fragment, Uri last, String mimeType, int requestCode) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setData(last); + intent.setType(mimeType); + fragment.startActivityForResult(intent, requestCode); + } + + /** + * Opens the storage browser on Android 4.4 or later for saving a file + * @param fragment + * @param last default selected file + * @param mimeType can be text/plain for example + * @param requestCode used to identify the result coming back from storage browser onActivityResult() in your + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static void saveDocument(Fragment fragment, Uri last, String mimeType, int requestCode) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setData(last); + intent.setType(mimeType); + fragment.startActivityForResult(intent, requestCode); + } + private static Intent buildFileIntent(String filename, String mimeType) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java index d64587578..f04d84315 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/helper/OtherHelper.java @@ -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); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java index 43557279f..2f14d77a8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/keyimport/KeybaseKeyserver.java @@ -53,15 +53,16 @@ public class KeybaseKeyserver extends Keyserver { // only list them if they have a key if (JWalk.optObject(match, "components", "key_fingerprint") != null) { - String keybaseId = JWalk.getString(match, "components", "username", "val"); - String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val"); - fingerprint = fingerprint.replace(" ", "").toUpperCase(); - - if (keybaseId.equals(query) || fingerprint.startsWith(query.toUpperCase())) { - results.add(makeEntry(match)); - } else { - results.add(makeEntry(match)); - } + // TODO: needed anymore? +// String keybaseId = JWalk.getString(match, "components", "username", "val"); +// String fingerprint = JWalk.getString(match, "components", "key_fingerprint", "val"); +// fingerprint = fingerprint.replace(" ", "").toUpperCase(); +// if (keybaseId.equals(query) || fingerprint.startsWith(query.toUpperCase())) { +// results.add(makeEntry(match)); +// } else { +// results.add(makeEntry(match)); +// } + results.add(makeEntry(match)); } } } catch (Exception e) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java index 5e49497c0..75f8bdb66 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java @@ -35,7 +35,6 @@ public class OpenPgpSignatureResultBuilder { private boolean mSignatureAvailable = false; private boolean mKnownKey = false; private boolean mValidSignature = false; - private boolean mValidKeyBinding = false; private boolean mIsSignatureKeyCertified = false; public void signatureOnly(boolean signatureOnly) { @@ -58,10 +57,6 @@ public class OpenPgpSignatureResultBuilder { this.mValidSignature = validSignature; } - public void validKeyBinding(boolean validKeyBinding) { - this.mValidKeyBinding = validKeyBinding; - } - public void signatureKeyCertified(boolean isSignatureKeyCertified) { this.mIsSignatureKeyCertified = isSignatureKeyCertified; } @@ -77,7 +72,7 @@ public class OpenPgpSignatureResultBuilder { // valid sig! if (mKnownKey) { - if (mValidKeyBinding && mValidSignature) { + if (mValidSignature) { result.setKeyId(mKeyId); result.setUserId(mUserId); @@ -89,8 +84,7 @@ public class OpenPgpSignatureResultBuilder { result.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED); } } else { - Log.d(Constants.TAG, "Error!\nvalidKeyBinding: " + mValidKeyBinding - + "\nvalidSignature: " + mValidSignature); + Log.d(Constants.TAG, "Error! Invalid signature."); result.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR); } } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index c009d1b5c..a5ccfbd3b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -122,9 +122,6 @@ public class PgpDecryptVerify { /** * Allow these key ids alone for decryption. * This means only ciphertexts encrypted for one of these private key can be decrypted. - * - * @param allowedKeyIds - * @return */ public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) { this.mAllowedKeyIds = allowedKeyIds; @@ -496,10 +493,7 @@ public class PgpDecryptVerify { // Verify signature and check binding signatures boolean validSignature = signature.verify(messageSignature); - boolean validKeyBinding = signingRing.verifySubkeyBinding(signingKey); - signatureResultBuilder.validSignature(validSignature); - signatureResultBuilder.validKeyBinding(validKeyBinding); } } @@ -643,10 +637,8 @@ public class PgpDecryptVerify { // Verify signature and check binding signatures boolean validSignature = signature.verify(); - boolean validKeyBinding = signingRing.verifySubkeyBinding(signingKey); signatureResultBuilder.validSignature(validSignature); - signatureResultBuilder.validKeyBinding(validKeyBinding); } result.setSignatureResult(signatureResultBuilder.build()); @@ -657,10 +649,6 @@ public class PgpDecryptVerify { /** * Mostly taken from ClearSignedFileProcessor in Bouncy Castle - * - * @param sig - * @param line - * @throws SignatureException */ private static void processLine(PGPSignature sig, byte[] line) throws SignatureException { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java index d24f65aa6..969ff1de8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java @@ -60,7 +60,7 @@ public class PgpHelper { } public static String getFullVersion(Context context) { - return "OpenPGP Keychain v" + getVersion(context); + return "OpenKeychain v" + getVersion(context); } // public static final class content { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java index e1967429a..7544f7b86 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpImportExport.java @@ -37,6 +37,7 @@ import org.sufficientlysecure.keychain.service.OperationResultParcel.OperationLo 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; @@ -130,6 +131,7 @@ public class PgpImportExport { 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()); @@ -147,9 +149,11 @@ public class PgpImportExport { SaveKeyringResult result; if (key.isSecret()) { - result = mProviderHelper.saveSecretKeyRing(key); + result = mProviderHelper.saveSecretKeyRing(key, + new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); } else { - result = mProviderHelper.savePublicKeyRing(key); + result = mProviderHelper.savePublicKeyRing(key, + new ProgressScaler(mProgressable, position, (position+1)*progSteps, 100)); } if (!result.success()) { badKeys += 1; @@ -165,7 +169,6 @@ public class PgpImportExport { } // update progress position++; - updateProgress(position / entries.size() * 100, 100); } OperationLog log = mProviderHelper.getLog(); @@ -223,7 +226,7 @@ public class PgpImportExport { try { WrappedPublicKeyRing ring = mProviderHelper.getWrappedPublicKeyRing( - KeychainContract.KeyRings.buildGenericKeyRingUri(pubKeyMasterId) + KeychainContract.KeyRings.buildUnifiedKeyRingUri(pubKeyMasterId) ); ring.encode(arOutStream); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 44fc4c8c9..c590200ee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -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,751 +101,269 @@ 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 { - - if (keySize < 512) { - throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit); - } - - if (passphrase == null) { - passphrase = ""; - } - - int algorithm; - KeyPairGenerator keyGen; - - switch (algorithmChoice) { - case Constants.choice.algorithm.dsa: { - keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); - keyGen.initialize(keySize, new SecureRandom()); - algorithm = PGPPublicKey.DSA; - break; - } - - case Constants.choice.algorithm.elgamal: { - if (isMasterKey) { - throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal); - } - keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); - BigInteger p = Primes.getBestPrime(keySize); - BigInteger g = new BigInteger("2"); - - ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); - - keyGen.initialize(elParams); - algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; - break; - } - - case Constants.choice.algorithm.rsa: { - keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); - keyGen.initialize(keySize, new SecureRandom()); - - algorithm = PGPPublicKey.RSA_GENERAL; - break; - } - - default: { - throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice); - } - } - - // build new key pair - PGPKeyPair keyPair = new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); - - // define hashing and signing algos - PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( - HashAlgorithmTags.SHA1); - - // Build key encrypter and decrypter based on passphrase - PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + /** Creates new secret key. */ + private PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase, + boolean isMasterKey) throws PgpGeneralMsgIdException { try { - return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), - sha1Calc, isMasterKey, keyEncryptor).getEncoded(); - } catch(IOException e) { - throw new PgpGeneralMsgIdException(R.string.error_encoding); - } - } - - 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); + if (keySize < 512) { + throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit); } - 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); + if (passphrase == null) { + passphrase = ""; + } - // Build key encrypter based on passphrase - PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - saveParcel.newPassphrase.toCharArray()); + int algorithm; + KeyPairGenerator keyGen; - PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, - masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(), - unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor); + switch (algorithmChoice) { + case Constants.choice.algorithm.dsa: { + keyGen = KeyPairGenerator.getInstance("DSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); + keyGen.initialize(keySize, new SecureRandom()); + algorithm = PGPPublicKey.DSA; + break; + } - updateProgress(R.string.progress_adding_sub_keys, 40, 100); + case Constants.choice.algorithm.elgamal: { + if (isMasterKey) { + throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal); + } + keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME); + BigInteger p = Primes.getBestPrime(keySize); + BigInteger g = new BigInteger("2"); - for (int i = 1; i < saveParcel.keys.size(); ++i) { - updateProgress(40 + 40 * (i - 1) / (saveParcel.keys.size() - 1), 100); + ElGamalParameterSpec elParams = new ElGamalParameterSpec(p, g); - PGPSecretKey subKey = saveParcel.keys.get(i).getSecretKeyExternal(); - PGPPublicKey subPublicKey = subKey.getPublicKey(); + keyGen.initialize(elParams); + algorithm = PGPPublicKey.ELGAMAL_ENCRYPT; + break; + } - PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - saveParcel.oldPassphrase.toCharArray()); - PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2); + case Constants.choice.algorithm.rsa: { + keyGen = KeyPairGenerator.getInstance("RSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); + keyGen.initialize(keySize, new SecureRandom()); - // TODO: now used without algorithm and creation time?! (APG 1) - PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey); + algorithm = PGPPublicKey.RSA_GENERAL; + break; + } - 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); + default: { + throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice); } - 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()); - } + // build new key pair + PGPKeyPair keyPair = new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date()); - PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing(); - PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing(); + // define hashing and signing algos + PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get( + HashAlgorithmTags.SHA1); - return new Pair(new UncachedKeyRing(secretKeyRing), new UncachedKeyRing(publicKeyRing)); + // Build key encrypter and decrypter based on passphrase + PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder( + PGPEncryptedData.CAST5, sha1Calc) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), + 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> buildSecretKey(WrappedSecretKeyRing wmKR, - WrappedPublicKeyRing wpKR, - OldSaveKeyringParcel saveParcel) - throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException { + /** 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) { - PGPSecretKeyRing mKR = wmKR.getRing(); - PGPPublicKeyRing pKR = wpKR.getRing(); + /* + * 1. Unlock private key + * 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); - if (saveParcel.oldPassphrase == null) { - saveParcel.oldPassphrase = ""; - } - if (saveParcel.newPassphrase == null) { - saveParcel.newPassphrase = ""; - } + // We work on bouncycastle object level here + PGPSecretKeyRing sKR = wsKR.getRing(); + PGPPublicKey masterPublicKey = sKR.getPublicKey(); + PGPSecretKey masterSecretKey = sKR.getSecretKey(); - /* - 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()); + // 1. Unlock private key + log.add(LogLevel.DEBUG, LogType.MSG_MF_UNLOCK, indent); + PGPPrivateKey masterPrivateKey; { + try { + PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + masterPrivateKey = masterSecretKey.extractPrivateKey(keyDecryptor); + } catch (PGPException e) { + log.add(LogLevel.ERROR, LogType.MSG_MF_UNLOCK_ERROR, indent+1); + return null; } } - - 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); + if (!Arrays.equals(saveParcel.mFingerprint, sKR.getPublicKey().getFingerprint())) { + return null; } - 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; - } + updateProgress(R.string.progress_certifying_master_key, 20, 100); - //update the keyring with the new ID information - if (anyIDChanged) { - pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey); - mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR); - } + // work on master secret key + try { - 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 - } + PGPPublicKey modifiedPublicKey = masterPublicKey; - 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? + // 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); } - } - 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()); + // 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); } - } - //replace lost IDs - if (saveParcel.moddedKeys[0]) { - masterPublicKey = mKR.getPublicKey(); - for (Pair<String, PGPSignature> toAdd : sigList) { - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second); + // 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 } - 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); + // Update the secret key ring + if (modifiedPublicKey != masterPublicKey) { + masterSecretKey = PGPSecretKey.replacePublicKey(masterSecretKey, modifiedPublicKey); + masterPublicKey = modifiedPublicKey; + sKR = PGPSecretKeyRing.insertSecretKey(sKR, masterSecretKey); } - } - - */ - - 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: - * - * 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) - * - */ - - // 1. Unlock private key - updateProgress(R.string.progress_building_key, 0, 100); - PGPPublicKey masterPublicKey = sKR.getPublicKey(); - PGPPrivateKey masterPrivateKey; { - PGPSecretKey masterKey = sKR.getSecretKey(); - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor); - } - // 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>(); - - // 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 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 + // 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; } - } - 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); + PGPPublicKey pKey = sKey.getPublicKey(); + + 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); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); } - secretKeys.add(PGPSecretKey.replacePublicKey(sKey, pKey)); - publicKeys.add(pKey); - } - // 4. Generate and add new subkeys - // TODO - - // 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; - - 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; - } - 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); - } - certified = true; + // 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(); + + // generate and add new signature + PGPSignature sig = generateRevocationSignature(masterPublicKey, masterPrivateKey, pKey); + + pKey = PGPPublicKey.addCertification(pKey, sig); + sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey)); } - // - Generate revocation if requested - if (!revoked && Arrays.binarySearch(saveParcel.revokeUserIds, userId) >= 0) { - PGPSignature cert = generateRevocationSignature(masterPrivateKey, - masterPublicKey, userId); - masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, cert); - } - } - // 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); - } + // 5. Generate and add new subkeys + for (SaveKeyringParcel.SubkeyAdd add : saveParcel.addSubKeys) { + try { - // 7. Generate PublicKeyRing from SecretKeyRing - updateProgress(R.string.progress_building_master_key, 30, 100); - PGPSecretKeyRing ring = new PGPSecretKeyRing(secretKeys); + 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; + } - // 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))) { + 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; + } } - } - for (PGPPublicKey newKey : publicKeys) { - PGPPublicKey oldKey = pKR.getPublicKey(newKey.getKeyID()); - for (PGPSignature sig : new IterableIterator<PGPSignature>( - oldKey.getSignatures())) { + // 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( + Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); + // Build key encryptor based on new passphrase + PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( + PGPEncryptedData.CAST5, sha1Calc) + .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( + saveParcel.newPassphrase.toCharArray()); + + sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew); } - } - - // If requested, set new passphrase - if (saveParcel.newPassphrase != null) { - PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build() - .get(HashAlgorithmTags.SHA1); - PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider( - Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray()); - // Build key encryptor based on new passphrase - PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder( - PGPEncryptedData.CAST5, sha1Calc) - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build( - saveParcel.newPassphrase.toCharArray()); - sKR = PGPSecretKeyRing.copyWithNewPassword(sKR, keyDecryptor, keyEncryptorNew); + // 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; } - // 8. Return pair (PublicKeyRing,SecretKeyRing) - - 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; - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index e309ed632..371202217 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -27,10 +27,14 @@ import java.io.ByteArrayInputStream; 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. @@ -50,24 +54,27 @@ 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; } - public long getMasterKeyId() { - return mRing.getPublicKey().getKeyID(); + private UncachedKeyRing(PGPKeyRing ring, boolean canonicalized) { + mRing = ring; + mIsSecret = ring instanceof PGPSecretKeyRing; + mIsCanonicalized = canonicalized; } - /* TODO don't use this */ - @Deprecated - public PGPKeyRing getRing() { - return mRing; + public long getMasterKeyId() { + return mRing.getPublicKey().getKeyID(); } public UncachedPublicKey getPublicKey() { @@ -94,6 +101,10 @@ public class UncachedKeyRing { return mIsSecret; } + public boolean isCanonicalized() { + return mIsCanonicalized; + } + public byte[] getEncoded() throws IOException { return mRing.getEncoded(); } @@ -102,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 = @@ -178,39 +180,41 @@ public class UncachedKeyRing { return result; } - /** "Canonicalizes" a key, removing inconsistencies in the process. This operation can be + /** "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 - * - * After this cleaning, a number of checks are done: TODO implement - * - See if each subkey retains a valid self certificate - * - See if each user id retains a valid self certificate + * - 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 + * @return A canonicalized key, or null on fatal error * */ + @SuppressWarnings("ConstantConditions") public UncachedKeyRing canonicalize(OperationLog log, int indent) { - if (isSecret()) { - throw new RuntimeException("Tried to canonicalize non-secret keyring. " + - "This is a programming error and should never happen!"); - } - log.add(LogLevel.START, LogType.MSG_KC, + 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 removedCerts = 0; + int redundantCerts = 0, badCerts = 0; - PGPPublicKeyRing ring = (PGPPublicKeyRing) mRing; + PGPKeyRing ring = mRing; PGPPublicKey masterKey = mRing.getPublicKey(); final long masterKeyId = masterKey.getKeyID(); @@ -240,7 +244,7 @@ public class UncachedKeyRing { "0x" + Integer.toString(type, 16) }, indent); modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + badCerts += 1; continue; } @@ -248,7 +252,7 @@ public class UncachedKeyRing { // Creation date in the future? No way! log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_TIME, null, indent); modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + badCerts += 1; continue; } @@ -256,7 +260,7 @@ public class UncachedKeyRing { // Creation date in the future? No way! log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + badCerts += 1; continue; } @@ -265,13 +269,13 @@ public class UncachedKeyRing { if (!cert.verifySignature(masterKey)) { log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD, null, indent); modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + badCerts += 1; continue; } } catch (PgpGeneralException e) { log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_ERR, null, indent); modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + badCerts += 1; continue; } @@ -281,12 +285,12 @@ public class UncachedKeyRing { // more revocations? at least one is superfluous, then. } else if (revocation.getCreationTime().before(zert.getCreationTime())) { modified = PGPPublicKey.removeCertification(modified, revocation); - removedCerts += 1; + redundantCerts += 1; log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); revocation = zert; } else { modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + redundantCerts += 1; log.add(LogLevel.INFO, LogType.MSG_KC_REVOKE_DUP, null, indent); } } @@ -312,14 +316,14 @@ public class UncachedKeyRing { "0x" + Integer.toString(zert.getSignatureType(), 16) }, indent); modified = PGPPublicKey.removeCertification(modified, userId, zert); - removedCerts += 1; + 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); - removedCerts += 1; + badCerts += 1; continue; } @@ -327,12 +331,19 @@ public class UncachedKeyRing { // Creation date in the future? No way! log.add(LogLevel.WARN, LogType.MSG_KC_REVOKE_BAD_LOCAL, null, indent); modified = PGPPublicKey.removeCertification(modified, zert); - removedCerts += 1; + badCerts += 1; continue; } - // If this is a foreign signature, never mind any further + // 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; } @@ -343,14 +354,14 @@ public class UncachedKeyRing { log.add(LogLevel.WARN, LogType.MSG_KC_UID_BAD, new String[] { userId }, indent); modified = PGPPublicKey.removeCertification(modified, userId, zert); - removedCerts += 1; + 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); - removedCerts += 1; + badCerts += 1; continue; } @@ -363,14 +374,14 @@ public class UncachedKeyRing { selfCert = zert; } else if (selfCert.getCreationTime().before(cert.getCreationTime())) { modified = PGPPublicKey.removeCertification(modified, userId, selfCert); - removedCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_UID_DUP, + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_DUP, new String[] { userId }, indent); selfCert = zert; } else { modified = PGPPublicKey.removeCertification(modified, userId, zert); - removedCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_UID_DUP, + 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 @@ -378,8 +389,8 @@ public class UncachedKeyRing { && revocation.getCreationTime().before(selfCert.getCreationTime())) { modified = PGPPublicKey.removeCertification(modified, userId, revocation); revocation = null; - removedCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_OLD, + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD, new String[] { userId }, indent); } break; @@ -388,8 +399,8 @@ public class UncachedKeyRing { // 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); - removedCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_OLD, + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_OLD, new String[] { userId }, indent); continue; } @@ -399,14 +410,14 @@ public class UncachedKeyRing { // more revocations? at least one is superfluous, then. } else if (revocation.getCreationTime().before(cert.getCreationTime())) { modified = PGPPublicKey.removeCertification(modified, userId, revocation); - removedCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_DUP, + 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); - removedCerts += 1; - log.add(LogLevel.INFO, LogType.MSG_KC_UID_REVOKE_DUP, + redundantCerts += 1; + log.add(LogLevel.DEBUG, LogType.MSG_KC_UID_REVOKE_DUP, new String[] { userId }, indent); } break; @@ -414,12 +425,23 @@ public class UncachedKeyRing { } } + + // 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); + } } - // Replace modified key in the keyring - ring = PGPPublicKeyRing.insertPublicKey(ring, modified); + // 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; + } - log.add(LogLevel.DEBUG, LogType.MSG_KC_MASTER_SUCCESS, null, indent); + // Replace modified key in the keyring + ring = replacePublicKey(ring, modified); indent -= 1; } @@ -437,18 +459,17 @@ public class UncachedKeyRing { // certificate. PGPPublicKey modified = key; PGPSignature selfCert = null, revocation = null; - uids: for (PGPSignature zig : new IterableIterator<PGPSignature>(key.getSignatures())) { + uids: for (PGPSignature zert : new IterableIterator<PGPSignature>(key.getSignatures())) { // remove from keyring (for now) - modified = PGPPublicKey.removeCertification(modified, zig); - // add this too, easier than adding it for every single "continue" case - removedCerts += 1; + modified = PGPPublicKey.removeCertification(modified, zert); - WrappedSignature cert = new WrappedSignature(zig); + 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; } @@ -456,18 +477,21 @@ public class UncachedKeyRing { 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; } @@ -478,20 +502,22 @@ public class UncachedKeyRing { 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 (zig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.KEY_FLAGS)) { - int flags = ((KeyFlags) zig.getHashedSubPackets() + 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 = zig.getUnhashedSubPackets().getEmbeddedSignatures(); + PGPSignatureList list = zert.getUnhashedSubPackets().getEmbeddedSignatures(); boolean ok = false; for (int i = 0; i < list.size(); i++) { WrappedSignature subsig = new WrappedSignature(list.get(i)); @@ -501,16 +527,19 @@ public class UncachedKeyRing { 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; } } @@ -518,10 +547,12 @@ public class UncachedKeyRing { // if we already have a cert, and this one is not newer: skip it if (selfCert != null && selfCert.getCreationTime().before(cert.getCreationTime())) { + log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_DUP, indent); + redundantCerts += 1; continue; } - selfCert = zig; + selfCert = zert; // if this is newer than a possibly existing revocation, drop that one if (revocation != null && selfCert.getCreationTime().after(revocation.getCreationTime())) { revocation = null; @@ -535,54 +566,236 @@ public class UncachedKeyRing { 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().before(cert.getCreationTime())) { - revocation = zig; + // if there is a certification that is newer than this revocation, don't bother + if (selfCert != null && selfCert.getCreationTime().after(cert.getCreationTime())) { + log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_REVOKE_DUP, indent); + redundantCerts += 1; + continue; } + + revocation = zert; } } // it is not properly bound? error! if (selfCert == null) { - ring = PGPPublicKeyRing.removePublicKey(ring, modified); + ring = replacePublicKey(ring, modified); log.add(LogLevel.ERROR, LogType.MSG_KC_SUB_NO_CERT, - new String[]{PgpKeyHelper.convertKeyIdToHex(key.getKeyID())}, indent); + new String[]{ PgpKeyHelper.convertKeyIdToHex(key.getKeyID()) }, indent); indent -= 1; continue; } // re-add certification modified = PGPPublicKey.addCertification(modified, selfCert); - removedCerts -= 1; // add revocation, if any if (revocation != null) { modified = PGPPublicKey.addCertification(modified, revocation); - removedCerts -= 1; } // replace pubkey in keyring - ring = PGPPublicKeyRing.insertPublicKey(ring, modified); - - log.add(LogLevel.DEBUG, LogType.MSG_KC_SUB_SUCCESS, null, indent); + ring = replacePublicKey(ring, modified); indent -= 1; } - if (removedCerts > 0) { - log.add(LogLevel.OK, LogType.MSG_KC_SUCCESS_REMOVED, - new String[] { Integer.toString(removedCerts) }, indent); + 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); + 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); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java index 71d237c05..6f3068261 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedKeyRing.java @@ -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()); + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java index 0bb84aee7..b2abf15a4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedPublicKeyRing.java @@ -1,24 +1,16 @@ package org.sufficientlysecure.keychain.pgp; import org.spongycastle.bcpg.ArmoredOutputStream; -import org.spongycastle.bcpg.SignatureSubpacketTags; -import org.spongycastle.openpgp.PGPException; import org.spongycastle.openpgp.PGPKeyRing; import org.spongycastle.openpgp.PGPObjectFactory; import org.spongycastle.openpgp.PGPPublicKey; import org.spongycastle.openpgp.PGPPublicKeyRing; -import org.spongycastle.openpgp.PGPSignature; -import org.spongycastle.openpgp.PGPSignatureList; -import org.spongycastle.openpgp.PGPSignatureSubpacketVector; -import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.util.IterableIterator; import org.sufficientlysecure.keychain.util.Log; import java.io.IOException; -import java.security.SignatureException; -import java.util.Arrays; import java.util.Iterator; public class WrappedPublicKeyRing extends WrappedKeyRing { @@ -70,106 +62,11 @@ public class WrappedPublicKeyRing extends WrappedKeyRing { } return cKey; } - // TODO handle with proper exception throw new PgpGeneralException("no encryption key available"); } - public boolean verifySubkeyBinding(WrappedPublicKey cachedSubkey) { - boolean validSubkeyBinding = false; - boolean validTempSubkeyBinding = false; - boolean validPrimaryKeyBinding = false; - - PGPPublicKey masterKey = getRing().getPublicKey(); - PGPPublicKey subKey = cachedSubkey.getPublicKey(); - - // Is this the master key? Match automatically, then. - if(Arrays.equals(masterKey.getFingerprint(), subKey.getFingerprint())) { - return true; - } - - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = - new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - - Iterator<PGPSignature> itr = subKey.getSignatures(); - - while (itr.hasNext()) { //what does gpg do if the subkey binding is wrong? - //gpg has an invalid subkey binding error on key import I think, but doesn't shout - //about keys without subkey signing. Can't get it to import a slightly broken one - //either, so we will err on bad subkey binding here. - PGPSignature sig = itr.next(); - if (sig.getKeyID() == masterKey.getKeyID() && - sig.getSignatureType() == PGPSignature.SUBKEY_BINDING) { - //check and if ok, check primary key binding. - try { - sig.init(contentVerifierBuilderProvider, masterKey); - validTempSubkeyBinding = sig.verifyCertification(masterKey, subKey); - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - - if (validTempSubkeyBinding) { - validSubkeyBinding = true; - } - if (validTempSubkeyBinding) { - validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getUnhashedSubPackets(), - masterKey, subKey); - if (validPrimaryKeyBinding) { - break; - } - validPrimaryKeyBinding = verifyPrimaryKeyBinding(sig.getHashedSubPackets(), - masterKey, subKey); - if (validPrimaryKeyBinding) { - break; - } - } - } - } - return validSubkeyBinding && validPrimaryKeyBinding; - - } - - static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts, - PGPPublicKey masterPublicKey, - PGPPublicKey signingPublicKey) { - boolean validPrimaryKeyBinding = false; - JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider = - new JcaPGPContentVerifierBuilderProvider() - .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); - PGPSignatureList eSigList; - - if (pkts.hasSubpacket(SignatureSubpacketTags.EMBEDDED_SIGNATURE)) { - try { - eSigList = pkts.getEmbeddedSignatures(); - } catch (IOException e) { - return false; - } catch (PGPException e) { - return false; - } - for (int j = 0; j < eSigList.size(); ++j) { - PGPSignature emSig = eSigList.get(j); - if (emSig.getSignatureType() == PGPSignature.PRIMARYKEY_BINDING) { - try { - emSig.init(contentVerifierBuilderProvider, signingPublicKey); - validPrimaryKeyBinding = emSig.verifyCertification(masterPublicKey, signingPublicKey); - if (validPrimaryKeyBinding) { - break; - } - } catch (PGPException e) { - continue; - } catch (SignatureException e) { - continue; - } - } - } - } - - return validPrimaryKeyBinding; - } - public IterableIterator<WrappedPublicKey> publicKeyIterator() { + @SuppressWarnings("unchecked") final Iterator<PGPPublicKey> it = getRing().getPublicKeys(); return new IterableIterator<WrappedPublicKey>(new Iterator<WrappedPublicKey>() { @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java index 9591cf8bc..d7148f710 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSecretKeyRing.java @@ -154,8 +154,4 @@ public class WrappedSecretKeyRing extends WrappedKeyRing { }); } - public UncachedKeyRing getUncached() { - return new UncachedKeyRing(mRing); - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java index be7f960a9..196ac1dee 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/WrappedSignature.java @@ -179,7 +179,8 @@ public class WrappedSignature { } public boolean isLocal() { - if (!mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) { + if (!mSig.hasSubpackets() + || !mSig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.EXPORTABLE)) { return false; } SignatureSubpacket p = mSig.getHashedSubPackets().getSubpacket(SignatureSubpacketTags.EXPORTABLE); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java index 3b88897ed..3700b4c34 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/exception/PgpGeneralMsgIdException.java @@ -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); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java index 102c8e6d0..955fb90ba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java @@ -29,6 +29,7 @@ import android.support.v4.util.LongSparseArray; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.KeyRing; +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; @@ -56,6 +57,7 @@ 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; @@ -67,7 +69,7 @@ import java.util.Set; * name, it is not only a helper but actually the main interface for all * synchronous database operations. * - * Operations in this class write logs (TODO). These can be obtained from the + * 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. @@ -186,9 +188,9 @@ public class ProviderHelper { KeyRings.PUBKEY_DATA }, KeyRings.HAS_ANY_SECRET + " = 1", null, null); - LongSparseArray<WrappedPublicKey> result = - new LongSparseArray<WrappedPublicKey>(cursor.getCount()); try { + LongSparseArray<WrappedPublicKey> result = new LongSparseArray<WrappedPublicKey>(); + if (cursor != null && cursor.moveToFirst()) do { long masterKeyId = cursor.getLong(0); boolean hasAnySecret = cursor.getInt(1) > 0; @@ -199,13 +201,15 @@ public class ProviderHelper { new WrappedPublicKeyRing(blob, hasAnySecret, verified).getSubkey()); } } while (cursor.moveToNext()); + + return result; + } finally { if (cursor != null) { cursor.close(); } } - return result; } public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) { @@ -259,38 +263,30 @@ 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 SaveKeyringResult 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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, 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(); - log(LogLevel.START, LogType.MSG_IP, - new String[]{ PgpKeyHelper.convertKeyIdToHex(masterKeyId) }); - mIndent += 1; - - // Canonicalize this key, to assert a number of assumptions made about it. - keyRing = keyRing.canonicalize(mLog, mIndent); - UncachedPublicKey masterKey = keyRing.getPublicKey(); - // 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; - } - ArrayList<ContentProviderOperation> operations; try { @@ -302,14 +298,13 @@ public class ProviderHelper { log(LogLevel.INFO, LogType.MSG_IP_INSERT_KEYRING); { // insert keyring - // insert new version of this 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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return SaveKeyringResult.RESULT_ERROR; } Uri uri = KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)); @@ -317,13 +312,15 @@ public class ProviderHelper { } 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_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) }); mIndent += 1; @@ -341,21 +338,41 @@ public class ProviderHelper { values.put(Keys.CAN_ENCRYPT, e); values.put(Keys.CAN_SIGN, s); values.put(Keys.IS_REVOKED, key.isRevoked()); - if (c) { - if (e) { - log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CES - : LogType.MSG_IP_SUBKEY_FLAGS_CEX, null); + 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 { - log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_CXS - : LogType.MSG_IP_SUBKEY_FLAGS_CXX, null); + 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 (e) { - log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XES - : LogType.MSG_IP_SUBKEY_FLAGS_XEX, null); + 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 { - log(LogLevel.DEBUG, s ? LogType.MSG_IP_SUBKEY_FLAGS_XXS - : LogType.MSG_IP_SUBKEY_FLAGS_XXX, null); + 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); + } } } @@ -365,13 +382,13 @@ public class ProviderHelper { if (expiryDate != null) { values.put(Keys.EXPIRY, expiryDate.getTime() / 1000); if (key.isExpired()) { - log(LogLevel.DEBUG, LogType.MSG_IP_SUBKEY_EXPIRED, new String[]{ - expiryDate.toString() - }); + log(LogLevel.DEBUG, keyId == masterKeyId ? + LogType.MSG_IP_MASTER_EXPIRED : LogType.MSG_IP_SUBKEY_EXPIRED, + new String[]{ expiryDate.toString() }); } else { - log(LogLevel.DEBUG, LogType.MSG_IP_SUBKEY_EXPIRES, new String[]{ - expiryDate.toString() - }); + log(LogLevel.DEBUG, keyId == masterKeyId ? + LogType.MSG_IP_MASTER_EXPIRES : LogType.MSG_IP_SUBKEY_EXPIRES, + new String[] { expiryDate.toString() }); } } @@ -415,11 +432,11 @@ public class ProviderHelper { if (!cert.isRevocation()) { item.selfCert = cert; item.isPrimary = cert.isPrimaryUserId(); - log(LogLevel.DEBUG, LogType.MSG_IP_UID_SELF_GOOD); } else { item.isRevoked = true; - log(LogLevel.DEBUG, LogType.MSG_IP_UID_REVOKED); + log(LogLevel.INFO, LogType.MSG_IP_UID_REVOKED); } + continue; } @@ -457,6 +474,7 @@ public class ProviderHelper { } mIndent -= 1; + 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. @@ -467,7 +485,7 @@ public class ProviderHelper { operations.add(buildUserIdOperations(masterKeyId, item, userIdRank)); if (item.selfCert != null) { operations.add(buildCertOperations(masterKeyId, userIdRank, item.selfCert, - secretRing != null ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); + selfCertsAreTrusted ? Certs.VERIFIED_SECRET : Certs.VERIFIED_SELF)); } // don't bother with trusted certs if the uid is revoked, anyways if (item.isRevoked) { @@ -479,14 +497,13 @@ public class ProviderHelper { } } - log(LogLevel.DEBUG, LogType.MSG_IP_PREPARE_SUCCESS); 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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return SaveKeyringResult.RESULT_ERROR; } try { @@ -501,31 +518,24 @@ public class ProviderHelper { } log(LogLevel.DEBUG, LogType.MSG_IP_APPLY_BATCH); + progress.setProgress(LogType.MSG_IP_APPLY_BATCH.getMsgId(), 75, 100); mContentResolver.applyBatch(KeychainContract.CONTENT_AUTHORITY, operations); - // Save the saved keyring (if any) - if (secretRing != null) { - log(LogLevel.DEBUG, LogType.MSG_IP_REINSERT_SECRET); - mIndent += 1; - saveSecretKeyRing(secretRing); - result |= SaveKeyringResult.SAVED_SECRET; - mIndent -= 1; - } - - mIndent -= 1; log(LogLevel.OK, LogType.MSG_IP_SUCCESS); - return new SaveKeyringResult(result, mLog); + 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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, 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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + return SaveKeyringResult.RESULT_ERROR; } } @@ -551,17 +561,19 @@ public class ProviderHelper { } } - /** - * Saves a PGPSecretKeyRing in the DB. This will only work if a corresponding public keyring - * is already in the database! - * - * TODO allow adding secret keys where no public key exists (ie, consolidate keys) + /** Saves an UncachedKeyRing of the secret variant into the db. + * This method will fail if no corresponding public keyring is in the database! */ - public SaveKeyringResult saveSecretKeyRing(UncachedKeyRing keyRing) { + private int internalSaveSecretKeyRing(UncachedKeyRing keyRing) { if (!keyRing.isSecret()) { log(LogLevel.ERROR, LogType.MSG_IS_BAD_TYPE_PUBLIC); - return new SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, 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(); @@ -569,6 +581,12 @@ public class ProviderHelper { 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; @@ -579,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 SaveKeyringResult(SaveKeyringResult.RESULT_ERROR, mLog); + log(LogLevel.ERROR, LogType.MSG_IS_FAIL_IO_EXC); + return SaveKeyringResult.RESULT_ERROR; } { @@ -627,22 +648,219 @@ public class ProviderHelper { } log(LogLevel.OK, LogType.MSG_IS_SUCCESS); - return new SaveKeyringResult(result, mLog); + 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); + internalSavePublicKeyRing(pubRing, null, true); + internalSaveSecretKeyRing(secRing); } /** @@ -758,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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java index 8db9294df..6c4d59a77 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/ContactSyncAdapterService.java @@ -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); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 3ddcdfcf4..e1514b16f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -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"; @@ -107,9 +104,11 @@ public class KeychainIntentService extends IntentService // encrypt, decrypt, import export public static final String TARGET = "target"; + public static final String SOURCE = "source"; // possible targets: - public static final int TARGET_BYTES = 1; - public static final int TARGET_URI = 2; + public static final int IO_BYTES = 1; + public static final int IO_FILE = 2; // This was misleadingly TARGET_URI before! + public static final int IO_URI = 3; // encrypt public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id"; @@ -118,7 +117,9 @@ public class KeychainIntentService extends IntentService public static final String ENCRYPT_COMPRESSION_ID = "compression_id"; public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes"; public static final String ENCRYPT_INPUT_FILE = "input_file"; + public static final String ENCRYPT_INPUT_URI = "input_uri"; public static final String ENCRYPT_OUTPUT_FILE = "output_file"; + public static final String ENCRYPT_OUTPUT_URI = "output_uri"; public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase"; // decrypt/verify @@ -127,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"; @@ -164,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"; @@ -227,7 +218,7 @@ public class KeychainIntentService extends IntentService if (ACTION_ENCRYPT_SIGN.equals(action)) { try { /* Input */ - int target = data.getInt(TARGET); + int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID); String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE); @@ -235,71 +226,8 @@ public class KeychainIntentService extends IntentService boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR); long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS); int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID); - InputStream inStream; - long inLength; - InputData inputData; - OutputStream outStream; -// String streamFilename = null; - switch (target) { - case TARGET_BYTES: /* encrypting bytes directly */ - byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES); - - inStream = new ByteArrayInputStream(bytes); - inLength = bytes.length; - - inputData = new InputData(inStream, inLength); - outStream = new ByteArrayOutputStream(); - - break; - case TARGET_URI: /* encrypting file */ - String inputFile = data.getString(ENCRYPT_INPUT_FILE); - String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); - - // check if storage is ready - if (!FileHelper.isStorageMounted(inputFile) - || !FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException( - getString(R.string.error_external_storage_not_ready)); - } - - inStream = new FileInputStream(inputFile); - File file = new File(inputFile); - inLength = file.length(); - inputData = new InputData(inStream, inLength); - - outStream = new FileOutputStream(outputFile); - - break; - - // TODO: not used currently -// case TARGET_STREAM: /* Encrypting stream from content uri */ -// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); -// -// // InputStream -// InputStream in = getContentResolver().openInputStream(providerUri); -// inLength = PgpHelper.getLengthOfStream(in); -// inputData = new InputData(in, inLength); -// -// // OutputStream -// try { -// while (true) { -// streamFilename = PgpHelper.generateRandomFilename(32); -// if (streamFilename == null) { -// throw new PgpGeneralException("couldn't generate random file name"); -// } -// openFileInput(streamFilename).close(); -// } -// } catch (FileNotFoundException e) { -// // found a name that isn't used yet -// } -// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); -// -// break; - - default: - throw new PgpGeneralException("No target choosen!"); - - } + InputData inputData = createEncryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); /* Operation */ PgpSignEncrypt.Builder builder = @@ -324,7 +252,7 @@ public class KeychainIntentService extends IntentService PassphraseCacheService.getCachedPassphrase(this, signatureKeyId)); // this assumes that the bytes are cleartext (valid for current implementation!) - if (target == TARGET_BYTES) { + if (source == IO_BYTES) { builder.setCleartextInput(true); } @@ -335,24 +263,7 @@ public class KeychainIntentService extends IntentService /* Output */ Bundle resultData = new Bundle(); - - switch (target) { - case TARGET_BYTES: - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - - resultData.putByteArray(RESULT_BYTES, output); - - break; - case TARGET_URI: - // nothing, file was written, just send okay - - break; -// case TARGET_STREAM: -// String uri = DataStream.buildDataStreamUri(streamFilename).toString(); -// resultData.putString(RESULT_URI, uri); -// -// break; - } + finalizeEncryptOutputStream(data, resultData, outStream); OtherHelper.logDebugBundle(resultData, "resultData"); @@ -363,78 +274,10 @@ public class KeychainIntentService extends IntentService } else if (ACTION_DECRYPT_VERIFY.equals(action)) { try { /* Input */ - int target = data.getInt(TARGET); - - byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES); String passphrase = data.getString(DECRYPT_PASSPHRASE); - InputStream inStream; - long inLength; - InputData inputData; - OutputStream outStream; - String streamFilename = null; - switch (target) { - case TARGET_BYTES: /* decrypting bytes directly */ - inStream = new ByteArrayInputStream(bytes); - inLength = bytes.length; - - inputData = new InputData(inStream, inLength); - outStream = new ByteArrayOutputStream(); - - break; - - case TARGET_URI: /* decrypting file */ - String inputFile = data.getString(ENCRYPT_INPUT_FILE); - String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); - - // check if storage is ready - if (!FileHelper.isStorageMounted(inputFile) - || !FileHelper.isStorageMounted(outputFile)) { - throw new PgpGeneralException( - getString(R.string.error_external_storage_not_ready)); - } - - // InputStream - inLength = -1; - inStream = new FileInputStream(inputFile); - File file = new File(inputFile); - inLength = file.length(); - inputData = new InputData(inStream, inLength); - - // OutputStream - outStream = new FileOutputStream(outputFile); - - break; - - // TODO: not used, maybe contains code useful for new decrypt method for files? -// case TARGET_STREAM: /* decrypting stream from content uri */ -// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI); -// -// // InputStream -// InputStream in = getContentResolver().openInputStream(providerUri); -// inLength = PgpHelper.getLengthOfStream(in); -// inputData = new InputData(in, inLength); -// -// // OutputStream -// try { -// while (true) { -// streamFilename = PgpHelper.generateRandomFilename(32); -// if (streamFilename == null) { -// throw new PgpGeneralException("couldn't generate random file name"); -// } -// openFileInput(streamFilename).close(); -// } -// } catch (FileNotFoundException e) { -// // found a name that isn't used yet -// } -// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE); -// -// break; - - default: - throw new PgpGeneralException("No target choosen!"); - - } + InputData inputData = createDecryptInputData(data); + OutputStream outStream = createCryptOutputStream(data); /* Operation */ @@ -465,21 +308,7 @@ public class KeychainIntentService extends IntentService /* Output */ - switch (target) { - case TARGET_BYTES: - byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); - resultData.putByteArray(RESULT_DECRYPTED_BYTES, output); - break; - case TARGET_URI: - // nothing, file was written, just send okay and verification bundle - - break; -// case TARGET_STREAM: -// String uri = DataStream.buildDataStreamUri(streamFilename).toString(); -// resultData.putString(RESULT_URI, uri); -// -// break; - } + finalizeDecryptOutputStream(data, resultData, outStream); OtherHelper.logDebugBundle(resultData, "resultData"); @@ -490,133 +319,36 @@ 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)); - try { - WrappedSecretKeyRing seckey = providerHelper.getWrappedSecretKeyRing(masterKeyId); - WrappedPublicKeyRing pubkey = providerHelper.getWrappedPublicKeyRing(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); - } catch (ProviderHelper.NotFoundException e) { - PgpKeyOperation.Pair<UncachedKeyRing,UncachedKeyRing> pair = - keyOperations.buildNewSecretKey(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_done, 100, 100); + PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 10, 50, 100)); + try { + String passphrase = data.getString(SAVE_KEYRING_PASSPHRASE); + WrappedSecretKeyRing secRing = providerHelper.getWrappedSecretKeyRing(masterKeyId); + + 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) { + // UncachedKeyRing ring = keyOperations.(saveParcel); //new Keyring + // save the pair + setProgress(R.string.progress_saving_key_ring, 95, 100); + // providerHelper.saveSecretKeyRing(ring); + sendErrorToHandler(e); } - 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); + setProgress(R.string.progress_done, 100, 100); - 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 + if (saveParcel.newPassphrase != null) { + PassphraseCacheService.addCachedPassphrase(this, masterKeyId, saveParcel.newPassphrase); + } /* 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); + sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY); } catch (Exception e) { sendErrorToHandler(e); } @@ -915,4 +647,95 @@ public class KeychainIntentService extends IntentService public boolean hasServiceStopped() { return mIsCanceled; } + + private InputData createDecryptInputData(Bundle data) throws IOException, PgpGeneralException { + return createCryptInputData(data, DECRYPT_CIPHERTEXT_BYTES); + } + + private InputData createEncryptInputData(Bundle data) throws IOException, PgpGeneralException { + return createCryptInputData(data, ENCRYPT_MESSAGE_BYTES); + } + + private InputData createCryptInputData(Bundle data, String bytesName) throws PgpGeneralException, IOException { + int source = data.get(SOURCE) != null ? data.getInt(SOURCE) : data.getInt(TARGET); + switch (source) { + case IO_BYTES: /* encrypting bytes directly */ + byte[] bytes = data.getByteArray(bytesName); + return new InputData(new ByteArrayInputStream(bytes), bytes.length); + + case IO_FILE: /* encrypting file */ + String inputFile = data.getString(ENCRYPT_INPUT_FILE); + + // check if storage is ready + if (!FileHelper.isStorageMounted(inputFile)) { + throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready)); + } + + return new InputData(new FileInputStream(inputFile), new File(inputFile).length()); + + case IO_URI: /* encrypting content uri */ + Uri providerUri = data.getParcelable(ENCRYPT_INPUT_URI); + + // InputStream + InputStream in = getContentResolver().openInputStream(providerUri); + return new InputData(in, 0); + + default: + throw new PgpGeneralException("No target choosen!"); + } + } + + private OutputStream createCryptOutputStream(Bundle data) throws PgpGeneralException, FileNotFoundException { + int target = data.getInt(TARGET); + switch (target) { + case IO_BYTES: + return new ByteArrayOutputStream(); + + case IO_FILE: + String outputFile = data.getString(ENCRYPT_OUTPUT_FILE); + + // check if storage is ready + if (!FileHelper.isStorageMounted(outputFile)) { + throw new PgpGeneralException( + getString(R.string.error_external_storage_not_ready)); + } + + // OutputStream + return new FileOutputStream(outputFile); + + case IO_URI: + Uri providerUri = data.getParcelable(ENCRYPT_OUTPUT_URI); + + return getContentResolver().openOutputStream(providerUri); + + default: + throw new PgpGeneralException("No target choosen!"); + } + } + + private void finalizeEncryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { + finalizeCryptOutputStream(data, resultData, outStream, RESULT_BYTES); + } + + private void finalizeDecryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream) { + finalizeCryptOutputStream(data, resultData, outStream, RESULT_DECRYPTED_BYTES); + } + + private void finalizeCryptOutputStream(Bundle data, Bundle resultData, OutputStream outStream, String bytesName) { + int target = data.getInt(TARGET); + switch (target) { + case IO_BYTES: + byte output[] = ((ByteArrayOutputStream) outStream).toByteArray(); + resultData.putByteArray(bytesName, output); + break; + case IO_FILE: + // nothing, file was written, just send okay and verification bundle + + break; + case IO_URI: + // nothing, output was written, just send okay and verification bundle + + break; + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OldSaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OldSaveKeyringParcel.java deleted file mode 100644 index b722393ad..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OldSaveKeyringParcel.java +++ /dev/null @@ -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; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java index b5f01ce4d..f88df5301 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/OperationResultParcel.java @@ -3,8 +3,10 @@ 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; @@ -16,6 +18,7 @@ import java.util.ArrayList; * 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 @@ -101,6 +104,23 @@ public class OperationResultParcel implements Parcelable { } + /** 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 @@ -111,18 +131,26 @@ public class OperationResultParcel implements Parcelable { 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_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_subkeys), + MSG_IP_INSERT_SUBKEYS (R.string.msg_ip_insert_keys), MSG_IP_PREPARE (R.string.msg_ip_prepare), - MSG_IP_PREPARE_SUCCESS(R.string.msg_ip_prepare_success), - MSG_IP_PRESERVING_SECRET (R.string.msg_ip_preserving_secret), 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 (R.string.msg_ip_subkey_flags), 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), @@ -132,6 +160,7 @@ public class OperationResultParcel implements Parcelable { 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), @@ -140,22 +169,25 @@ public class OperationResultParcel implements Parcelable { 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), - MSG_IP_UID_SELF_GOOD (R.string.msg_ip_uid_self_good), // 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_IO_EXCPTION (R.string.msg_is_io_excption), + 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 (R.string.msg_kc), + 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_MASTER_SUCCESS (R.string.msg_kc_master_success), 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), @@ -169,6 +201,7 @@ public class OperationResultParcel implements Parcelable { 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_DUP (R.string.msg_kc_sub_dup), 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), @@ -176,8 +209,9 @@ public class OperationResultParcel implements Parcelable { 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_SUB_SUCCESS (R.string.msg_kc_sub_success), - MSG_KC_SUCCESS_REMOVED (R.string.msg_kc_success_removed), + 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), @@ -185,8 +219,38 @@ public class OperationResultParcel implements Parcelable { 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; @@ -233,9 +297,14 @@ public class OperationResultParcel implements Parcelable { /// 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) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 3514ab2e5..c68b7c189 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -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(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java index 7fdab7bdd..28a465436 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFileFragment.java @@ -20,10 +20,13 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.provider.OpenableColumns; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -58,7 +61,9 @@ public class DecryptFileFragment extends DecryptFragment { private View mDecryptButton; private String mInputFilename = null; + private Uri mInputUri = null; private String mOutputFilename = null; + private Uri mOutputUri = null; private FileDialogFragment mFileDialog; @@ -75,8 +80,12 @@ public class DecryptFileFragment extends DecryptFragment { mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + if (Constants.KITKAT) { + FileHelper.openDocument(DecryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + } else { + FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*", + RESULT_CODE_FILE); + } } }); mDecryptButton.setOnClickListener(new View.OnClickListener() { @@ -99,20 +108,24 @@ public class DecryptFileFragment extends DecryptFragment { } } - private void guessOutputFilename() { - mInputFilename = mFilename.getText().toString(); + private String guessOutputFilename() { File file = new File(mInputFilename); String filename = file.getName(); if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) { filename = filename.substring(0, filename.length() - 4); } - mOutputFilename = Constants.Path.APP_DIR + "/" + filename; + return Constants.Path.APP_DIR + "/" + filename; } private void decryptAction() { String currentFilename = mFilename.getText().toString(); if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { - guessOutputFilename(); + mInputUri = null; + mInputFilename = mFilename.getText().toString(); + } + + if (mInputUri == null) { + mOutputFilename = guessOutputFilename(); } if (mInputFilename.equals("")) { @@ -121,7 +134,7 @@ public class DecryptFileFragment extends DecryptFragment { return; } - if (mInputFilename.startsWith("file")) { + if (mInputUri == null && mInputFilename.startsWith("file")) { File file = new File(mInputFilename); if (!file.exists() || !file.isFile()) { AppMsg.makeText( @@ -143,7 +156,11 @@ public class DecryptFileFragment extends DecryptFragment { public void handleMessage(Message message) { if (message.what == FileDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { + mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); + } else { + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + } decryptStart(null); } } @@ -172,13 +189,25 @@ public class DecryptFileFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); + + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" + + mOutputUri); + + if (mInputUri != null) { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + } else { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + } - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + if (mOutputUri != null) { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + } else { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + } data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); @@ -209,8 +238,13 @@ public class DecryptFileFragment extends DecryptFragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); + DeleteFileDialogFragment deleteFileDialog; + if (mInputUri != null) { + deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); + } else { + deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + } deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); } } @@ -234,13 +268,25 @@ public class DecryptFileFragment extends DecryptFragment { switch (requestCode) { case RESULT_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(getActivity(), data.getData()); - Log.d(Constants.TAG, "path=" + path); + if (Constants.KITKAT) { + mInputUri = data.getData(); + Cursor cursor = getActivity().getContentResolver().query(mInputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + mInputFilename = cursor.getString(0); + mFilename.setText(mInputFilename); + } + cursor.close(); + } + } else { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } } } return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java index d1ad7fbc5..46462f924 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptMessageFragment.java @@ -129,7 +129,7 @@ public class DecryptMessageFragment extends DecryptFragment { intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY); // data - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes()); data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java index bcb2286ab..4309e3505 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyActivity.java @@ -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() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java index d150abdeb..2671e0d40 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFileFragment.java @@ -20,11 +20,13 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.app.ProgressDialog; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; +import android.provider.OpenableColumns; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -73,7 +75,9 @@ public class EncryptFileFragment extends Fragment { // model private String mInputFilename = null; + private Uri mInputUri = null; private String mOutputFilename = null; + private Uri mOutputUri = null; @Override public void onAttach(Activity activity) { @@ -104,8 +108,12 @@ public class EncryptFileFragment extends Fragment { mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", - RESULT_CODE_FILE); + if (Constants.KITKAT) { + FileHelper.openDocument(EncryptFileFragment.this, mInputUri, "*/*", RESULT_CODE_FILE); + } else { + FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*", + RESULT_CODE_FILE); + } } }); @@ -178,7 +186,11 @@ public class EncryptFileFragment extends Fragment { public void handleMessage(Message message) { if (message.what == FileDialogFragment.MESSAGE_OKAY) { Bundle data = message.getData(); - mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + if (data.containsKey(FileDialogFragment.MESSAGE_DATA_URI)) { + mOutputUri = data.getParcelable(FileDialogFragment.MESSAGE_DATA_URI); + } else { + mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME); + } encryptStart(); } } @@ -197,17 +209,20 @@ public class EncryptFileFragment extends Fragment { private void encryptClicked() { String currentFilename = mFilename.getText().toString(); if (mInputFilename == null || !mInputFilename.equals(currentFilename)) { + mInputUri = null; mInputFilename = mFilename.getText().toString(); } - mOutputFilename = guessOutputFilename(mInputFilename); + if (mInputUri == null) { + mOutputFilename = guessOutputFilename(mInputFilename); + } if (mInputFilename.equals("")) { AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show(); return; } - if (!mInputFilename.startsWith("content")) { + if (mInputUri == null && !mInputFilename.startsWith("content")) { File file = new File(mInputFilename); if (!file.exists() || !file.isFile()) { AppMsg.makeText( @@ -280,7 +295,25 @@ public class EncryptFileFragment extends Fragment { // fill values for this action Bundle data = new Bundle(); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI); + Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" + + mOutputFilename + ",mInputUri=" + mInputUri + ", mOutputUri=" + + mOutputUri); + + if (mInputUri != null) { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_INPUT_URI, mInputUri); + } else { + data.putInt(KeychainIntentService.SOURCE, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); + } + + if (mOutputUri != null) { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_URI); + data.putParcelable(KeychainIntentService.ENCRYPT_OUTPUT_URI, mOutputUri); + } else { + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_FILE); + data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); + } if (mEncryptInterface.isModeSymmetric()) { Log.d(Constants.TAG, "Symmetric encryption enabled!"); @@ -296,12 +329,6 @@ public class EncryptFileFragment extends Fragment { mEncryptInterface.getEncryptionKeys()); } - Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename=" - + mOutputFilename); - - data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename); - data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename); - boolean useAsciiArmor = mAsciiArmor.isChecked(); data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor); @@ -323,8 +350,13 @@ public class EncryptFileFragment extends Fragment { if (mDeleteAfter.isChecked()) { // Create and show dialog to delete original file - DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment - .newInstance(mInputFilename); + DeleteFileDialogFragment deleteFileDialog; + if (mInputUri != null) { + deleteFileDialog = DeleteFileDialogFragment.newInstance(mInputUri); + } else { + deleteFileDialog = DeleteFileDialogFragment + .newInstance(mInputFilename); + } deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog"); } @@ -332,7 +364,11 @@ public class EncryptFileFragment extends Fragment { // Share encrypted file Intent sendFileIntent = new Intent(Intent.ACTION_SEND); sendFileIntent.setType("*/*"); - sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + if (mOutputUri != null) { + sendFileIntent.putExtra(Intent.EXTRA_STREAM, mOutputUri); + } else { + sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename)); + } startActivity(Intent.createChooser(sendFileIntent, getString(R.string.title_share_file))); } @@ -356,13 +392,25 @@ public class EncryptFileFragment extends Fragment { switch (requestCode) { case RESULT_CODE_FILE: { if (resultCode == Activity.RESULT_OK && data != null) { - try { - String path = FileHelper.getPath(getActivity(), data.getData()); - Log.d(Constants.TAG, "path=" + path); - - mFilename.setText(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + if (Constants.KITKAT) { + mInputUri = data.getData(); + Cursor cursor = getActivity().getContentResolver().query(mInputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + mInputFilename = cursor.getString(0); + mFilename.setText(mInputFilename); + } + cursor.close(); + } + } else { + try { + String path = FileHelper.getPath(getActivity(), data.getData()); + Log.d(Constants.TAG, "path=" + path); + + mFilename.setText(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!"); + } } } return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java index 4c35806e5..8a6103b16 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptMessageFragment.java @@ -177,7 +177,7 @@ public class EncryptMessageFragment extends Fragment { // fill values for this action Bundle data = new Bundle(); - data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES); + data.putInt(KeychainIntentService.TARGET, KeychainIntentService.IO_BYTES); String message = mMessage.getText().toString(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java index f389726ff..e829df7a0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysActivity.java @@ -18,9 +18,7 @@ package org.sufficientlysecure.keychain.ui; import android.annotation.TargetApi; -import android.app.Activity; import android.app.ProgressDialog; -import android.content.Context; import android.content.Intent; import android.net.Uri; import android.nfc.NdefMessage; @@ -31,12 +29,12 @@ import android.os.Message; import android.os.Messenger; import android.os.Parcelable; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.ActionBar; +import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBarActivity; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; -import android.widget.ArrayAdapter; +import android.view.ViewGroup; import com.github.johnpersano.supertoasts.SuperCardToast; import com.github.johnpersano.supertoasts.SuperToast; @@ -45,18 +43,21 @@ 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.ParcelableKeyRing; import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; import org.sufficientlysecure.keychain.service.KeychainIntentService; import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; 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"; @@ -90,23 +91,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) { @@ -114,6 +110,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,21 +123,11 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O mNavigationStrings = getResources().getStringArray(R.array.import_action_list); - 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); - } + // TODO: add actionbar button for this action? +// if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) { +// } handleActions(savedInstanceState, getIntent()); - } protected void handleActions(Bundle savedInstanceState, Intent intent) { @@ -157,6 +146,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O action = ACTION_IMPORT_KEY; } + Bundle serverBundle = null; + boolean serverOnly = false; if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) { /* Scanning a fingerprint directly with Barcode Scanner */ loadFromFingerprintUri(savedInstanceState, dataUri); @@ -164,7 +155,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 @@ -197,9 +188,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O if (query != null && query.length() > 0) { // display keyserver fragment with query - Bundle args = new Bundle(); - args.putString(ImportKeysServerFragment.ARG_QUERY, query); - loadNavFragment(NAV_SERVER, args); + serverBundle = new Bundle(); + serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query); + mSwitchToTab = NAV_SERVER; // action: search immediately startListFragment(savedInstanceState, null, null, query); @@ -214,7 +205,19 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O */ String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT); - loadFromFingerprint(savedInstanceState, fingerprint); + if (isFingerprintValid(fingerprint)) { + String query = "0x" + fingerprint; + + // display keyserver fragment with query + serverBundle = new Bundle(); + serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query); + serverBundle.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true); + serverOnly = true; + mSwitchToTab = NAV_SERVER; + + // action: search immediately + startListFragment(savedInstanceState, null, null, query); + } } else { Log.e(Constants.TAG, "IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " + @@ -223,9 +226,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); @@ -233,26 +235,66 @@ 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); } + + initTabs(serverBundle, serverOnly); + } + + private void initTabs(Bundle serverBundle, boolean serverOnly) { + 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) { + } + }); + + mTabsAdapter.addTab(ImportKeysServerFragment.class, + serverBundle, getString(R.string.import_tab_keyserver)); + if (!serverOnly) { + mTabsAdapter.addTab(ImportKeysQrCodeFragment.class, + null, getString(R.string.import_tab_qr_code)); + mTabsAdapter.addTab(ImportKeysFileFragment.class, + null, getString(R.string.import_tab_direct)); + mTabsAdapter.addTab(ImportKeysKeybaseFragment.class, + null, getString(R.string.import_tab_keybase)); + } + + // update layout after operations + mSlidingTabLayout.setViewPager(mViewPager); + + mViewPager.setCurrentItem(mSwitchToTab); } private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) { @@ -275,54 +317,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; - } - - 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; - } + 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 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) { @@ -330,33 +334,52 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O Log.d(Constants.TAG, "fingerprint: " + fingerprint); - loadFromFingerprint(savedInstanceState, fingerprint); + // TODO: reload fragment when coming from qr code! +// loadFromFingerprint(savedInstanceState, fingerprint); + + +// String query = "0x" + fingerprint; +// +// // display keyserver fragment with query +// Bundle serverBundle = new Bundle(); +// serverBundle.putString(ImportKeysServerFragment.ARG_QUERY, query); +// serverBundle.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true); +// +// return serverBundle; } - public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) { + private boolean isFingerprintValid(String fingerprint) { if (fingerprint == null || fingerprint.length() < 40) { 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; + return false; + } else { + return true; } + } - String query = "0x" + fingerprint; + /** + * Scroll ViewPager left and right + * + * @param event + * @return + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean result = super.onTouchEvent(event); - // display keyserver fragment with query - Bundle args = new Bundle(); - args.putString(ImportKeysServerFragment.ARG_QUERY, query); - args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true); - loadNavFragment(NAV_SERVER, args); + if (!result) { + mViewPager.onTouchEvent(event); + } - // action: search directly - startListFragment(savedInstanceState, null, null, query); + return result; } - public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) { - mListFragment.loadNew(importData, dataUri, serverQuery, keyServer, keybaseQuery); + public void loadCallback(ImportKeysListFragment.LoaderState loaderState) { + mListFragment.loadNew(loaderState); } /** @@ -437,15 +460,16 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O 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); + 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(); /* @@ -466,7 +490,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O } }; - if (mListFragment.getKeyBytes() != null || mListFragment.getDataUri() != null) { + ImportKeysListFragment.LoaderState ls = mListFragment.getLoaderState(); + if (ls instanceof ImportKeysListFragment.BytesLoaderState) { Log.d(Constants.TAG, "importKeys started"); // Send all information needed to service to import key in other thread @@ -492,7 +517,9 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // start service with intent startService(intent); - } else if (mListFragment.getServerQuery() != null) { + } else if (ls instanceof ImportKeysListFragment.KeyserverLoaderState) { + ImportKeysListFragment.KeyserverLoaderState sls = (ImportKeysListFragment.KeyserverLoaderState) ls; + // Send all information needed to service to query keys in other thread Intent intent = new Intent(this, KeychainIntentService.class); @@ -501,7 +528,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // fill values for this action Bundle data = new Bundle(); - data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, mListFragment.getKeyServer()); + data.putString(KeychainIntentService.DOWNLOAD_KEY_SERVER, sls.keyserver); // get selected key entries ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedEntries(); @@ -518,7 +545,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O // start service with intent startService(intent); - } else if (mListFragment.getKeybaseQuery() != null) { + } else if (ls instanceof ImportKeysListFragment.KeybaseLoaderState) { // Send all information needed to service to query keys in other thread Intent intent = new Intent(this, KeychainIntentService.class); @@ -560,9 +587,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())) { - handleActionNdefDiscovered(getIntent()); + 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"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java deleted file mode 100644 index f331358fa..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysClipboardFragment.java +++ /dev/null @@ -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(); - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java index 51f961aab..60e5324c5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysFileFragment.java @@ -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(new ImportKeysListFragment.BytesLoaderState(sendText.getBytes(), 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 @@ -85,7 +107,7 @@ public class ImportKeysFileFragment extends Fragment { if (resultCode == Activity.RESULT_OK && data != null) { // load data - mImportActivity.loadCallback(null, data.getData(), null, null, null); + mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(null, data.getData())); } break; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java index a639fe0e0..3a82bdc32 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysKeybaseFragment.java @@ -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,7 +109,14 @@ 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); + mImportActivity.loadCallback(new ImportKeysListFragment.KeybaseLoaderState(query)); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index d91c55da3..cba6dd78f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -27,8 +27,6 @@ import android.support.v4.util.LongSparseArray; import android.view.View; import android.widget.ListView; -import com.devspark.appmsg.AppMsg; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.helper.Preferences; @@ -42,6 +40,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; @@ -59,11 +58,7 @@ public class ImportKeysListFragment extends ListFragment implements private Activity mActivity; private ImportKeysAdapter mAdapter; - private byte[] mKeyBytes; - private Uri mDataUri; - private String mServerQuery; - private String mKeyServer; - private String mKeybaseQuery; + private LoaderState mLoaderState; private static final int LOADER_ID_BYTES = 0; private static final int LOADER_ID_SERVER_QUERY = 1; @@ -71,24 +66,8 @@ public class ImportKeysListFragment extends ListFragment implements private LongSparseArray<ParcelableKeyRing> mCachedKeyData; - public byte[] getKeyBytes() { - return mKeyBytes; - } - - public Uri getDataUri() { - return mDataUri; - } - - public String getServerQuery() { - return mServerQuery; - } - - public String getKeybaseQuery() { - return mKeybaseQuery; - } - - public String getKeyServer() { - return mKeyServer; + public LoaderState getLoaderState() { + return mLoaderState; } public List<ImportKeysListEntry> getData() { @@ -97,7 +76,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; @@ -123,6 +102,37 @@ public class ImportKeysListFragment extends ListFragment implements return frag; } + static public class LoaderState { + } + + static public class BytesLoaderState extends LoaderState { + byte[] keyBytes; + Uri dataUri; + + BytesLoaderState(byte[] keyBytes, Uri dataUri) { + this.keyBytes = keyBytes; + this.dataUri = dataUri; + } + } + + static public class KeyserverLoaderState extends LoaderState { + String serverQuery; + String keyserver; + + KeyserverLoaderState(String serverQuery, String keyserver) { + this.serverQuery = serverQuery; + this.keyserver = keyserver; + } + } + + static public class KeybaseLoaderState extends LoaderState { + String keybaseQuery; + + KeybaseLoaderState(String keybaseQuery) { + this.keybaseQuery = keybaseQuery; + } + } + /** * Define Adapter and Loader on create of Activity */ @@ -139,43 +149,20 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter = new ImportKeysAdapter(mActivity); setListAdapter(mAdapter); - mDataUri = getArguments().getParcelable(ARG_DATA_URI); - mKeyBytes = getArguments().getByteArray(ARG_BYTES); - mServerQuery = getArguments().getString(ARG_SERVER_QUERY); - - // TODO: this is used when scanning QR Code. Currently it simply uses keyserver nr 0 - mKeyServer = Preferences.getPreferences(getActivity()) - .getKeyServers()[0]; - - if (mDataUri != null || mKeyBytes != null) { - // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - // give arguments to onCreateLoader() - getLoaderManager().initLoader(LOADER_ID_BYTES, null, this); - } - - if (mServerQuery != null && mKeyServer != null) { - // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - // give arguments to onCreateLoader() - getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this); + if (getArguments().containsKey(ARG_DATA_URI) || getArguments().containsKey(ARG_BYTES)) { + Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); + byte[] bytes = getArguments().getByteArray(ARG_BYTES); + mLoaderState = new BytesLoaderState(bytes, dataUri); + } else if (getArguments().containsKey(ARG_SERVER_QUERY)) { + String query = getArguments().getString(ARG_SERVER_QUERY); + // TODO: this is used when scanning QR Code or updating a key. + // Currently it simply uses keyserver nr 0 + String keyserver = Preferences.getPreferences(getActivity()) + .getKeyServers()[0]; + mLoaderState = new KeyserverLoaderState(query, keyserver); } - if (mKeybaseQuery != null) { - // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - // give arguments to onCreateLoader() - getLoaderManager().initLoader(LOADER_ID_KEYBASE, null, this); - } + restartLoaders(); } @Override @@ -191,31 +178,33 @@ public class ImportKeysListFragment extends ListFragment implements mAdapter.notifyDataSetChanged(); } - public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) { - mKeyBytes = keyBytes; - mDataUri = dataUri; - mServerQuery = serverQuery; - mKeyServer = keyServer; - mKeybaseQuery = keybaseQuery; + public void loadNew(LoaderState loaderState) { + mLoaderState = loaderState; + + restartLoaders(); + } - if (mKeyBytes != null || mDataUri != null) { + private void restartLoaders() { + if (mLoaderState instanceof BytesLoaderState) { // Start out with a progress indicator. setListShown(false); getLoaderManager().restartLoader(LOADER_ID_BYTES, null, this); - } - - if (mServerQuery != null && mKeyServer != null) { + getLoaderManager().destroyLoader(LOADER_ID_SERVER_QUERY); + getLoaderManager().destroyLoader(LOADER_ID_KEYBASE); + } else if (mLoaderState instanceof KeyserverLoaderState) { // Start out with a progress indicator. setListShown(false); + getLoaderManager().destroyLoader(LOADER_ID_BYTES); getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this); - } - - if (mKeybaseQuery != null) { + getLoaderManager().destroyLoader(LOADER_ID_KEYBASE); + } else if (mLoaderState instanceof KeybaseLoaderState) { // Start out with a progress indicator. setListShown(false); + getLoaderManager().destroyLoader(LOADER_ID_BYTES); + getLoaderManager().destroyLoader(LOADER_ID_SERVER_QUERY); getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this); } } @@ -225,14 +214,17 @@ public class ImportKeysListFragment extends ListFragment implements onCreateLoader(int id, Bundle args) { switch (id) { case LOADER_ID_BYTES: { - InputData inputData = getInputData(mKeyBytes, mDataUri); + BytesLoaderState ls = (BytesLoaderState) mLoaderState; + InputData inputData = getInputData(ls.keyBytes, ls.dataUri); return new ImportKeysListLoader(mActivity, inputData); } case LOADER_ID_SERVER_QUERY: { - return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer); + KeyserverLoaderState ls = (KeyserverLoaderState) mLoaderState; + return new ImportKeysListServerLoader(getActivity(), ls.serverQuery, ls.keyserver); } case LOADER_ID_KEYBASE: { - return new ImportKeysListKeybaseLoader(getActivity(), mKeybaseQuery); + KeybaseLoaderState ls = (KeybaseLoaderState) mLoaderState; + return new ImportKeysListKeybaseLoader(getActivity(), ls.keybaseQuery); } default: @@ -273,42 +265,34 @@ 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; case LOADER_ID_SERVER_QUERY: case LOADER_ID_KEYBASE: - // 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; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java deleted file mode 100644 index 45f464b1c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysNFCFragment.java +++ /dev/null @@ -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; - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java index 22b56e1ab..5766fc189 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysQrCodeFragment.java @@ -17,47 +17,40 @@ 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 String[] mScannedContent; + private View mNfcButton; + private View mQrCodeButton; /** * 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 +61,21 @@ 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() { + + @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); + } + }); - mButton.setOnClickListener(new OnClickListener() { + mQrCodeButton = view.findViewById(R.id.import_qrcode_button); + + mQrCodeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -85,10 +88,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 @@ -108,22 +111,14 @@ public class ImportKeysQrCodeFragment extends Fragment { return; } - // look if it is the whole key - String[] parts = scannedContent.split(","); - if (parts.length == 3) { - importParts(parts); - return; - } - // is this a full key encoded as qr code? if (scannedContent.startsWith("-----BEGIN PGP")) { - mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null, null); + mImportActivity.loadCallback(new ImportKeysListFragment.BytesLoaderState(scannedContent.getBytes(), null)); return; } // 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; @@ -140,65 +135,4 @@ public class ImportKeysQrCodeFragment extends Fragment { mImportActivity.loadFromFingerprintUri(null, dataUri); } - private void importParts(String[] parts) { - int counter = Integer.valueOf(parts[0]); - int size = Integer.valueOf(parts[1]); - String content = parts[2]; - - Log.d(Constants.TAG, "" + counter); - Log.d(Constants.TAG, "" + size); - Log.d(Constants.TAG, "" + content); - - // first qr code -> setup - if (counter == 0) { - mScannedContent = new String[size]; - mProgress.setMax(size); - mProgress.setVisibility(View.VISIBLE); - mText.setVisibility(View.VISIBLE); - } - - if (mScannedContent == null || counter > mScannedContent.length) { - AppMsg.makeText(getActivity(), R.string.import_qr_code_start_with_one, AppMsg.STYLE_ALERT) - .show(); - return; - } - - // save scanned content - mScannedContent[counter] = content; - - // get missing numbers - ArrayList<Integer> missing = new ArrayList<Integer>(); - for (int i = 0; i < mScannedContent.length; i++) { - if (mScannedContent[i] == null) { - missing.add(i); - } - } - - // update progress and text - int alreadyScanned = mScannedContent.length - missing.size(); - mProgress.setProgress(alreadyScanned); - - String missingString = ""; - for (int m : missing) { - if (!missingString.equals("")) { - missingString += ", "; - } - missingString += String.valueOf(m + 1); - } - - String missingText = getResources().getQuantityString(R.plurals.import_qr_code_missing, - missing.size(), missingString); - mText.setText(missingText); - - // finished! - if (missing.size() == 0) { - mText.setText(R.string.import_qr_code_finished); - String result = ""; - for (String in : mScannedContent) { - result += in; - } - mImportActivity.loadCallback(result.getBytes(), null, null, null, null); - } - } - } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java index 9e3d88ff5..c7467d789 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysServerFragment.java @@ -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,8 +163,15 @@ public class ImportKeysServerFragment extends Fragment { } } - private void search(String query, String keyServer) { - mImportActivity.loadCallback(null, null, query, keyServer, null); + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + + mImportActivity = (ImportKeysActivity) activity; + } + + private void search(String query, String keyserver) { + mImportActivity.loadCallback(new ImportKeysListFragment.KeyserverLoaderState(query, keyserver)); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 463c800d2..1912b6e7d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java index 03a82696d..c2712e89e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ImportKeysListLoader.java @@ -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); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java index b42a79993..cae6cf043 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/DeleteFileDialogFragment.java @@ -21,9 +21,11 @@ import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; +import android.provider.DocumentsContract; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import android.widget.Toast; @@ -34,6 +36,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler; public class DeleteFileDialogFragment extends DialogFragment { private static final String ARG_DELETE_FILE = "delete_file"; + private static final String ARG_DELETE_URI = "delete_uri"; /** * Creates new instance of this delete file dialog fragment @@ -50,12 +53,27 @@ public class DeleteFileDialogFragment extends DialogFragment { } /** + * Creates new instance of this delete file dialog fragment + */ + public static DeleteFileDialogFragment newInstance(Uri deleteUri) { + DeleteFileDialogFragment frag = new DeleteFileDialogFragment(); + Bundle args = new Bundle(); + + args.putParcelable(ARG_DELETE_URI, deleteUri); + + frag.setArguments(args); + + return frag; + } + + /** * Creates dialog */ @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final FragmentActivity activity = getActivity(); + final Uri deleteUri = getArguments().containsKey(ARG_DELETE_URI) ? getArguments().<Uri>getParcelable(ARG_DELETE_URI) : null; final String deleteFile = getArguments().getString(ARG_DELETE_FILE); CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity); @@ -71,6 +89,12 @@ public class DeleteFileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); + if (deleteUri != null) { + // We can not securely delete Documents, so just use usual delete on them + DocumentsContract.deleteDocument(getActivity().getContentResolver(), deleteUri); + return; + } + // Send all information needed to service to edit key in other thread Intent intent = new Intent(activity, KeychainIntentService.class); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java index 24f93bed7..10a24ddf0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/FileDialogFragment.java @@ -23,10 +23,13 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.provider.OpenableColumns; import android.support.v4.app.DialogFragment; import android.view.LayoutInflater; import android.view.View; @@ -50,6 +53,7 @@ public class FileDialogFragment extends DialogFragment { public static final int MESSAGE_OKAY = 1; + public static final String MESSAGE_DATA_URI = "uri"; public static final String MESSAGE_DATA_FILENAME = "filename"; public static final String MESSAGE_DATA_CHECKED = "checked"; @@ -60,6 +64,9 @@ public class FileDialogFragment extends DialogFragment { private CheckBox mCheckBox; private TextView mMessageTextView; + private String mOutputFilename; + private Uri mOutputUri; + private static final int REQUEST_CODE = 0x00007004; /** @@ -92,7 +99,7 @@ public class FileDialogFragment extends DialogFragment { String title = getArguments().getString(ARG_TITLE); String message = getArguments().getString(ARG_MESSAGE); - String defaultFile = getArguments().getString(ARG_DEFAULT_FILE); + mOutputFilename = getArguments().getString(ARG_DEFAULT_FILE); String checkboxText = getArguments().getString(ARG_CHECKBOX_TEXT); LayoutInflater inflater = (LayoutInflater) activity @@ -106,15 +113,18 @@ public class FileDialogFragment extends DialogFragment { mMessageTextView.setText(message); mFilename = (EditText) view.findViewById(R.id.input); - mFilename.setText(defaultFile); + mFilename.setText(mOutputFilename); mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse); mBrowse.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // only .asc or .gpg files // setting it to text/plain prevents Cynaogenmod's file manager from selecting asc // or gpg types! - FileHelper.openFile(FileDialogFragment.this, mFilename.getText().toString(), "*/*", - REQUEST_CODE); + if (Constants.KITKAT) { + FileHelper.saveDocument(FileDialogFragment.this, mOutputUri, "*/*", REQUEST_CODE); + } else { + FileHelper.openFile(FileDialogFragment.this, mOutputFilename, "*/*", REQUEST_CODE); + } } }); @@ -136,13 +146,19 @@ public class FileDialogFragment extends DialogFragment { public void onClick(DialogInterface dialog, int id) { dismiss(); - boolean checked = false; - if (mCheckBox.isEnabled()) { - checked = mCheckBox.isChecked(); + String currentFilename = mFilename.getText().toString(); + if (mOutputFilename == null || !mOutputFilename.equals(currentFilename)) { + mOutputUri = null; + mOutputFilename = mFilename.getText().toString(); } + boolean checked = mCheckBox.isEnabled() && mCheckBox.isChecked(); + // return resulting data back to activity Bundle data = new Bundle(); + if (mOutputUri != null) { + data.putParcelable(MESSAGE_DATA_URI, mOutputUri); + } data.putString(MESSAGE_DATA_FILENAME, mFilename.getText().toString()); data.putBoolean(MESSAGE_DATA_CHECKED, checked); @@ -178,14 +194,26 @@ public class FileDialogFragment extends DialogFragment { switch (requestCode & 0xFFFF) { case REQUEST_CODE: { if (resultCode == Activity.RESULT_OK && data != null) { - try { - String path = data.getData().getPath(); - Log.d(Constants.TAG, "path=" + path); - - // set filename used in export/import dialogs - setFilename(path); - } catch (NullPointerException e) { - Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); + if (Constants.KITKAT) { + mOutputUri = data.getData(); + Cursor cursor = getActivity().getContentResolver().query(mOutputUri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null) { + if (cursor.moveToNext()) { + mOutputFilename = cursor.getString(0); + mFilename.setText(mOutputFilename); + } + cursor.close(); + } + } else { + try { + String path = data.getData().getPath(); + Log.d(Constants.TAG, "path=" + path); + + // set filename used in export/import dialogs + setFilename(path); + } catch (NullPointerException e) { + Log.e(Constants.TAG, "Nullpointer while retrieving path!", e); + } } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java index 3e8e18ce5..b7336318f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -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) { diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..8de91173c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_nfc.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_nfc.png Binary files differnew file mode 100644 index 000000000..635633709 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_nfc.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..1c65e5af8 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-hdpi/icon.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_launcher.png Binary files differindex f5487599b..f5487599b 100644 --- a/OpenKeychain/src/main/res/drawable-hdpi/icon.png +++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_launcher.png diff --git a/OpenKeychain/src/main/res/drawable-ldpi/icon.png b/OpenKeychain/src/main/res/drawable-ldpi/ic_launcher.png Binary files differindex 7cd482bff..7cd482bff 100644 --- a/OpenKeychain/src/main/res/drawable-ldpi/icon.png +++ b/OpenKeychain/src/main/res/drawable-ldpi/ic_launcher.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..b89ea93ff --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_nfc.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_nfc.png Binary files differnew file mode 100644 index 000000000..da5e267d0 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_nfc.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..570212461 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-mdpi/icon.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_launcher.png Binary files differindex 34f1420ac..34f1420ac 100644 --- a/OpenKeychain/src/main/res/drawable-mdpi/icon.png +++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_launcher.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..88240fd30 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_nfc.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_nfc.png Binary files differnew file mode 100644 index 000000000..ff569927c --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_nfc.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..ce8965396 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/icon.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_launcher.png Binary files differindex 32584f3ff..32584f3ff 100644 --- a/OpenKeychain/src/main/res/drawable-xhdpi/icon.png +++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_launcher.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_collection.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_collection.png Binary files differnew file mode 100644 index 000000000..c41ca8c8b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_collection.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_nfc.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_nfc.png Binary files differnew file mode 100644 index 000000000..1f96ce37b --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_nfc.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_qr_code.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_qr_code.png Binary files differnew file mode 100644 index 000000000..c56b128e4 --- /dev/null +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_action_qr_code.png diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/icon.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_launcher.png Binary files differindex b2922309f..b2922309f 100644 --- a/OpenKeychain/src/main/res/drawable-xxhdpi/icon.png +++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_launcher.png diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/icon.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_launcher.png Binary files differindex 93ea6b0f5..93ea6b0f5 100644 --- a/OpenKeychain/src/main/res/drawable-xxxhdpi/icon.png +++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_launcher.png diff --git a/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml b/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml index 96271d418..ceea1d8a6 100644 --- a/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml +++ b/OpenKeychain/src/main/res/layout/api_app_settings_fragment.xml @@ -22,7 +22,7 @@ android:layout_alignParentBottom="true" android:layout_alignParentTop="true" android:layout_marginRight="6dp" - android:src="@drawable/icon" /> + android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/api_app_settings_app_name" diff --git a/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml b/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml index e70a79589..543f7d675 100644 --- a/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml +++ b/OpenKeychain/src/main/res/layout/api_apps_adapter_list_item.xml @@ -11,7 +11,7 @@ android:layout_height="48dp" android:layout_marginLeft="8dp" android:layout_centerVertical="true" - android:src="@drawable/icon" /> + android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/api_apps_adapter_item_name" diff --git a/OpenKeychain/src/main/res/layout/help_about_fragment.xml b/OpenKeychain/src/main/res/layout/help_about_fragment.xml index 71788e720..6afab2e12 100644 --- a/OpenKeychain/src/main/res/layout/help_about_fragment.xml +++ b/OpenKeychain/src/main/res/layout/help_about_fragment.xml @@ -28,7 +28,7 @@ android:layout_height="wrap_content" android:layout_gravity="top" android:layout_marginRight="10dp" - android:src="@drawable/icon" /> + android:src="@drawable/ic_launcher" /> </LinearLayout> <LinearLayout diff --git a/OpenKeychain/src/main/res/layout/import_keys_activity.xml b/OpenKeychain/src/main/res/layout/import_keys_activity.xml index 0486b6bd6..fc9d21e23 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_activity.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_activity.xml @@ -3,7 +3,7 @@ android:id="@+id/content_frame" android:layout_marginLeft="@dimen/drawer_content_padding" android:layout_width="match_parent" - android:layout_height="fill_parent" + android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout @@ -12,21 +12,38 @@ android:layout_height="wrap_content" android:orientation="vertical" /> - <FrameLayout - android:id="@+id/import_navigation_fragment" + <org.sufficientlysecure.keychain.ui.widget.SlidingTabLayout + android:id="@+id/import_sliding_tab_layout" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" /> + 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:paddingTop="8dp" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:layout_weight="0.9" /> + android:layout_weight="1" + android:background="@android:color/white" /> <LinearLayout android:id="@+id/import_footer" @@ -34,7 +51,8 @@ android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="16dp" - android:paddingRight="16dp"> + android:paddingRight="16dp" + android:background="@android:color/white"> <View android:layout_width="match_parent" diff --git a/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml deleted file mode 100644 index 739c34fba..000000000 --- a/OpenKeychain/src/main/res/layout/import_keys_clipboard_fragment.xml +++ /dev/null @@ -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>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml index c07d2bb40..b1056dab3 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_file_fragment.xml @@ -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: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_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: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>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml index ceba0e1ce..062289688 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_keybase_fragment.xml @@ -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" @@ -24,15 +20,13 @@ android:minLines="1" android:layout_gravity="center_vertical" /> - <com.beardedhen.androidbootstrap.BootstrapButton + <ImageButton android:id="@+id/import_keybase_search" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + 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>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml b/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml index c91335a5b..56f34e2eb 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_list_entry.xml @@ -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" diff --git a/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml deleted file mode 100644 index 8c0a80e4e..000000000 --- a/OpenKeychain/src/main/res/layout/import_keys_nfc_fragment.xml +++ /dev/null @@ -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>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml index 590f7f797..1cc414dab 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_qr_code_fragment.xml @@ -1,39 +1,47 @@ <?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: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" /> + <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" /> - <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" /> + <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_nfc_button" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + android:src="@drawable/ic_action_nfc" + android:layout_gravity="center_vertical" + style="@style/SelectableItem" /> + + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml b/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml index e17dbe783..47c354c53 100644 --- a/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml +++ b/OpenKeychain/src/main/res/layout/import_keys_server_fragment.xml @@ -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 @@ -32,16 +25,52 @@ android:minLines="1" android:layout_gravity="center_vertical" /> - <com.beardedhen.androidbootstrap.BootstrapButton + <ImageButton android:id="@+id/import_server_search" android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_height="match_parent" + android:padding="8dp" + 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>
\ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml index 67c2e241a..1cd2b9f1b 100644 --- a/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_share_fragment.xml @@ -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" diff --git a/OpenKeychain/src/main/res/values-de/strings.xml b/OpenKeychain/src/main/res/values-de/strings.xml index 3485846ad..d5cf037c8 100644 --- a/OpenKeychain/src/main/res/values-de/strings.xml +++ b/OpenKeychain/src/main/res/values-de/strings.xml @@ -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> diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 5a2c38419..e24ac6925 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -75,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> @@ -229,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> @@ -278,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> @@ -339,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> @@ -372,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> @@ -387,6 +381,7 @@ <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> @@ -512,18 +507,26 @@ <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_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_subkeys">Evaluating subkeys</string> + <string name="msg_ip_insert_keys">Parsing keys</string> <string name="msg_ip_prepare">Preparing database operations</string> - <string name="msg_ip_prepare_success">OK</string> - <string name="msg_ip_preserving_secret">Preserving available secret key</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">Subkey flags: %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> @@ -533,31 +536,35 @@ <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">Found good certificate from %1$s (%2$s)</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">Found uid revocation certificate</string> - <string name="msg_ip_uid_self_good">Found good self certificate</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_excption">Error encoding keyring</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">Canonicalizing keyring %s</string> + <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_master_success">OK</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> @@ -571,24 +578,55 @@ <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_dup">Removing redundant subkey binding certificate</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_sub_success">Subkey binding OK</string> - <string name="msg_kc_success">Keyring canonicalization successful</string> - <string name="msg_kc_success_removed">Keyring canonicalization successful, removed %s certificates</string> + <string name="msg_kc_sub_revoke_bad_err">Removing bad subkey revocation certificate</string> + <string name="msg_kc_sub_revoke_bad">Removing bad subkey revocation certificate</string> + <string name="msg_kc_sub_revoke_dup">Removing redundant subkey revocation certificate</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> diff --git a/OpenKeychain/src/main/res/xml/account_desc.xml b/OpenKeychain/src/main/res/xml/account_desc.xml index 94ffdf40b..a6989e9ef 100644 --- a/OpenKeychain/src/main/res/xml/account_desc.xml +++ b/OpenKeychain/src/main/res/xml/account_desc.xml @@ -2,5 +2,5 @@ <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="org.sufficientlysecure.keychain" - android:icon="@drawable/icon" + android:icon="@drawable/ic_launcher" android:label="@string/app_name"/> @@ -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/ diff --git a/Resources/graphics/ic_action_nfc.svg b/Resources/graphics/ic_action_nfc.svg new file mode 100644 index 000000000..23ec040ff --- /dev/null +++ b/Resources/graphics/ic_action_nfc.svg @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<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" + width="32" + height="32" + id="svg2" + version="1.1" + inkscape:version="0.48.3.1 r9886" + sodipodi:docname="ic_action_nfc.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="7.9195959" + inkscape:cx="55.015233" + inkscape:cy="28.459126" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="1918" + inkscape:window-height="1179" + inkscape:window-x="0" + inkscape:window-y="19" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-1020.3622)"> + <path + style="fill:#333333;fill-opacity:1;opacity:0.6" + d="m 5.8721355,1048.2554 c -0.4529966,-0.1375 -0.740227,-0.4023 -0.9002787,-0.8301 -0.1191051,-0.3184 -0.1207083,-0.4654 -0.1210019,-11.0625 -2.937e-4,-10.087 0.00614,-10.7582 0.1054687,-11.0385 0.1209465,-0.3414 0.3739125,-0.6212 0.714231,-0.7901 0.2298623,-0.1143 0.4666295,-0.1167 10.3597324,-0.1167 9.762967,0 10.132027,0 10.33542,0.1119 0.293646,0.1565 0.533124,0.3907 0.668434,0.6537 0.11192,0.2176 0.115013,0.517 0.115013,11.171 l 0,10.9473 -0.158878,0.292 c -0.170555,0.3133 -0.479601,0.5651 -0.810206,0.6599 -0.246895,0.071 -20.0747083,0.072 -20.3079261,0 z m 16.2976125,-2.4107 c 0.23156,-0.072 0.233209,-0.075 0.197176,-0.3243 -0.01999,-0.1382 -0.06539,-0.3531 -0.100895,-0.4778 -0.05999,-0.2104 -0.07395,-0.2212 -0.197176,-0.1528 -0.08059,0.045 -0.380826,0.075 -0.765398,0.075 -0.599986,0 -0.648397,-0.012 -0.934105,-0.1829 -0.791251,-0.4824 -0.931603,-1.6091 -0.289037,-2.3203 0.358193,-0.3965 0.834326,-0.478 2.00208,-0.3427 0.130096,0.016 0.159263,-0.024 0.265386,-0.3525 0.06557,-0.203 0.10323,-0.411 0.08367,-0.4619 -0.09945,-0.2592 -1.822581,-0.2986 -2.442687,-0.055 -0.730348,0.2858 -1.380503,1.0115 -1.523992,1.7009 -0.163151,0.7839 0.03489,1.6739 0.487021,2.1889 0.242512,0.2762 0.819987,0.6217 1.231169,0.7366 0.365786,0.1023 1.625695,0.083 1.986785,-0.03 z m -11.484429,-1.5922 -0.01861,-1.6494 0.305471,0.5797 c 0.168009,0.3188 0.590546,1.0543 0.938971,1.6343 l 0.633498,1.0547 0.627916,0 0.627916,0 0.01577,-2.3655 0.01577,-2.3653 -0.579954,0 -0.579953,0 0.02229,1.4971 0.02229,1.497 -0.393102,-0.7287 c -0.216208,-0.4008 -0.449401,-0.81 -0.518211,-0.9095 -0.137702,-0.199 -0.444752,-0.7018 -0.671555,-1.0998 l -0.145965,-0.2561 -0.718026,0 -0.7180255,0 0,2.3805 0,2.3804 0.5760585,0 0.576058,0 -0.01861,-1.6494 z m 5.254566,0.7169 0,-0.9325 0.888907,-0.017 0.888907,-0.017 0.0176,-0.467 0.01761,-0.4671 -0.906507,0 -0.906509,0 0,-0.4806 0,-0.4806 0.949172,-0.017 0.949173,-0.017 0.0176,-0.467 0.01761,-0.4671 -1.569422,0 -1.569421,0 0,2.3806 0,2.3804 0.602649,0 0.602648,0 0,-0.9326 z m 6.273699,-6.4951 c 0.263993,-0.1748 0.61016,-0.8748 0.983357,-1.9885 0.326831,-0.9753 0.423664,-1.4032 0.58101,-2.5681 0.146707,-1.0861 0.146304,-1.9076 -0.0016,-3.0218 -0.154191,-1.1622 -0.252803,-1.6038 -0.569457,-2.5496 -0.370747,-1.1074 -0.703918,-1.8019 -0.957103,-1.995 -0.398108,-0.3037 -1.035569,-0.1671 -1.308366,0.2804 -0.1709,0.2803 -0.152866,0.4134 0.144075,1.0638 0.748762,1.6401 1.136923,3.7484 0.996979,5.4153 -0.126969,1.5121 -0.448209,2.7968 -1.003437,4.0129 -0.286491,0.6275 -0.3029,0.8042 -0.103013,1.1092 0.247511,0.3778 0.853068,0.4959 1.237432,0.2414 z m -3.212265,-1.1548 c 0.08951,-0.047 0.234524,-0.162 0.322265,-0.2561 0.350427,-0.3759 0.948601,-2.1223 1.142298,-3.335 0.09437,-0.5908 0.09141,-2.0711 -0.0054,-2.6818 -0.199949,-1.2618 -0.839385,-3.0751 -1.195876,-3.3914 -0.324129,-0.2875 -0.671846,-0.3307 -1.031906,-0.1284 -0.496876,0.2792 -0.547131,0.6948 -0.179059,1.4805 0.135063,0.2883 0.343972,0.8922 0.464242,1.342 0.21768,0.8142 0.218674,0.8235 0.218674,2.0533 0,1.2298 -9.28e-4,1.2392 -0.218674,2.0533 -0.120272,0.4497 -0.325465,1.0465 -0.45599,1.3258 -0.315221,0.6747 -0.334073,0.7469 -0.256435,0.9821 0.160477,0.4864 0.778774,0.7737 1.195836,0.5557 z m -3.174159,-1.6681 c 0.186492,-0.1329 0.54841,-0.7896 0.691807,-1.2555 0.508888,-1.6532 0.377076,-3.2602 -0.388485,-4.7361 -0.327298,-0.6311 -0.615499,-0.8731 -1.039587,-0.8731 -0.674978,0 -0.899355,0.6963 -0.495246,1.5367 0.24473,0.5091 0.384292,1.1084 0.42106,1.808 0.03924,0.7467 -0.04997,1.3559 -0.198551,1.3559 -0.07304,0 -2.190719,-1.4926 -4.081753,-2.877 -1.1019127,-0.8066 -1.3287251,-0.9434 -1.5671351,-0.9452 -0.4533423,0 -0.7275993,0.4736 -0.9154451,1.5925 -0.1551399,0.924 -0.097393,1.9682 0.1566469,2.8324 0.1502703,0.5113 0.2975533,0.7419 0.6027144,0.9438 0.2927514,0.1937 0.5593056,0.2103 0.8029728,0.051 0.3564331,-0.2345 0.4157281,-0.6689 0.2187151,-1.6023 -0.1185758,-0.5619 -0.1563702,-1.0842 -0.083696,-1.1568 0.048046,-0.048 0.316088,0.09 0.775803,0.3986 0.396405,0.2664 2.231928,1.5694 3.043374,2.1604 1.077365,0.7846 1.270646,0.8934 1.587716,0.8936 0.19986,10e-5 0.347904,-0.04 0.469094,-0.1259 z" + id="path3047" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/Resources/graphics/ic_action_nfc/NFC.png b/Resources/graphics/ic_action_nfc/NFC.png Binary files differnew file mode 100644 index 000000000..96af64049 --- /dev/null +++ b/Resources/graphics/ic_action_nfc/NFC.png diff --git a/Resources/graphics/ic_action_qr_code.svg b/Resources/graphics/ic_action_qr_code.svg new file mode 100644 index 000000000..da7d0f97c --- /dev/null +++ b/Resources/graphics/ic_action_qr_code.svg @@ -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>
\ No newline at end of file diff --git a/Resources/graphics/ic_action_qr_code/ic_menu_qr_code.svg b/Resources/graphics/ic_action_qr_code/ic_menu_qr_code.svg new file mode 100644 index 000000000..5cbe9defc --- /dev/null +++ b/Resources/graphics/ic_action_qr_code/ic_menu_qr_code.svg @@ -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.2 r9819" + 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="1280" + inkscape:window-height="921" + id="namedview227" + showgrid="false" + inkscape:zoom="8.4558186" + inkscape:cx="9.2917208" + inkscape:cy="14.446251" + inkscape:window-x="-4" + inkscape:window-y="-4" + inkscape:window-maximized="1" + inkscape:current-layer="Layer_1" /> +<rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect25" + height="6.8569999" + width="2.286" + y="4.5710001" + x="4.5710001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect39" + height="6.8569999" + width="2.286" + y="4.5710001" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect55" + height="6.8569999" + width="2.2850001" + y="4.5710001" + x="9.1440001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect33" + height="6.8569999" + width="2.286" + y="36.57" + x="4.5710001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect49" + height="6.8569999" + width="2.286" + y="36.57" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect61" + height="6.8569999" + width="2.2850001" + y="36.57" + x="9.1440001" /><rect + y="0" + x="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect5" + height="16" + width="2.2850001" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect13" + height="2.2850001" + width="2.286" + x="2.2850001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect15" + height="2.286" + width="2.286" + y="13.714" + x="2.2850001" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect23" + height="2.2850001" + width="2.286" + x="4.5710001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect27" + height="2.286" + width="2.286" + y="13.714" + x="4.5710001" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect37" + height="2.2850001" + width="2.286" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect41" + height="2.286" + width="2.286" + y="13.714" + x="6.8569999" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect53" + height="2.2850001" + width="2.2850001" + x="9.1440001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect57" + height="2.286" + width="2.2850001" + y="13.714" + x="9.1440001" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect65" + height="2.2850001" + width="2.2850001" + x="11.429" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect67" + height="2.286" + width="2.2850001" + y="13.714" + x="11.429" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect75" + height="16" + width="2.286" + x="13.714" /><rect + x="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect11" + height="16" + width="2.2850001" + y="32" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect19" + height="2.2850001" + width="2.286" + y="32" + x="2.2850001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect21" + height="2.286" + width="2.286" + y="45.714001" + x="2.2850001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect31" + height="2.2850001" + width="2.286" + y="32" + x="4.5710001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect35" + height="2.286" + width="2.286" + y="45.714001" + x="4.5710001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect47" + height="2.2850001" + width="2.286" + y="32" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect51" + height="2.286" + width="2.286" + y="45.714001" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect59" + height="2.2850001" + width="2.2850001" + y="32" + x="9.1440001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect63" + height="2.286" + width="2.2850001" + y="45.714001" + x="9.1440001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect71" + height="2.2850001" + width="2.2850001" + y="32" + x="11.429" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect73" + height="2.286" + width="2.2850001" + y="45.714001" + x="11.429" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect83" + height="16" + width="2.286" + y="32" + x="13.714" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect163" + height="6.8569999" + width="2.2869999" + y="4.5710001" + x="36.57" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect177" + height="6.8569999" + width="2.2850001" + y="4.5710001" + x="38.856998" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect191" + height="6.8569999" + width="2.2869999" + y="4.5710001" + x="41.143002" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect141" + height="16" + width="2.2850001" + x="32" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect151" + height="2.2850001" + width="2.2850001" + x="34.285" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect153" + height="2.286" + width="2.2850001" + y="13.714" + x="34.285" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect161" + height="2.2850001" + width="2.2869999" + x="36.57" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect165" + height="2.286" + width="2.2869999" + y="13.714" + x="36.57" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect175" + height="2.2850001" + width="2.2850001" + x="38.856998" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect179" + height="2.286" + width="2.2850001" + y="13.714" + x="38.856998" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect189" + height="2.2850001" + width="2.2869999" + x="41.143002" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect193" + height="2.286" + width="2.2869999" + y="13.714" + x="41.143002" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect203" + height="2.2850001" + width="2.2839999" + x="43.43" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect205" + height="2.286" + width="2.2839999" + y="13.714" + x="43.43" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect217" + height="16" + width="2.286" + x="45.714001" /><rect + x="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect7" + height="4.572" + width="2.2850001" + y="18.285" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect17" + height="2.2869999" + width="2.286" + y="27.427999" + x="2.2850001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect29" + height="6.8559999" + width="2.286" + y="20.570999" + x="4.5710001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect43" + height="2.286" + width="2.286" + y="20.570999" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect45" + height="4.572" + width="2.286" + y="25.143" + x="6.8569999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect69" + height="2.286" + width="2.2850001" + y="22.857" + x="11.429" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect77" + height="2.286" + width="2.286" + y="18.285" + x="13.714" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect79" + height="2.286" + width="2.286" + y="22.857" + x="13.714" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect81" + height="2.2869999" + width="2.286" + y="27.427999" + x="13.714" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect85" + height="4.572" + width="2.286" + y="20.570999" + x="16" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect87" + height="6.8569999" + width="2.286" + y="2.2850001" + x="18.285999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect89" + height="18.285999" + width="2.286" + y="13.714" + x="18.285999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect91" + height="2.286" + width="2.286" + y="45.714001" + x="18.285999" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect93" + height="2.2850001" + width="2.2850001" + x="20.572001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect95" + height="2.286" + width="2.2850001" + y="9.1429996" + x="20.572001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect97" + height="6.8569999" + width="2.2850001" + y="25.143" + x="20.572001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect99" + height="2.2850001" + width="2.2850001" + y="34.285" + x="20.572001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect101" + height="9.1429996" + width="2.2850001" + y="38.856998" + x="20.572001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect103" + height="2.2850001" + width="2.286" + y="6.8569999" + x="22.857" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect107" + height="6.8569999" + width="2.286" + y="18.285" + x="22.857" /><rect + y="0" + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect113" + height="2.2850001" + width="2.2850001" + x="25.143" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect117" + height="2.2850001" + width="2.2850001" + y="16" + x="25.143" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect119" + height="2.286" + width="2.2850001" + y="22.857" + x="25.143" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect121" + height="2.2850001" + width="2.2850001" + y="29.715" + x="25.143" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect123" + height="2.2850001" + width="2.2850001" + y="34.285" + x="25.143" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect125" + height="2.286" + width="2.2850001" + y="45.714001" + x="25.143" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect127" + height="4.572" + width="2.286" + y="2.2850001" + x="27.427999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect129" + height="13.715" + width="2.286" + y="9.1429996" + x="27.427999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect131" + height="11.428" + width="2.286" + y="29.715" + x="27.427999" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect135" + height="4.572" + width="2.286" + y="27.427999" + x="29.714001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect137" + height="2.2850001" + width="2.286" + y="34.285" + x="29.714001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect139" + height="2.286" + width="2.286" + y="45.714001" + x="29.714001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect145" + height="2.286" + width="2.2850001" + y="22.857" + x="32" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect149" + height="2.2869999" + width="2.2850001" + y="41.143002" + x="32" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect155" + height="6.8559999" + width="2.2850001" + y="20.570999" + x="34.285" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect157" + height="4.572" + width="2.2850001" + y="34.285" + x="34.285" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect169" + height="2.2869999" + width="2.2869999" + y="27.427999" + x="36.57" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect171" + height="4.5700002" + width="2.2869999" + y="32" + x="36.57" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect173" + height="9.1429996" + width="2.2869999" + y="38.856998" + x="36.57" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect181" + height="2.286" + width="2.2850001" + y="18.285" + x="38.856998" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect183" + height="4.5700002" + width="2.2850001" + y="22.857" + x="38.856998" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect185" + height="2.2850001" + width="2.2850001" + y="34.285" + x="38.856998" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect187" + height="2.2839999" + width="2.2850001" + y="43.43" + x="38.856998" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect195" + height="9.1429996" + width="2.2869999" + y="18.285" + x="41.143002" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect197" + height="2.2850001" + width="2.2869999" + y="29.715" + x="41.143002" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect199" + height="2.2850001" + width="2.2869999" + y="34.285" + x="41.143002" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect201" + height="6.855" + width="2.2869999" + y="38.856998" + x="41.143002" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect207" + height="2.286" + width="2.2839999" + y="18.285" + x="43.43" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect209" + height="6.8569999" + width="2.2839999" + y="22.857" + x="43.43" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect211" + height="2.2850001" + width="2.2839999" + y="32" + x="43.43" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect215" + height="2.286" + width="2.2839999" + y="45.714001" + x="43.43" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect219" + height="2.2850001" + width="2.286" + y="25.143" + x="45.714001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect223" + height="4.572" + width="2.286" + y="34.285" + x="45.714001" /><rect + style="opacity:0.6;fill:#333333;fill-opacity:1" + id="rect225" + height="2.2869999" + width="2.286" + y="41.143002" + x="45.714001" /> +</svg>
\ No newline at end of file diff --git a/Resources/graphics/icon.png b/Resources/graphics/ic_launcher.png Binary files differindex a7b133eb9..a7b133eb9 100644 --- a/Resources/graphics/icon.png +++ b/Resources/graphics/ic_launcher.png diff --git a/Resources/graphics/icon.svg b/Resources/graphics/ic_launcher.svg index 2532ed83c..2532ed83c 100644 --- a/Resources/graphics/icon.svg +++ b/Resources/graphics/ic_launcher.svg diff --git a/Resources/graphics/icon/AUTHORS b/Resources/graphics/ic_launcher/AUTHORS index dbfcfb4fc..dbfcfb4fc 100644 --- a/Resources/graphics/icon/AUTHORS +++ b/Resources/graphics/ic_launcher/AUTHORS diff --git a/Resources/graphics/icon/COPYING b/Resources/graphics/ic_launcher/COPYING index 2faa27568..2faa27568 100644 --- a/Resources/graphics/icon/COPYING +++ b/Resources/graphics/ic_launcher/COPYING diff --git a/Resources/graphics/icon/kgpg_key2_kopete.svgz b/Resources/graphics/ic_launcher/kgpg_key2_kopete.svgz Binary files differindex 2d43afb83..2d43afb83 100644 --- a/Resources/graphics/icon/kgpg_key2_kopete.svgz +++ b/Resources/graphics/ic_launcher/kgpg_key2_kopete.svgz diff --git a/Resources/graphics/update-icon.sh b/Resources/graphics/update-drawables.sh index 3df587a21..c1dfc77e6 100755 --- a/Resources/graphics/update-icon.sh +++ b/Resources/graphics/update-drawables.sh @@ -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 @@ -20,7 +20,7 @@ PLAY_DIR=./ # xxxhdpi 192x192. # google play: 512x512 -NAME="icon" +NAME="ic_launcher" inkscape -w 36 -h 36 -e "$LDPI_DIR/$NAME.png" $NAME.svg inkscape -w 48 -h 48 -e "$MDPI_DIR/$NAME.png" $NAME.svg @@ -30,3 +30,17 @@ inkscape -w 144 -h 144 -e "$XXDPI_DIR/$NAME.png" $NAME.svg inkscape -w 192 -h 192 -e "$XXXDPI_DIR/$NAME.png" $NAME.svg inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg +# Actionbar Icons +# ----------------------- +# mdpi: 32x32 +# hdpi: 48x48 +# xhdpi: 64x64 +# xxhdpi: 96x96 + +for NAME in "ic_action_nfc" "ic_action_qr_code" +do +inkscape -w 32 -h 32 -e "$MDPI_DIR/$NAME.png" $NAME.svg +inkscape -w 48 -h 48 -e "$HDPI_DIR/$NAME.png" $NAME.svg +inkscape -w 64 -h 64 -e "$XDPI_DIR/$NAME.png" $NAME.svg +inkscape -w 96 -h 96 -e "$XXDPI_DIR/$NAME.png" $NAME.svg +done
\ No newline at end of file diff --git a/extern/spongycastle b/extern/spongycastle -Subproject 2c47e5fca2a820a4fd584066871bed993f1c391 +Subproject 09d85b7d7a64b3003210d065c4210ff7fb7a8c6 |