Merge branch 'master' of github.com:open-keychain/open-keychain
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!");
|
||||
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user