Merge branch 'master' of github.com:open-keychain/open-keychain

This commit is contained in:
Adithya Abraham Philip
2015-06-30 22:12:16 +05:30
140 changed files with 5316 additions and 1937 deletions

View File

@@ -33,6 +33,7 @@ import android.provider.ContactsContract;
import android.widget.Toast;
import org.spongycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.ConsolidateDialogActivity;
@@ -101,7 +102,10 @@ public class KeychainApplication extends Application {
TemporaryStorageProvider.cleanUp(this);
checkConsolidateRecovery();
if (!checkConsolidateRecovery()) {
// force DB upgrade, https://github.com/open-keychain/open-keychain/issues/1334
new KeychainDatabase(this).getReadableDatabase().close();
}
}
public static HashMap<String,Bitmap> qrCodeCache = new HashMap<>();
@@ -118,12 +122,15 @@ public class KeychainApplication extends Application {
/**
* Restart consolidate process if it has been interruped before
*/
public void checkConsolidateRecovery() {
public boolean checkConsolidateRecovery() {
if (Preferences.getPreferences(this).getCachedConsolidate()) {
Intent consolidateIntent = new Intent(this, ConsolidateDialogActivity.class);
consolidateIntent.putExtra(ConsolidateDialogActivity.EXTRA_CONSOLIDATE_RECOVERY, true);
consolidateIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(consolidateIntent);
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.compatibility;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
* <p/>
* This technique can be used with an {@link android.app.Activity} class, not just
* {@link android.preference.PreferenceActivity}.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@@ -38,7 +38,7 @@ public class ClipboardReflection {
}
public static CharSequence getClipboardText(Context context) {
public static String getClipboardText(Context context) {
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = clipboard.getPrimaryClip();
@@ -48,6 +48,10 @@ public class ClipboardReflection {
}
ClipData.Item item = clip.getItemAt(0);
return item.coerceToText(context);
CharSequence seq = item.coerceToText(context);
if (seq != null) {
return seq.toString();
}
return null;
}
}

View File

@@ -70,7 +70,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLConnection;
import java.security.SignatureException;
import java.util.Date;
import java.util.Iterator;
@@ -676,8 +675,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
|| literalData.getFormat() == PGPLiteralData.UTF8) {
mimeType = "text/plain";
} else {
// TODO: better would be: https://github.com/open-keychain/open-keychain/issues/753
// try to guess from file ending
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
if (extension != null) {
@@ -685,10 +682,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
mimeType = mime.getMimeTypeFromExtension(extension);
}
if (mimeType == null) {
mimeType = URLConnection.guessContentTypeFromName(originalFilename);
}
if (mimeType == null) {
mimeType = "*/*";
mimeType = "application/octet-stream";
}
}
@@ -944,7 +938,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
log.add(LogType.MSG_DC_OK, indent);
OpenPgpMetadata metadata = new OpenPgpMetadata(
"",
"text/plain",
-1,
clearText.length);
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setDecryptMetadata(metadata);
result.setSignatureResult(signatureResultBuilder.build());
return result;
}

View File

@@ -520,7 +520,6 @@ public class KeychainProvider extends ContentProvider {
}
break;
}
case KEY_RINGS_PUBLIC:
@@ -607,23 +606,26 @@ public class KeychainProvider extends ContentProvider {
break;
}
case API_APPS:
case API_APPS: {
qb.setTables(Tables.API_APPS);
break;
case API_APPS_BY_PACKAGE_NAME:
}
case API_APPS_BY_PACKAGE_NAME: {
qb.setTables(Tables.API_APPS);
qb.appendWhere(ApiApps.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API_ACCOUNTS:
}
case API_ACCOUNTS: {
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
}
case API_ACCOUNTS_BY_ACCOUNT_NAME: {
qb.setTables(Tables.API_ACCOUNTS);
qb.appendWhere(Tables.API_ACCOUNTS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
@@ -632,14 +634,17 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
case API_ALLOWED_KEYS:
}
case API_ALLOWED_KEYS: {
qb.setTables(Tables.API_ALLOWED_KEYS);
qb.appendWhere(Tables.API_ALLOWED_KEYS + "." + ApiAccounts.PACKAGE_NAME + " = ");
qb.appendWhereEscapeString(uri.getPathSegments().get(1));
break;
default:
}
default: {
throw new IllegalArgumentException("Unknown URI " + uri + " (" + match + ")");
}
}
@@ -684,47 +689,47 @@ public class KeychainProvider extends ContentProvider {
final int match = mUriMatcher.match(uri);
switch (match) {
case KEY_RING_PUBLIC:
case KEY_RING_PUBLIC: {
db.insertOrThrow(Tables.KEY_RINGS_PUBLIC, null, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
case KEY_RING_SECRET:
}
case KEY_RING_SECRET: {
db.insertOrThrow(Tables.KEY_RINGS_SECRET, null, values);
keyId = values.getAsLong(KeyRings.MASTER_KEY_ID);
break;
case KEY_RING_KEYS:
}
case KEY_RING_KEYS: {
db.insertOrThrow(Tables.KEYS, null, values);
keyId = values.getAsLong(Keys.MASTER_KEY_ID);
break;
case KEY_RING_USER_IDS:
}
case KEY_RING_USER_IDS: {
// iff TYPE is null, user_id MUST be null as well
if ( ! (values.get(UserPacketsColumns.TYPE) == null
if (!(values.get(UserPacketsColumns.TYPE) == null
? (values.get(UserPacketsColumns.USER_ID) != null && values.get(UserPacketsColumns.ATTRIBUTE_DATA) == null)
: (values.get(UserPacketsColumns.ATTRIBUTE_DATA) != null && values.get(UserPacketsColumns.USER_ID) == null)
)) {
)) {
throw new AssertionError("Incorrect type for user packet! This is a bug!");
}
if (((Number)values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
if (((Number) values.get(UserPacketsColumns.RANK)).intValue() == 0 && values.get(UserPacketsColumns.USER_ID) == null) {
throw new AssertionError("Rank 0 user packet must be a user id!");
}
db.insertOrThrow(Tables.USER_PACKETS, null, values);
keyId = values.getAsLong(UserPackets.MASTER_KEY_ID);
break;
case KEY_RING_CERTS:
}
case KEY_RING_CERTS: {
// we replace here, keeping only the latest signature
// TODO this would be better handled in savePublicKeyRing directly!
db.replaceOrThrow(Tables.CERTS, null, values);
keyId = values.getAsLong(Certs.MASTER_KEY_ID);
break;
case API_APPS:
}
case API_APPS: {
db.insertOrThrow(Tables.API_APPS, null, values);
break;
}
case API_ACCOUNTS: {
// set foreign key automatically based on given uri
// e.g., api_apps/com.example.app/accounts/
@@ -743,8 +748,9 @@ public class KeychainProvider extends ContentProvider {
db.insertOrThrow(Tables.API_ALLOWED_KEYS, null, values);
break;
}
default:
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
if (keyId != null) {
@@ -802,20 +808,24 @@ public class KeychainProvider extends ContentProvider {
break;
}
case API_APPS_BY_PACKAGE_NAME:
case API_APPS_BY_PACKAGE_NAME: {
count = db.delete(Tables.API_APPS, buildDefaultApiAppsSelection(uri, additionalSelection),
selectionArgs);
break;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
}
case API_ACCOUNTS_BY_ACCOUNT_NAME: {
count = db.delete(Tables.API_ACCOUNTS, buildDefaultApiAccountsSelection(uri, additionalSelection),
selectionArgs);
break;
case API_ALLOWED_KEYS:
}
case API_ALLOWED_KEYS: {
count = db.delete(Tables.API_ALLOWED_KEYS, buildDefaultApiAllowedKeysSelection(uri, additionalSelection),
selectionArgs);
break;
default:
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
// notify of changes in db
@@ -851,16 +861,19 @@ public class KeychainProvider extends ContentProvider {
count = db.update(Tables.KEYS, values, actualSelection, selectionArgs);
break;
}
case API_APPS_BY_PACKAGE_NAME:
case API_APPS_BY_PACKAGE_NAME: {
count = db.update(Tables.API_APPS, values,
buildDefaultApiAppsSelection(uri, selection), selectionArgs);
break;
case API_ACCOUNTS_BY_ACCOUNT_NAME:
}
case API_ACCOUNTS_BY_ACCOUNT_NAME: {
count = db.update(Tables.API_ACCOUNTS, values,
buildDefaultApiAccountsSelection(uri, selection), selectionArgs);
break;
default:
}
default: {
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
// notify of changes in db

View File

@@ -19,23 +19,15 @@
package org.sufficientlysecure.keychain.provider;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
import android.content.ClipDescription;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.provider.OpenableColumns;
@@ -43,6 +35,30 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.DatabaseUtil;
import org.sufficientlysecure.keychain.util.Log;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;
/**
* TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to
* sharing them with other applications.
*
* Security:
* - It is writable by OpenKeychain only (see Manifest), but exported for reading files
* - It uses UUIDs as identifiers which makes predicting files from outside impossible
* - Querying a number of files is not allowed, only querying single files
* -> You can only open a file if you know the Uri containing the precise UUID, this Uri is only
* revealed when the user shares a decrypted file with another app.
*
* Why is support lib's FileProvider not used?
* Because granting Uri permissions temporarily does not work correctly. See
* - https://code.google.com/p/android/issues/detail?id=76683
* - https://github.com/nmr8acme/FileProvider-permission-bug
* - http://stackoverflow.com/q/24467696
* - http://stackoverflow.com/q/18249007
* - Comments at http://www.blogc.at/2014/03/23/share-private-files-with-other-apps-fileprovider/
*/
public class TemporaryStorageProvider extends ContentProvider {
private static final String DB_NAME = "tempstorage.db";
@@ -143,6 +159,10 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
if (uri.getLastPathSegment() == null) {
throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
}
File file;
try {
file = getFile(uri);
@@ -153,9 +173,15 @@ public class TemporaryStorageProvider extends ContentProvider {
new String[]{uri.getLastPathSegment()}, null, null, null);
if (fileName != null) {
if (fileName.moveToNext()) {
MatrixCursor cursor =
new MatrixCursor(new String[]{OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, "_data"});
cursor.newRow().add(fileName.getString(0)).add(file.length()).add(file.getAbsolutePath());
MatrixCursor cursor = new MatrixCursor(new String[]{
OpenableColumns.DISPLAY_NAME,
OpenableColumns.SIZE,
"_data"
});
cursor.newRow()
.add(fileName.getString(0))
.add(file.length())
.add(file.getAbsolutePath());
fileName.close();
return cursor;
}
@@ -167,8 +193,8 @@ public class TemporaryStorageProvider extends ContentProvider {
@Override
public String getType(Uri uri) {
Cursor cursor = db.getReadableDatabase().query(TABLE_FILES,
new String[]{ COLUMN_TYPE }, COLUMN_ID + "=?",
new String[]{ uri.getLastPathSegment() }, null, null, null);
new String[]{COLUMN_TYPE}, COLUMN_ID + "=?",
new String[]{uri.getLastPathSegment()}, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToNext()) {
@@ -180,14 +206,14 @@ public class TemporaryStorageProvider extends ContentProvider {
cursor.close();
}
}
return "*/*";
return "application/octet-stream";
}
@Override
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
String type = getType(uri);
if (ClipDescription.compareMimeTypes(type, mimeTypeFilter)) {
return new String[] { type };
return new String[]{type};
}
return null;
}
@@ -200,9 +226,14 @@ public class TemporaryStorageProvider extends ContentProvider {
String uuid = UUID.randomUUID().toString();
values.put(COLUMN_ID, uuid);
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
if (insert == -1) {
Log.e(Constants.TAG, "Insert failed!");
return null;
}
try {
getFile(uuid).createNewFile();
} catch (IOException e) {
Log.e(Constants.TAG, "File creation failed!");
return null;
}
return Uri.withAppendedPath(BASE_URI, uuid);
@@ -238,7 +269,7 @@ public class TemporaryStorageProvider extends ContentProvider {
throw new UnsupportedOperationException("Update supported only for plain uri!");
}
return db.getWritableDatabase().update(TABLE_FILES, values,
COLUMN_ID + " = ?", new String[]{ uri.getLastPathSegment() });
COLUMN_ID + " = ?", new String[]{uri.getLastPathSegment()});
}
@Override

View File

@@ -167,7 +167,7 @@ public class OpenPgpService extends RemoteService {
Intent data, RequiredInputParcel requiredInput) {
switch (requiredInput.mType) {
case NFC_KEYTOCARD:
case NFC_MOVE_KEY_TO_CARD:
case NFC_DECRYPT:
case NFC_SIGN: {
// build PendingIntent for YubiKey NFC operations

View File

@@ -459,11 +459,16 @@ public class PassphraseCacheService extends Service {
* Called when one specific passphrase for keyId timed out
*/
private void timeout(long keyId) {
CachedPassphrase cPass = mPassphraseCache.get(keyId);
// clean internal char[] from memory!
cPass.getPassphrase().removeFromMemory();
// remove passphrase object
mPassphraseCache.remove(keyId);
if (cPass != null) {
if (cPass.getPassphrase() != null) {
// clean internal char[] from memory!
cPass.getPassphrase().removeFromMemory();
}
// remove passphrase object
mPassphraseCache.remove(keyId);
}
Log.d(Constants.TAG, "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");

View File

@@ -12,7 +12,7 @@ import android.os.Parcelable;
public class RequiredInputParcel implements Parcelable {
public enum RequiredInputType {
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_KEYTOCARD
PASSPHRASE, PASSPHRASE_SYMMETRIC, NFC_SIGN, NFC_DECRYPT, NFC_MOVE_KEY_TO_CARD
}
public Date mSignatureTime;
@@ -226,7 +226,7 @@ public class RequiredInputParcel implements Parcelable {
ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0));
// We need to pass in a subkey here...
return new RequiredInputParcel(RequiredInputType.NFC_KEYTOCARD,
return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD,
inputHashes, null, null, mMasterKeyId, buf.getLong());
}
@@ -241,7 +241,7 @@ public class RequiredInputParcel implements Parcelable {
if (!mMasterKeyId.equals(input.mMasterKeyId)) {
throw new AssertionError("Master keys must match, this is a programming error!");
}
if (input.mType != RequiredInputType.NFC_KEYTOCARD) {
if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
throw new AssertionError("Operation types must match, this is a programming error!");
}

View File

@@ -17,9 +17,8 @@
package org.sufficientlysecure.keychain.ui;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
@@ -64,6 +63,23 @@ public class CreateKeyActivity extends BaseNfcActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// React on NDEF_DISCOVERED from Manifest
// NOTE: ACTION_NDEF_DISCOVERED and not ACTION_TAG_DISCOVERED like in BaseNfcActivity
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
try {
handleTagDiscoveredIntent(getIntent());
} catch (CardException e) {
handleNfcError(e);
} catch (IOException e) {
handleNfcError(e);
}
setTitle(R.string.title_manage_my_keys);
// done
return;
}
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
@@ -89,17 +105,25 @@ public class CreateKeyActivity extends BaseNfcActivity {
String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID);
byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID);
Fragment frag2 = CreateKeyYubiKeyImportFragment.createInstance(
nfcFingerprints, nfcAid, nfcUserId);
loadFragment(frag2, FragAction.START);
if (containsKeys(nfcFingerprints)) {
Fragment frag = CreateKeyYubiKeyImportFragment.newInstance(
nfcFingerprints, nfcAid, nfcUserId);
loadFragment(frag, FragAction.START);
setTitle(R.string.title_import_keys);
setTitle(R.string.title_import_keys);
} else {
Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance();
loadFragment(frag, FragAction.START);
setTitle(R.string.title_manage_my_keys);
}
// done
return;
} else {
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
loadFragment(frag, FragAction.START);
}
// normal key creation
CreateKeyStartFragment frag = CreateKeyStartFragment.newInstance();
loadFragment(frag, FragAction.START);
}
if (mFirstTime) {
@@ -122,16 +146,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
byte[] nfcAid = nfcGetAid();
String userId = nfcGetUserId();
// If all fingerprint bytes are 0, the card contains no keys.
boolean cardContainsKeys = false;
for (byte b : scannedFingerprints) {
if (b != 0) {
cardContainsKeys = true;
break;
}
}
if (cardContainsKeys) {
if (containsKeys(scannedFingerprints)) {
try {
long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(scannedFingerprints);
CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
@@ -146,25 +161,29 @@ public class CreateKeyActivity extends BaseNfcActivity {
finish();
} catch (PgpKeyNotFoundException e) {
Fragment frag = CreateKeyYubiKeyImportFragment.createInstance(
Fragment frag = CreateKeyYubiKeyImportFragment.newInstance(
scannedFingerprints, nfcAid, userId);
loadFragment(frag, FragAction.TO_RIGHT);
}
} else {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.first_time_blank_smartcard_title)
.setMessage(R.string.first_time_blank_smartcard_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int button) {
CreateKeyActivity.this.mUseSmartCardSettings = true;
}
})
.setNegativeButton(android.R.string.no, null).show();
Fragment frag = CreateKeyYubiKeyBlankFragment.newInstance();
loadFragment(frag, FragAction.TO_RIGHT);
}
}
private boolean containsKeys(byte[] scannedFingerprints) {
// If all fingerprint bytes are 0, the card contains no keys.
boolean cardContainsKeys = false;
for (byte b : scannedFingerprints) {
if (b != 0) {
cardContainsKeys = true;
break;
}
}
return cardContainsKeys;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -182,7 +201,7 @@ public class CreateKeyActivity extends BaseNfcActivity {
setContentView(R.layout.create_key_activity);
}
public static enum FragAction {
public enum FragAction {
START,
TO_RIGHT,
TO_LEFT

View File

@@ -18,9 +18,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.Message;
import android.os.Messenger;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -28,25 +32,32 @@ import android.widget.CheckBox;
import android.widget.TextView;
import org.spongycastle.bcpg.sig.KeyFlags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.ExportResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.ExportKeyringParcel;
import org.sufficientlysecure.keychain.service.KeychainService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.ChangeUnlockParcel;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationFragment;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import java.util.Iterator;
public class CreateKeyFinalFragment
extends CryptoOperationFragment<SaveKeyringParcel, EditKeyResult> {
public class CreateKeyFinalFragment extends CryptoOperationFragment<SaveKeyringParcel, OperationResult> {
public static final int REQUEST_EDIT_KEY = 0x00008007;
@@ -61,6 +72,7 @@ public class CreateKeyFinalFragment
SaveKeyringParcel mSaveKeyringParcel;
private CryptoOperationHelper<ExportKeyringParcel, ExportResult> mUploadOpHelper;
private CryptoOperationHelper<SaveKeyringParcel, EditKeyResult> mCreateOpHelper;
public static CreateKeyFinalFragment newInstance() {
CreateKeyFinalFragment frag = new CreateKeyFinalFragment();
@@ -128,14 +140,16 @@ public class CreateKeyFinalFragment
}
});
// If this is a debug build, don't upload by default
if (Constants.DEBUG) {
mUploadCheckbox.setChecked(false);
}
return view;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mUploadOpHelper != null) {
mUploadOpHelper.handleActivityResult(requestCode, resultCode, data);
}
switch (requestCode) {
case REQUEST_EDIT_KEY: {
if (resultCode == Activity.RESULT_OK) {
@@ -150,30 +164,6 @@ public class CreateKeyFinalFragment
}
}
@Override
public SaveKeyringParcel createOperationInput() {
return mSaveKeyringParcel;
}
@Override
public void onCryptoOperationSuccess(EditKeyResult result) {
if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) {
// result will be displayed after upload
uploadKey(result);
} else {
Intent data = new Intent();
data.putExtra(OperationResult.EXTRA_RESULT, result);
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().finish();
}
}
@Override
protected void onCryptoOperationResult(EditKeyResult result) {
// do something else?
super.onCryptoOperationResult(result);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -190,6 +180,7 @@ public class CreateKeyFinalFragment
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
2048, null, KeyFlags.AUTHENTICATION, 0L));
mEditText.setText(R.string.create_key_custom);
mEditButton.setEnabled(false);
} else {
mSaveKeyringParcel.mAddSubKeys.add(new SaveKeyringParcel.SubkeyAdd(Algorithm.RSA,
4096, null, KeyFlags.CERTIFY_OTHER, 0L));
@@ -218,15 +209,112 @@ public class CreateKeyFinalFragment
}
}
private void createKey() {
super.setProgressMessageResource(R.string.progress_building_key);
super.cryptoOperation();
final CreateKeyActivity createKeyActivity = (CreateKeyActivity) getActivity();
CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult> createKeyCallback
= new CryptoOperationHelper.Callback<SaveKeyringParcel, EditKeyResult>() {
@Override
public SaveKeyringParcel createOperationInput() {
return mSaveKeyringParcel;
}
@Override
public void onCryptoOperationSuccess(EditKeyResult result) {
if (createKeyActivity.mUseSmartCardSettings) {
// save key id in between
mSaveKeyringParcel.mMasterKeyId = result.mMasterKeyId;
// calls cryptoOperation corresponding to moveToCard
cryptoOperation(new CryptoInputParcel());
return;
}
if (result.mMasterKeyId != null && mUploadCheckbox.isChecked()) {
// result will be displayed after upload
uploadKey(result);
return;
}
Intent data = new Intent();
data.putExtra(OperationResult.EXTRA_RESULT, result);
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().finish();
}
@Override
public void onCryptoOperationCancelled() {
}
@Override
public void onCryptoOperationError(EditKeyResult result) {
result.createNotify(getActivity()).show();
}
@Override
public boolean onCryptoSetProgress(String msg, int progress, int max) {
return false;
}
};
mCreateOpHelper = new CryptoOperationHelper<>(this, createKeyCallback,
R.string.progress_building_key);
mCreateOpHelper.cryptoOperation();
}
// currently only used for moveToCard
@Override
public SaveKeyringParcel createOperationInput() {
CachedPublicKeyRing key = (new ProviderHelper(getActivity()))
.getCachedPublicKeyRing(mSaveKeyringParcel.mMasterKeyId);
// overwrite mSaveKeyringParcel!
try {
mSaveKeyringParcel = new SaveKeyringParcel(key.getMasterKeyId(), key.getFingerprint());
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "Key that should be moved to YubiKey not found in database!");
return null;
}
Cursor cursor = getActivity().getContentResolver().query(
KeychainContract.Keys.buildKeysUri(mSaveKeyringParcel.mMasterKeyId),
new String[]{KeychainContract.Keys.KEY_ID,}, null, null, null
);
try {
while (cursor != null && cursor.moveToNext()) {
long subkeyId = cursor.getLong(0);
mSaveKeyringParcel.getOrCreateSubkeyChange(subkeyId).mMoveKeyToCard = true;
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return mSaveKeyringParcel;
}
// currently only used for moveToCard
@Override
public void onCryptoOperationSuccess(OperationResult result) {
EditKeyResult editResult = (EditKeyResult) result;
if (editResult.mMasterKeyId != null && mUploadCheckbox.isChecked()) {
// result will be displayed after upload
uploadKey(editResult);
return;
}
Intent data = new Intent();
data.putExtra(OperationResult.EXTRA_RESULT, result);
getActivity().setResult(Activity.RESULT_OK, data);
getActivity().finish();
}
// TODO move into EditKeyOperation
private void uploadKey(final EditKeyResult saveKeyResult) {
// set data uri as path to keyring
final Uri blobUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(
saveKeyResult.mMasterKeyId);

View File

@@ -0,0 +1,90 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction;
public class CreateKeyYubiKeyBlankFragment extends Fragment {
CreateKeyActivity mCreateKeyActivity;
View mBackButton;
View mNextButton;
/**
* Creates new instance of this fragment
*/
public static CreateKeyYubiKeyBlankFragment newInstance() {
CreateKeyYubiKeyBlankFragment frag = new CreateKeyYubiKeyBlankFragment();
Bundle args = new Bundle();
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_yubi_key_blank_fragment, container, false);
mBackButton = view.findViewById(R.id.create_key_back_button);
mNextButton = view.findViewById(R.id.create_key_next_button);
mBackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (getFragmentManager().getBackStackEntryCount() == 0) {
getActivity().setResult(Activity.RESULT_CANCELED);
getActivity().finish();
} else {
mCreateKeyActivity.loadFragment(null, FragAction.TO_LEFT);
}
}
});
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nextClicked();
}
});
return view;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mCreateKeyActivity = (CreateKeyActivity) getActivity();
}
private void nextClicked() {
mCreateKeyActivity.mUseSmartCardSettings = true;
CreateKeyNameFragment frag = CreateKeyNameFragment.newInstance();
mCreateKeyActivity.loadFragment(frag, FragAction.TO_RIGHT);
}
}

View File

@@ -66,7 +66,7 @@ public class CreateKeyYubiKeyImportFragment
private String mKeyserver;
private ArrayList<ParcelableKeyRing> mKeyList;
public static Fragment createInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
public static Fragment newInstance(byte[] scannedFingerprints, byte[] nfcAid, String userId) {
CreateKeyYubiKeyImportFragment frag = new CreateKeyYubiKeyImportFragment();
@@ -97,7 +97,7 @@ public class CreateKeyYubiKeyImportFragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_yubikey_import_fragment, container, false);
View view = inflater.inflate(R.layout.create_yubi_key_import_fragment, container, false);
vSerNo = (TextView) view.findViewById(R.id.yubikey_serno);
vUserId = (TextView) view.findViewById(R.id.yubikey_userid);

View File

@@ -35,7 +35,7 @@ public class CreateKeyYubiKeyWaitFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.create_yubikey_wait_fragment, container, false);
View view = inflater.inflate(R.layout.create_yubi_key_wait_fragment, container, false);
mBackButton = view.findViewById(R.id.create_key_back_button);

View File

@@ -120,7 +120,14 @@ public class DecryptActivity extends BaseActivity {
case ACTION_DECRYPT_FROM_CLIPBOARD: {
ClipboardManager clipMan = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if (clipMan == null) {
break;
}
ClipData clip = clipMan.getPrimaryClip();
if (clip == null) {
break;
}
// check if data is available as uri
Uri uri = null;

View File

@@ -29,6 +29,7 @@ import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,6 +38,7 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.Notify.Style;
import org.sufficientlysecure.keychain.ui.util.SubtleAttentionSeeker;
import org.sufficientlysecure.keychain.util.FileHelper;
@@ -92,15 +94,31 @@ public class EncryptDecryptOverviewFragment extends Fragment {
mDecryptFromClipboard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class);
clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
startActivityForResult(clipboardDecrypt, 0);
decryptFromClipboard();
}
});
return view;
}
private void decryptFromClipboard() {
Activity activity = getActivity();
if (activity == null) {
return;
}
final CharSequence clipboardText = ClipboardReflection.getClipboardText(activity);
if (clipboardText == null || TextUtils.isEmpty(clipboardText)) {
Notify.create(activity, R.string.error_clipboard_empty, Style.ERROR).show();
return;
}
Intent clipboardDecrypt = new Intent(getActivity(), DecryptActivity.class);
clipboardDecrypt.setAction(DecryptActivity.ACTION_DECRYPT_FROM_CLIPBOARD);
startActivityForResult(clipboardDecrypt, 0);
}
@Override
public void onResume() {
super.onResume();

View File

@@ -63,7 +63,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
mServiceIntent = data.getParcelable(EXTRA_SERVICE_INTENT);
// obtain passphrase for this subkey
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_KEYTOCARD) {
if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD) {
obtainYubiKeyPin(mRequiredInput);
}
}
@@ -96,7 +96,7 @@ public class NfcOperationActivity extends BaseNfcActivity {
}
break;
}
case NFC_KEYTOCARD: {
case NFC_MOVE_KEY_TO_CARD: {
ProviderHelper providerHelper = new ProviderHelper(this);
CanonicalizedSecretKeyRing secretKeyRing;
try {

View File

@@ -1,4 +1,5 @@
/*
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
*
* This program is free software: you can redistribute it and/or modify
@@ -17,14 +18,11 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.v7.widget.Toolbar;
@@ -32,15 +30,15 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
import org.sufficientlysecure.keychain.util.Preferences;
import java.util.List;
public class SettingsActivity extends PreferenceActivity {
public class SettingsActivity extends AppCompatPreferenceActivity {
public static final String ACTION_PREFS_CLOUD = "org.sufficientlysecure.keychain.ui.PREFS_CLOUD";
public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
@@ -91,22 +89,6 @@ public class SettingsActivity extends PreferenceActivity {
initializePassphraseCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
int[] valueIds = new int[]{
CompressionAlgorithmTags.UNCOMPRESSED,
CompressionAlgorithmTags.ZIP,
CompressionAlgorithmTags.ZLIB,
CompressionAlgorithmTags.BZIP2,
};
String[] entries = new String[]{
getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
"ZIP (" + getString(R.string.compression_fast) + ")",
"ZLIB (" + getString(R.string.compression_fast) + ")",
"BZIP2 (" + getString(R.string.compression_very_slow) + ")",};
String[] values = new String[valueIds.length];
for (int i = 0; i < values.length; ++i) {
values[i] = "" + valueIds[i];
}
initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
@@ -122,13 +104,14 @@ public class SettingsActivity extends PreferenceActivity {
private void setupToolbar() {
ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
LinearLayout content = (LinearLayout) root.getChildAt(0);
LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar_activity, null);
LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.preference_toolbar, null);
root.removeAllViews();
toolbarContainer.addView(content);
root.addView(toolbarContainer);
Toolbar toolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar);
toolbar.setTitle(R.string.title_preferences);
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@@ -168,7 +151,7 @@ public class SettingsActivity extends PreferenceActivity {
}
/**
* This fragment shows the Cloud Search preferences in android 3.0+
* This fragment shows the Cloud Search preferences
*/
public static class CloudSearchPrefsFragment extends PreferenceFragment {
@@ -226,7 +209,7 @@ public class SettingsActivity extends PreferenceActivity {
}
/**
* This fragment shows the advanced preferences in android 3.0+
* This fragment shows the PIN/password preferences
*/
public static class AdvancedPrefsFragment extends PreferenceFragment {
@@ -243,25 +226,6 @@ public class SettingsActivity extends PreferenceActivity {
initializePassphraseCacheTtl(
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
int[] valueIds = new int[]{
CompressionAlgorithmTags.UNCOMPRESSED,
CompressionAlgorithmTags.ZIP,
CompressionAlgorithmTags.ZLIB,
CompressionAlgorithmTags.BZIP2,
};
String[] entries = new String[]{
getString(R.string.choice_none) + " (" + getString(R.string.compression_fast) + ")",
"ZIP (" + getString(R.string.compression_fast) + ")",
"ZLIB (" + getString(R.string.compression_fast) + ")",
"BZIP2 (" + getString(R.string.compression_very_slow) + ")",
};
String[] values = new String[valueIds.length];
for (int i = 0; i < values.length; ++i) {
values[i] = "" + valueIds[i];
}
initializeUseDefaultYubiKeyPin(
(CheckBoxPreference) findPreference(Constants.Pref.USE_DEFAULT_YUBIKEY_PIN));
@@ -270,7 +234,6 @@ public class SettingsActivity extends PreferenceActivity {
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
protected boolean isValidFragment(String fragmentName) {
return AdvancedPrefsFragment.class.getName().equals(fragmentName)
|| CloudSearchPrefsFragment.class.getName().equals(fragmentName)

View File

@@ -53,13 +53,15 @@ public class ViewKeyAdvActivity extends BaseActivity implements
protected Uri mDataUri;
public static final String EXTRA_SELECTED_TAB = "selected_tab";
public static final int TAB_MAIN = 0;
public static final int TAB_SHARE = 1;
public static final int TAB_SHARE = 0;
public static final int TAB_IDENTITIES = 1;
public static final int TAB_SUBKEYS = 2;
public static final int TAB_CERTS = 3;
public static final int TAB_KEYBASE = 4;
// view
private ViewPager mViewPager;
private PagerSlidingTabStrip mSlidingTabLayout;
private PagerTabStripAdapter mTabsAdapter;
private static final int LOADER_ID_UNIFIED = 0;
@@ -80,11 +82,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements
mViewPager = (ViewPager) findViewById(R.id.pager);
mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.sliding_tab_layout);
int switchToTab = TAB_MAIN;
Intent intent = getIntent();
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
}
int switchToTab = intent.getIntExtra(EXTRA_SELECTED_TAB, TAB_SHARE);
mDataUri = getIntent().getData();
if (mDataUri == null) {
@@ -102,8 +101,6 @@ public class ViewKeyAdvActivity extends BaseActivity implements
}
}
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
@@ -120,32 +117,32 @@ public class ViewKeyAdvActivity extends BaseActivity implements
}
private void initTabs(Uri dataUri) {
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
PagerTabStripAdapter adapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(adapter);
Bundle shareBundle = new Bundle();
shareBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvShareFragment.class,
adapter.addTab(ViewKeyAdvShareFragment.class,
shareBundle, getString(R.string.key_view_tab_share));
Bundle userIdsBundle = new Bundle();
userIdsBundle.putParcelable(ViewKeyAdvUserIdsFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvUserIdsFragment.class,
adapter.addTab(ViewKeyAdvUserIdsFragment.class,
userIdsBundle, getString(R.string.section_user_ids));
Bundle keysBundle = new Bundle();
keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvSubkeysFragment.class,
adapter.addTab(ViewKeyAdvSubkeysFragment.class,
keysBundle, getString(R.string.key_view_tab_keys));
Bundle certsBundle = new Bundle();
certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class,
adapter.addTab(ViewKeyAdvCertsFragment.class,
certsBundle, getString(R.string.key_view_tab_certs));
Bundle trustBundle = new Bundle();
trustBundle.putParcelable(ViewKeyTrustFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyTrustFragment.class,
adapter.addTab(ViewKeyTrustFragment.class,
trustBundle, getString(R.string.key_view_tab_keybase));
// update layout after operations
@@ -185,11 +182,8 @@ public class ViewKeyAdvActivity extends BaseActivity implements
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
if (data == null || data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the

View File

@@ -17,6 +17,13 @@
package org.sufficientlysecure.keychain.ui;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Intent;
import android.database.Cursor;
@@ -43,12 +50,10 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.TemporaryStorageProvider;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -57,57 +62,35 @@ import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.NfcHelper;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.io.FileNotFoundException;
public class ViewKeyAdvShareFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private TextView mFingerprint;
private ImageView mQrCode;
private CardView mQrCodeLayout;
private View mFingerprintShareButton;
private View mFingerprintClipboardButton;
private View mKeyShareButton;
private View mKeyClipboardButton;
private View mKeyNfcButton;
private ImageButton mKeySafeSlingerButton;
private View mKeyUploadButton;
private TextView mFingerprintView;
ProviderHelper mProviderHelper;
NfcHelper mNfcHelper;
private static final int LOADER_ID_UNIFIED = 0;
private Uri mDataUri;
private byte[] mFingerprint;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer());
mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
mNfcHelper = new NfcHelper(getActivity(), mProviderHelper);
ProviderHelper providerHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
mNfcHelper = new NfcHelper(getActivity(), providerHelper);
mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
mFingerprintView = (TextView) view.findViewById(R.id.view_key_fingerprint);
mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code);
mQrCodeLayout = (CardView) view.findViewById(R.id.view_key_qr_code_layout);
mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
mKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc);
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
mKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
mKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -115,50 +98,60 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
});
mFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
View vFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
View vFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
View vKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
View vKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc);
View vKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
ImageButton vKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger);
View vKeyUploadButton = view.findViewById(R.id.view_key_action_upload);
vKeySafeSlingerButton.setColorFilter(getResources().getColor(R.color.tertiary_text_light),
PorterDuff.Mode.SRC_IN);
vFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, true, false);
share(true, false);
}
});
mFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() {
vFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, true, true);
share(true, true);
}
});
mKeyShareButton.setOnClickListener(new View.OnClickListener() {
vKeyShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, false, false);
share(false, false);
}
});
mKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
vKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, false, true);
share(false, true);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mKeyNfcButton.setVisibility(View.VISIBLE);
mKeyNfcButton.setOnClickListener(new View.OnClickListener() {
vKeyNfcButton.setVisibility(View.VISIBLE);
vKeyNfcButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mNfcHelper.invokeNfcBeam();
}
});
} else {
mKeyNfcButton.setVisibility(View.GONE);
vKeyNfcButton.setVisibility(View.GONE);
}
mKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
vKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startSafeSlinger(mDataUri);
}
});
mKeyUploadButton.setOnClickListener(new View.OnClickListener() {
vKeyUploadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
uploadToKeyserver();
@@ -182,97 +175,79 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
startActivityForResult(safeSlingerIntent, 0);
}
private void share(Uri dataUri, ProviderHelper providerHelper, boolean fingerprintOnly,
boolean toClipboard) {
private void share(boolean fingerprintOnly, boolean toClipboard) {
Activity activity = getActivity();
if (activity == null || mFingerprint == null) {
return;
}
ProviderHelper providerHelper = new ProviderHelper(activity);
try {
String content;
byte[] fingerprintData = (byte[]) providerHelper.getGenericData(
KeyRings.buildUnifiedKeyRingUri(dataUri),
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
if (fingerprintOnly) {
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintData);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(mFingerprint);
if (!toClipboard) {
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
} else {
content = fingerprint;
}
} else {
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
// get public keyring as ascii armored string
content = providerHelper.getKeyRingAsArmoredString(uri);
content = providerHelper.getKeyRingAsArmoredString(
KeychainContract.KeyRingData.buildPublicKeyRingUri(mDataUri));
}
if (toClipboard) {
ClipboardReflection.copyToClipboard(getActivity(), content);
String message;
if (fingerprintOnly) {
message = getResources().getString(R.string.fingerprint_copied_to_clipboard);
} else {
message = getResources().getString(R.string.key_copied_to_clipboard);
}
Notify.create(getActivity(), message, Notify.Style.OK).show();
} else {
// Android will fail with android.os.TransactionTooLargeException if key is too big
// see http://www.lonestarprod.com/?p=34
if (content.length() >= 86389) {
Notify.create(getActivity(), R.string.key_too_big_for_sharing,
Notify.Style.ERROR).show();
return;
}
// let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, content);
sendIntent.setType("text/plain");
String title;
if (fingerprintOnly) {
title = getResources().getString(R.string.title_share_fingerprint_with);
} else {
title = getResources().getString(R.string.title_share_key);
}
Intent shareChooser = Intent.createChooser(sendIntent, title);
// Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML
// Add replacement extra to send a text/plain file instead.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
String primaryUserId = UncachedKeyRing.decodeFromData(content.getBytes()).
getPublicKey().getPrimaryUserIdWithFallback();
TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider();
Uri contentUri = TemporaryStorageProvider.createFile(getActivity(),
primaryUserId + Constants.FILE_EXTENSION_ASC);
BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter(
new ParcelFileDescriptor.AutoCloseOutputStream(
shareFileProv.openFile(contentUri, "w"))));
contentWriter.write(content);
contentWriter.close();
// create replacement extras inside try{}:
// if file creation fails, just don't add the replacements
Bundle replacements = new Bundle();
shareChooser.putExtra(Intent.EXTRA_REPLACEMENT_EXTRAS, replacements);
Bundle bluetoothExtra = new Bundle(sendIntent.getExtras());
replacements.putBundle("com.android.bluetooth", bluetoothExtra);
bluetoothExtra.putParcelable(Intent.EXTRA_STREAM, contentUri);
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e);
Notify.create(getActivity(), R.string.error_bluetooth_file, Notify.Style.ERROR).show();
}
}
startActivity(shareChooser);
ClipboardReflection.copyToClipboard(activity, content);
Notify.create(activity, fingerprintOnly ? R.string.fingerprint_copied_to_clipboard
: R.string.key_copied_to_clipboard, Notify.Style.OK).show();
return;
}
// Android will fail with android.os.TransactionTooLargeException if key is too big
// see http://www.lonestarprod.com/?p=34
if (content.length() >= 86389) {
Notify.create(activity, R.string.key_too_big_for_sharing, Notify.Style.ERROR).show();
return;
}
// let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, content);
sendIntent.setType("text/plain");
// Bluetooth Share will convert text/plain sent via EXTRA_TEXT to HTML
// Add replacement extra to send a text/plain file instead.
try {
TemporaryStorageProvider shareFileProv = new TemporaryStorageProvider();
Uri contentUri = TemporaryStorageProvider.createFile(activity,
KeyFormattingUtils.convertFingerprintToHex(mFingerprint) + Constants.FILE_EXTENSION_ASC);
BufferedWriter contentWriter = new BufferedWriter(new OutputStreamWriter(
new ParcelFileDescriptor.AutoCloseOutputStream(
shareFileProv.openFile(contentUri, "w"))));
contentWriter.write(content);
contentWriter.close();
sendIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
} catch (FileNotFoundException e) {
Log.e(Constants.TAG, "error creating temporary Bluetooth key share file!", e);
// no need for a snackbar because one sharing option doesn't work
// Notify.create(getActivity(), R.string.error_temp_file, Notify.Style.ERROR).show();
}
String title = getString(fingerprintOnly
? R.string.title_share_fingerprint_with : R.string.title_share_key);
Intent shareChooser = Intent.createChooser(sendIntent, title);
startActivity(shareChooser);
} catch (PgpGeneralException | IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
Notify.create(getActivity(), R.string.error_key_processing, Notify.Style.ERROR).show();
Notify.create(activity, R.string.error_key_processing, Notify.Style.ERROR).show();
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
Notify.create(getActivity(), R.string.error_key_not_found, Notify.Style.ERROR).show();
Notify.create(activity, R.string.error_key_not_found, Notify.Style.ERROR).show();
}
}
@@ -293,8 +268,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
@@ -309,8 +284,6 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
@@ -320,19 +293,10 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
static final String[] UNIFIED_PROJECTION = new String[] {
KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET,
KeyRings.USER_ID, KeyRings.FINGERPRINT,
KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.IS_EXPIRED,
KeyRings._ID, KeyRings.FINGERPRINT
};
static final int INDEX_UNIFIED_MASTER_KEY_ID = 1;
static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2;
static final int INDEX_UNIFIED_USER_ID = 3;
static final int INDEX_UNIFIED_FINGERPRINT = 4;
static final int INDEX_UNIFIED_ALGORITHM = 5;
static final int INDEX_UNIFIED_KEY_SIZE = 6;
static final int INDEX_UNIFIED_CREATION = 7;
static final int INDEX_UNIFIED_ID_EXPIRED = 8;
static final int INDEX_UNIFIED_FINGERPRINT = 1;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
@@ -348,11 +312,8 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
if (data == null || data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
@@ -362,10 +323,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
if (data.moveToFirst()) {
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
loadQrCode(fingerprint);
setFingerprint(fingerprintBlob);
break;
}
@@ -380,14 +338,16 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements
* We need to make sure we are no longer using it.
*/
public void onLoaderReset(Loader<Cursor> loader) {
mFingerprint = null;
}
/**
* Load QR Code asynchronously and with a fade in animation
*
* @param fingerprint
*/
private void loadQrCode(final String fingerprint) {
/** Load QR Code asynchronously and with a fade in animation */
private void setFingerprint(byte[] fingerprintBlob) {
mFingerprint = fingerprintBlob;
final String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
mFingerprintView.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
AsyncTask<Void, Void, Bitmap> loadTask =
new AsyncTask<Void, Void, Bitmap>() {
protected Bitmap doInBackground(Void... unused) {

View File

@@ -94,7 +94,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
public void onNewIntent(Intent intent) {
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
try {
handleNdefDiscoveredIntent(intent);
handleTagDiscoveredIntent(intent);
} catch (CardException e) {
handleNfcError(e);
} catch (IOException e) {
@@ -269,7 +269,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
* This method is called by onNewIntent above upon discovery of an NFC tag.
* It handles initialization and login to the application, subsequently
* calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then
* finishes the activity with an appropiate result.
* finishes the activity with an appropriate result.
*
* On general communication, see also
* http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx
@@ -278,7 +278,7 @@ public abstract class BaseNfcActivity extends BaseActivity {
* on ISO SmartCard Systems specification.
*
*/
protected void handleNdefDiscoveredIntent(Intent intent) throws IOException {
protected void handleTagDiscoveredIntent(Intent intent) throws IOException {
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);

View File

@@ -121,7 +121,7 @@ public class CryptoOperationHelper<T extends Parcelable, S extends OperationResu
Activity activity = mUseFragment ? mFragment.getActivity() : mActivity;
switch (requiredInput.mType) {
case NFC_KEYTOCARD:
case NFC_MOVE_KEY_TO_CARD:
case NFC_DECRYPT:
case NFC_SIGN: {
Intent intent = new Intent(activity, NfcOperationActivity.class);