mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-27 11:12:15 -05:00
Merge remote-tracking branch 'origin/master' into certs
A lot of things are completely broken, but it compiles and doesn't crash right away. Good enough for me. Conflicts: OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainContract.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainDatabase.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/KeyListFragment.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyMainFragment.java OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/ViewKeyUserIdsAdapter.java OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml
This commit is contained in:
commit
aa6f5118f5
22
CHANGELOG
22
CHANGELOG
@ -1,3 +1,25 @@
|
||||
2.5
|
||||
* fix decryption of symmetric pgp messages/files
|
||||
* refactored edit key screen (thanks to Ash Hughes)
|
||||
* new modern design for encrypt/decrypt screens
|
||||
* OpenPGP API version 3 (multiple api accounts, internal fixes, key lookup)
|
||||
|
||||
2.4
|
||||
Thanks to all applicants of Google Summer of Code 2014 who made this release feature rich and bug free!
|
||||
Besides several small patches, a notable number of patches are made by the following people (in alphabetical order):
|
||||
Daniel Hammann, Daniel Haß, Greg Witczak, Miroojin Bakshi, Nikhil Peter Raj, Paul Sarbinowski, Sreeram Boyapati, Vincent Breitmoser.
|
||||
* new unified key list
|
||||
* colorized key fingerprint
|
||||
* support for keyserver ports
|
||||
* deactivate possibility to generate weak keys
|
||||
* much more internal work on the API
|
||||
* certify user ids
|
||||
* keyserver query based on machine-readable output
|
||||
* lock navigation drawer on tablets
|
||||
* suggestions for emails on creation of keys
|
||||
* search in public key lists
|
||||
* and much more improvements and fixes…
|
||||
|
||||
2.3.1
|
||||
* hotfix for crash when upgrading from old versions
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.sufficientlysecure.keychain.demo"
|
||||
android:versionCode="3"
|
||||
android:versionName="2">
|
||||
android:versionCode="4"
|
||||
android:versionName="3">
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="9"
|
||||
|
@ -48,6 +48,7 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
private Button mEncrypt;
|
||||
private Button mSignAndEncrypt;
|
||||
private Button mDecryptAndVerify;
|
||||
private EditText mAccount;
|
||||
|
||||
private OpenPgpServiceConnection mServiceConnection;
|
||||
|
||||
@ -57,8 +58,8 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.openpgp_provider);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
|
||||
@ -68,6 +69,7 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
mEncrypt = (Button) findViewById(R.id.crypto_provider_demo_encrypt);
|
||||
mSignAndEncrypt = (Button) findViewById(R.id.crypto_provider_demo_sign_and_encrypt);
|
||||
mDecryptAndVerify = (Button) findViewById(R.id.crypto_provider_demo_decrypt_and_verify);
|
||||
mAccount = (EditText) findViewById(R.id.crypto_provider_demo_account);
|
||||
|
||||
mSign.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -142,7 +144,7 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
private InputStream getInputstream(boolean ciphertext) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
String inputStr = null;
|
||||
String inputStr;
|
||||
if (ciphertext) {
|
||||
inputStr = mCiphertext.getText().toString();
|
||||
} else {
|
||||
@ -169,7 +171,7 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onReturn(Intent result) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
||||
try {
|
||||
Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length
|
||||
@ -213,9 +215,10 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
public void sign(Intent data) {
|
||||
data.setAction(OpenPgpApi.ACTION_SIGN);
|
||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||
|
||||
InputStream is = getInputstream(false);
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN));
|
||||
@ -225,9 +228,10 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
data.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
|
||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||
|
||||
InputStream is = getInputstream(false);
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_ENCRYPT));
|
||||
@ -237,9 +241,10 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
data.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
|
||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||
|
||||
InputStream is = getInputstream(false);
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN_AND_ENCRYPT));
|
||||
@ -248,9 +253,10 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
public void decryptAndVerify(Intent data) {
|
||||
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||
|
||||
InputStream is = getInputstream(true);
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||
api.executeApiAsync(data, is, os, new MyCallback(false, os, REQUEST_CODE_DECRYPT_AND_VERIFY));
|
||||
@ -264,13 +270,11 @@ public class OpenPgpProviderActivity extends Activity {
|
||||
// try again after user interaction
|
||||
if (resultCode == RESULT_OK) {
|
||||
/*
|
||||
* The data originally given to the pgp method are are again
|
||||
* returned here to be used when calling again after user interaction.
|
||||
*
|
||||
* They also contain results from the user interaction which happened,
|
||||
* for example selected key ids.
|
||||
* The data originally given to one of the methods above, is again
|
||||
* returned here to be used when calling the method again after user
|
||||
* interaction. The Intent now also contains results from the user
|
||||
* interaction, for example selected key ids.
|
||||
*/
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_SIGN: {
|
||||
sign(data);
|
||||
|
@ -46,6 +46,7 @@
|
||||
android:scrollHorizontally="true"
|
||||
android:scrollbars="vertical"
|
||||
android:text="message"
|
||||
android:hint="cleartext message"
|
||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||
</ScrollView>
|
||||
|
||||
@ -66,6 +67,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="ciphertext"
|
||||
android:hint="ciphertext"
|
||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||
</ScrollView>
|
||||
|
||||
@ -104,5 +106,18 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Decrypt and Verify" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Account ID:"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/textView" />
|
||||
|
||||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Alice <alice@example.com>"
|
||||
android:id="@+id/crypto_provider_demo_account" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -19,12 +19,22 @@ package org.openintents.openpgp;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Parcelable versioning has been copied from Dashclock Widget
|
||||
* https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
|
||||
*/
|
||||
public class OpenPgpError implements Parcelable {
|
||||
public static final int CLIENT_SIDE_ERROR = -1;
|
||||
/**
|
||||
* Since there might be a case where new versions of the client using the library getting
|
||||
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||
* system for the parcels sent between the clients and the providers.
|
||||
*/
|
||||
public static final int PARCELABLE_VERSION = 1;
|
||||
|
||||
// possible values for errorId
|
||||
public static final int CLIENT_SIDE_ERROR = -1;
|
||||
public static final int GENERIC_ERROR = 0;
|
||||
public static final int INCOMPATIBLE_API_VERSIONS = 1;
|
||||
|
||||
public static final int NO_OR_WRONG_PASSPHRASE = 2;
|
||||
public static final int NO_USER_IDS = 3;
|
||||
|
||||
@ -65,15 +75,39 @@ public class OpenPgpError implements Parcelable {
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
/**
|
||||
* NOTE: When adding fields in the process of updating this API, make sure to bump
|
||||
* {@link #PARCELABLE_VERSION}.
|
||||
*/
|
||||
dest.writeInt(PARCELABLE_VERSION);
|
||||
// Inject a placeholder that will store the parcel size from this point on
|
||||
// (not including the size itself).
|
||||
int sizePosition = dest.dataPosition();
|
||||
dest.writeInt(0);
|
||||
int startPosition = dest.dataPosition();
|
||||
// version 1
|
||||
dest.writeInt(errorId);
|
||||
dest.writeString(message);
|
||||
// Go back and write the size
|
||||
int parcelableSize = dest.dataPosition() - startPosition;
|
||||
dest.setDataPosition(sizePosition);
|
||||
dest.writeInt(parcelableSize);
|
||||
dest.setDataPosition(startPosition + parcelableSize);
|
||||
}
|
||||
|
||||
public static final Creator<OpenPgpError> CREATOR = new Creator<OpenPgpError>() {
|
||||
public OpenPgpError createFromParcel(final Parcel source) {
|
||||
int parcelableVersion = source.readInt();
|
||||
int parcelableSize = source.readInt();
|
||||
int startPosition = source.dataPosition();
|
||||
|
||||
OpenPgpError error = new OpenPgpError();
|
||||
error.errorId = source.readInt();
|
||||
error.message = source.readString();
|
||||
|
||||
// skip over all fields added in future versions of this parcel
|
||||
source.setDataPosition(startPosition + parcelableSize);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,18 @@ package org.openintents.openpgp;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Parcelable versioning has been copied from Dashclock Widget
|
||||
* https://code.google.com/p/dashclock/source/browse/api/src/main/java/com/google/android/apps/dashclock/api/ExtensionData.java
|
||||
*/
|
||||
public class OpenPgpSignatureResult implements Parcelable {
|
||||
/**
|
||||
* Since there might be a case where new versions of the client using the library getting
|
||||
* old versions of the protocol (and thus old versions of this class), we need a versioning
|
||||
* system for the parcels sent between the clients and the providers.
|
||||
*/
|
||||
public static final int PARCELABLE_VERSION = 1;
|
||||
|
||||
// generic error on signature verification
|
||||
public static final int SIGNATURE_ERROR = 0;
|
||||
// successfully verified signature, with certified public key
|
||||
@ -90,19 +101,43 @@ public class OpenPgpSignatureResult implements Parcelable {
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
/**
|
||||
* NOTE: When adding fields in the process of updating this API, make sure to bump
|
||||
* {@link #PARCELABLE_VERSION}.
|
||||
*/
|
||||
dest.writeInt(PARCELABLE_VERSION);
|
||||
// Inject a placeholder that will store the parcel size from this point on
|
||||
// (not including the size itself).
|
||||
int sizePosition = dest.dataPosition();
|
||||
dest.writeInt(0);
|
||||
int startPosition = dest.dataPosition();
|
||||
// version 1
|
||||
dest.writeInt(status);
|
||||
dest.writeByte((byte) (signatureOnly ? 1 : 0));
|
||||
dest.writeString(userId);
|
||||
dest.writeLong(keyId);
|
||||
// Go back and write the size
|
||||
int parcelableSize = dest.dataPosition() - startPosition;
|
||||
dest.setDataPosition(sizePosition);
|
||||
dest.writeInt(parcelableSize);
|
||||
dest.setDataPosition(startPosition + parcelableSize);
|
||||
}
|
||||
|
||||
public static final Creator<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() {
|
||||
public OpenPgpSignatureResult createFromParcel(final Parcel source) {
|
||||
int parcelableVersion = source.readInt();
|
||||
int parcelableSize = source.readInt();
|
||||
int startPosition = source.dataPosition();
|
||||
|
||||
OpenPgpSignatureResult vr = new OpenPgpSignatureResult();
|
||||
vr.status = source.readInt();
|
||||
vr.signatureOnly = source.readByte() == 1;
|
||||
vr.userId = source.readString();
|
||||
vr.keyId = source.readLong();
|
||||
|
||||
// skip over all fields added in future versions of this parcel
|
||||
source.setDataPosition(startPosition + parcelableSize);
|
||||
|
||||
return vr;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ public class OpenPgpApi {
|
||||
|
||||
public static final String TAG = "OpenPgp API";
|
||||
|
||||
public static final int API_VERSION = 2;
|
||||
public static final int API_VERSION = 3;
|
||||
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
|
||||
|
||||
/**
|
||||
@ -126,6 +126,8 @@ public class OpenPgpApi {
|
||||
/* Intent extras */
|
||||
public static final String EXTRA_API_VERSION = "api_version";
|
||||
|
||||
public static final String EXTRA_ACCOUNT_NAME = "account_name";
|
||||
|
||||
// SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
|
||||
// request ASCII Armor for output
|
||||
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
|
||||
|
@ -25,34 +25,64 @@ import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class OpenPgpServiceConnection {
|
||||
|
||||
// interface to create callbacks for onServiceConnected
|
||||
public interface OnBound {
|
||||
public void onBound(IOpenPgpService service);
|
||||
}
|
||||
|
||||
private Context mApplicationContext;
|
||||
|
||||
private boolean mBound;
|
||||
private IOpenPgpService mService;
|
||||
private String mProviderPackageName;
|
||||
|
||||
private OnBound mOnBoundListener;
|
||||
|
||||
/**
|
||||
* Create new OpenPgpServiceConnection
|
||||
*
|
||||
* @param context
|
||||
* @param providerPackageName specify package name of OpenPGP provider,
|
||||
* e.g., "org.sufficientlysecure.keychain"
|
||||
*/
|
||||
public OpenPgpServiceConnection(Context context, String providerPackageName) {
|
||||
this.mApplicationContext = context.getApplicationContext();
|
||||
this.mProviderPackageName = providerPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new OpenPgpServiceConnection
|
||||
*
|
||||
* @param context
|
||||
* @param providerPackageName specify package name of OpenPGP provider,
|
||||
* e.g., "org.sufficientlysecure.keychain"
|
||||
* @param onBoundListener callback, executed when connection to service has been established
|
||||
*/
|
||||
public OpenPgpServiceConnection(Context context, String providerPackageName,
|
||||
OnBound onBoundListener) {
|
||||
this.mApplicationContext = context.getApplicationContext();
|
||||
this.mProviderPackageName = providerPackageName;
|
||||
this.mOnBoundListener = onBoundListener;
|
||||
}
|
||||
|
||||
public IOpenPgpService getService() {
|
||||
return mService;
|
||||
}
|
||||
|
||||
public boolean isBound() {
|
||||
return mBound;
|
||||
return (mService != null);
|
||||
}
|
||||
|
||||
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mService = IOpenPgpService.Stub.asInterface(service);
|
||||
mBound = true;
|
||||
if (mOnBoundListener != null) {
|
||||
mOnBoundListener.onBound(mService);
|
||||
}
|
||||
}
|
||||
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mService = null;
|
||||
mBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
@ -63,7 +93,7 @@ public class OpenPgpServiceConnection {
|
||||
*/
|
||||
public boolean bindToService() {
|
||||
// if not already bound...
|
||||
if (mService == null && !mBound) {
|
||||
if (mService == null) {
|
||||
try {
|
||||
Intent serviceIntent = new Intent();
|
||||
serviceIntent.setAction(IOpenPgpService.class.getName());
|
||||
|
@ -1,5 +1,12 @@
|
||||
apply plugin: 'android'
|
||||
|
||||
sourceSets {
|
||||
testLocal {
|
||||
java.srcDir file('src/test/java')
|
||||
resources.srcDir file('src/test/resources')
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-v4:19.0.1'
|
||||
compile 'com.android.support:appcompat-v7:19.0.1'
|
||||
@ -15,6 +22,25 @@ dependencies {
|
||||
compile project(':libraries:spongycastle:pkix')
|
||||
compile project(':libraries:spongycastle:prov')
|
||||
compile project(':libraries:Android-AppMsg:library')
|
||||
|
||||
// Dependencies for the `testLocal` task, make sure to list all your global dependencies here as well
|
||||
testLocalCompile 'junit:junit:4.11'
|
||||
testLocalCompile 'org.robolectric:robolectric:2.1.+'
|
||||
testLocalCompile 'com.google.android:android:4.1.1.4'
|
||||
testLocalCompile 'com.android.support:support-v4:19.0.1'
|
||||
testLocalCompile 'com.android.support:appcompat-v7:19.0.1'
|
||||
testLocalCompile project(':OpenPGP-Keychain-API:libraries:openpgp-api-library')
|
||||
testLocalCompile project(':OpenPGP-Keychain-API:libraries:openkeychain-api-library')
|
||||
testLocalCompile project(':libraries:HtmlTextView')
|
||||
testLocalCompile project(':libraries:StickyListHeaders:library')
|
||||
testLocalCompile project(':libraries:AndroidBootstrap')
|
||||
testLocalCompile project(':libraries:zxing')
|
||||
testLocalCompile project(':libraries:zxing-android-integration')
|
||||
testLocalCompile project(':libraries:spongycastle:core')
|
||||
testLocalCompile project(':libraries:spongycastle:pg')
|
||||
testLocalCompile project(':libraries:spongycastle:pkix')
|
||||
testLocalCompile project(':libraries:spongycastle:prov')
|
||||
testLocalCompile project(':libraries:Android-AppMsg:library')
|
||||
}
|
||||
|
||||
android {
|
||||
@ -61,3 +87,19 @@ android {
|
||||
htmlOutput file("lint-report.html")
|
||||
}
|
||||
}
|
||||
|
||||
task localTest(type: Test, dependsOn: assemble) {
|
||||
testClassesDir = sourceSets.testLocal.output.classesDir
|
||||
|
||||
android.sourceSets.main.java.srcDirs.each { dir ->
|
||||
def buildDir = dir.getAbsolutePath().split('/')
|
||||
buildDir = (buildDir[0..(buildDir.length - 4)] + ['build', 'classes', 'debug']).join('/')
|
||||
|
||||
sourceSets.testLocal.compileClasspath += files(buildDir)
|
||||
sourceSets.testLocal.runtimeClasspath += files(buildDir)
|
||||
}
|
||||
|
||||
classpath = sourceSets.testLocal.runtimeClasspath
|
||||
}
|
||||
|
||||
//check.dependsOn localTest
|
||||
|
@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.sufficientlysecure.keychain"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="23104"
|
||||
android:versionName="2.3.1 beta4">
|
||||
android:versionCode="25000"
|
||||
android:versionName="2.5">
|
||||
|
||||
<!--
|
||||
General remarks
|
||||
@ -50,7 +50,7 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
|
||||
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
|
||||
<application
|
||||
@ -107,14 +107,12 @@
|
||||
android:name=".ui.SelectPublicKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_select_recipients"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
android:launchMode="singleTop"></activity>
|
||||
<activity
|
||||
android:name=".ui.SelectSecretKeyActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_select_secret_key"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
android:launchMode="singleTop"></activity>
|
||||
<activity
|
||||
android:name=".ui.EncryptActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
@ -153,23 +151,23 @@
|
||||
|
||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-signature" />-->
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-signature" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
<!--<action android:name="android.intent.action.VIEW" />-->
|
||||
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
<!--<category android:name="android.intent.category.BROWSABLE" />-->
|
||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!-- Keychain's own Actions -->
|
||||
<!-- DECRYPT with text as extra -->
|
||||
@ -245,7 +243,7 @@
|
||||
<activity
|
||||
android:name=".ui.PreferencesActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:label="@string/title_preferences" >
|
||||
android:label="@string/title_preferences">
|
||||
<intent-filter>
|
||||
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@ -383,44 +381,49 @@
|
||||
android:exported="false"
|
||||
android:process=":passphrase_cache" />
|
||||
<service
|
||||
android:name="org.sufficientlysecure.keychain.service.KeychainIntentService"
|
||||
android:name=".service.KeychainIntentService"
|
||||
android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="org.sufficientlysecure.keychain.provider.KeychainProvider"
|
||||
android:name=".provider.KeychainProvider"
|
||||
android:authorities="org.sufficientlysecure.keychain.provider"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- Internal classes of the remote APIs (not exported) -->
|
||||
<activity
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
|
||||
android:name=".remote.ui.RemoteServiceActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name" />
|
||||
<!--android:launchMode="singleTop"-->
|
||||
<!--android:process=":remote_api"-->
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop"
|
||||
android:process=":remote_api" />
|
||||
<activity
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity"
|
||||
android:name=".remote.ui.AppsListActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:exported="false"
|
||||
android:label="@string/title_api_registered_apps" />
|
||||
<activity
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.AppSettingsActivity"
|
||||
android:name=".remote.ui.AppSettingsActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:exported="false">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".remote.ui.AppsListActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".remote.ui.AccountSettingsActivity"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- OpenPGP Remote API -->
|
||||
<service
|
||||
android:name="org.sufficientlysecure.keychain.service.remote.OpenPgpService"
|
||||
android:name=".remote.OpenPgpService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:process=":remote_api">
|
||||
<intent-filter>
|
||||
<action android:name="org.openintents.openpgp.IOpenPgpService" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="api_version"
|
||||
android:value="1" />
|
||||
</service>
|
||||
|
||||
<!-- Extended Remote API -->
|
||||
|
@ -19,6 +19,11 @@ package org.sufficientlysecure.keychain;
|
||||
import android.os.Environment;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.sufficientlysecure.keychain.remote.ui.AppsListActivity;
|
||||
import org.sufficientlysecure.keychain.ui.DecryptActivity;
|
||||
import org.sufficientlysecure.keychain.ui.EncryptActivity;
|
||||
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
|
||||
import org.sufficientlysecure.keychain.ui.KeyListActivity;
|
||||
|
||||
public final class Constants {
|
||||
|
||||
@ -42,7 +47,7 @@ public final class Constants {
|
||||
|
||||
public static final class Path {
|
||||
public static final String APP_DIR = Environment.getExternalStorageDirectory()
|
||||
+ "/OpenPGP-Keychain";
|
||||
+ "/OpenKeychain";
|
||||
public static final String APP_DIR_FILE_SEC = APP_DIR + "/secexport.asc";
|
||||
public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.asc";
|
||||
}
|
||||
@ -50,10 +55,10 @@ public final class Constants {
|
||||
public static final class Pref {
|
||||
public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm";
|
||||
public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm";
|
||||
public static final String DEFAULT_ASCII_ARMOUR = "defaultAsciiArmour";
|
||||
public static final String DEFAULT_ASCII_ARMOR = "defaultAsciiArmor";
|
||||
public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression";
|
||||
public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
|
||||
public static final String PASS_PHRASE_CACHE_TTL = "passphraseCacheTtl";
|
||||
public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl";
|
||||
public static final String LANGUAGE = "language";
|
||||
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
|
||||
public static final String KEY_SERVERS = "keyServers";
|
||||
@ -63,4 +68,18 @@ public final class Constants {
|
||||
public static final String KEY_SERVERS = "pool.sks-keyservers.net, subkeys.pgp.net, pgp.mit.edu";
|
||||
}
|
||||
|
||||
public static final class DrawerItems {
|
||||
public static final Class KEY_LIST = KeyListActivity.class;
|
||||
public static final Class ENCRYPT = EncryptActivity.class;
|
||||
public static final Class DECRYPT = DecryptActivity.class;
|
||||
public static final Class IMPORT_KEYS = ImportKeysActivity.class;
|
||||
public static final Class REGISTERED_APPS_LIST = AppsListActivity.class;
|
||||
public static final Class[] ARRAY = new Class[]{
|
||||
KEY_LIST,
|
||||
ENCRYPT,
|
||||
DECRYPT,
|
||||
IMPORT_KEYS,
|
||||
REGISTERED_APPS_LIST
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +119,7 @@ public final class Id {
|
||||
public static final int secret_key = 0x21070002;
|
||||
public static final int user_id = 0x21070003;
|
||||
public static final int key = 0x21070004;
|
||||
public static final int public_secret_key = 0x21070005;
|
||||
}
|
||||
|
||||
public static final class choice {
|
||||
|
@ -17,16 +17,17 @@
|
||||
|
||||
package org.sufficientlysecure.keychain;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
import android.app.Application;
|
||||
import android.os.Environment;
|
||||
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Environment;
|
||||
import java.io.File;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
public class KeychainApplication extends Application {
|
||||
|
||||
@ -40,14 +41,14 @@ public class KeychainApplication extends Application {
|
||||
|
||||
/*
|
||||
* Sets Bouncy (Spongy) Castle as preferred security provider
|
||||
*
|
||||
*
|
||||
* insertProviderAt() position starts from 1
|
||||
*/
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
|
||||
/*
|
||||
* apply RNG fixes
|
||||
*
|
||||
*
|
||||
* among other things, executes Security.insertProviderAt(new
|
||||
* LinuxPRNGSecureRandomProvider(), 1) for Android <= SDK 17
|
||||
*/
|
||||
|
@ -17,20 +17,20 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.compatibility;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ClipboardReflection {
|
||||
|
||||
private static final String clipboardLabel = "Keychain";
|
||||
|
||||
/**
|
||||
* Wrapper around ClipboardManager based on Android version using Reflection API
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
* @param text
|
||||
*/
|
||||
@ -57,7 +57,7 @@ public class ClipboardReflection {
|
||||
|
||||
/**
|
||||
* Wrapper around ClipboardManager based on Android version using Reflection API
|
||||
*
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public static CharSequence getClipboardText(Context context) {
|
||||
|
@ -37,7 +37,6 @@ public class ActionBarHelper {
|
||||
* @param activity
|
||||
*/
|
||||
public static void setBackButton(ActionBarActivity activity) {
|
||||
// set actionbar without home button if called from another app
|
||||
final ActionBar actionBar = activity.getSupportActionBar();
|
||||
Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
|
||||
+ activity.getCallingPackage());
|
||||
|
@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
@ -47,23 +48,21 @@ public class ExportHelper {
|
||||
this.mActivity = activity;
|
||||
}
|
||||
|
||||
public void deleteKey(Uri dataUri, final int keyType, Handler deleteHandler) {
|
||||
long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment());
|
||||
|
||||
public void deleteKey(Uri dataUri, Handler deleteHandler) {
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(deleteHandler);
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(mActivity, dataUri);
|
||||
|
||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
||||
new long[]{keyRingRowId}, keyType);
|
||||
|
||||
new long[]{ masterKeyId });
|
||||
deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog where to export keys
|
||||
*/
|
||||
public void showExportKeysDialog(final long[] rowIds, final int keyType,
|
||||
final String exportFilename) {
|
||||
public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename,
|
||||
final boolean showSecretCheckbox) {
|
||||
mExportFilename = exportFilename;
|
||||
|
||||
// Message is received after file is selected
|
||||
@ -74,7 +73,7 @@ public class ExportHelper {
|
||||
Bundle data = message.getData();
|
||||
mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
|
||||
exportKeys(rowIds, keyType);
|
||||
exportKeys(masterKeyIds, data.getBoolean(FileDialogFragment.MESSAGE_DATA_CHECKED));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -85,7 +84,7 @@ public class ExportHelper {
|
||||
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||
public void run() {
|
||||
String title = null;
|
||||
if (rowIds == null) {
|
||||
if (masterKeyIds == null) {
|
||||
// export all keys
|
||||
title = mActivity.getString(R.string.title_export_keys);
|
||||
} else {
|
||||
@ -93,15 +92,12 @@ public class ExportHelper {
|
||||
title = mActivity.getString(R.string.title_export_key);
|
||||
}
|
||||
|
||||
String message = null;
|
||||
if (keyType == Id.type.public_key) {
|
||||
message = mActivity.getString(R.string.specify_file_to_export_to);
|
||||
} else {
|
||||
message = mActivity.getString(R.string.specify_file_to_export_secret_keys_to);
|
||||
}
|
||||
String message = mActivity.getString(R.string.specify_file_to_export_to);
|
||||
String checkMsg = showSecretCheckbox ?
|
||||
mActivity.getString(R.string.also_export_secret_keys) : null;
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
|
||||
exportFilename, null);
|
||||
exportFilename, checkMsg);
|
||||
|
||||
mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
@ -111,7 +107,7 @@ public class ExportHelper {
|
||||
/**
|
||||
* Export keys
|
||||
*/
|
||||
public void exportKeys(long[] rowIds, int keyType) {
|
||||
public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
|
||||
Log.d(Constants.TAG, "exportKeys started");
|
||||
|
||||
// Send all information needed to service to export key in other thread
|
||||
@ -123,17 +119,17 @@ public class ExportHelper {
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
|
||||
data.putInt(KeychainIntentService.EXPORT_KEY_TYPE, keyType);
|
||||
data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret);
|
||||
|
||||
if (rowIds == null) {
|
||||
if (masterKeyIds == null) {
|
||||
data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
|
||||
} else {
|
||||
data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_ROW_ID, rowIds);
|
||||
data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);
|
||||
}
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after exporting is done in ApgService
|
||||
// Message is received after exporting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity,
|
||||
mActivity.getString(R.string.progress_exporting),
|
||||
ProgressDialog.STYLE_HORIZONTAL,
|
||||
@ -145,7 +141,7 @@ public class ExportHelper {
|
||||
}
|
||||
}) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
@ -17,18 +17,14 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.helper;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.Spanned;
|
||||
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
@ -65,81 +61,10 @@ public class OtherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
|
||||
try {
|
||||
// for each 4 characters of the fingerprint + 1 space
|
||||
for (int i = 0; i < fingerprint.length(); i += 5) {
|
||||
int spanEnd = Math.min(i + 4, fingerprint.length());
|
||||
String fourChars = fingerprint.substring(i, spanEnd);
|
||||
|
||||
int raw = Integer.parseInt(fourChars, 16);
|
||||
byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)};
|
||||
int[] color = OtherHelper.getRgbForData(bytes);
|
||||
int r = color[0];
|
||||
int g = color[1];
|
||||
int b = color[2];
|
||||
|
||||
// we cannot change black by multiplication, so adjust it to an almost-black grey,
|
||||
// which will then be brightened to the minimal brightness level
|
||||
if (r == 0 && g == 0 && b == 0) {
|
||||
r = 1;
|
||||
g = 1;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// Convert rgb to brightness
|
||||
double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
|
||||
// If a color is too dark to be seen on black,
|
||||
// then brighten it up to a minimal brightness.
|
||||
if (brightness < 80) {
|
||||
double factor = 80.0 / brightness;
|
||||
r = Math.min(255, (int) (r * factor));
|
||||
g = Math.min(255, (int) (g * factor));
|
||||
b = Math.min(255, (int) (b * factor));
|
||||
|
||||
// If it is too light, then darken it to a respective maximal brightness.
|
||||
} else if (brightness > 180) {
|
||||
double factor = 180.0 / brightness;
|
||||
r = (int) (r * factor);
|
||||
g = (int) (g * factor);
|
||||
b = (int) (b * factor);
|
||||
}
|
||||
|
||||
// Create a foreground color with the 3 digest integers as RGB
|
||||
// and then converting that int to hex to use as a color
|
||||
sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)),
|
||||
i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Colorization failed", e);
|
||||
// if anything goes wrong, then just display the fingerprint without colour,
|
||||
// instead of partially correct colour or wrong colours
|
||||
return new SpannableStringBuilder(fingerprint);
|
||||
}
|
||||
|
||||
public static SpannableStringBuilder strikeOutText(CharSequence text) {
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||
sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given bytes to a unique RGB color using SHA1 algorithm
|
||||
*
|
||||
* @param bytes
|
||||
* @return an integer array containing 3 numeric color representations (Red, Green, Black)
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws DigestException
|
||||
*/
|
||||
public static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
|
||||
md.update(bytes);
|
||||
byte[] digest = md.digest();
|
||||
|
||||
int[] result = {((int) digest[0] + 256) % 256,
|
||||
((int) digest[1] + 256) % 256,
|
||||
((int) digest[2] + 256) % 256};
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
*
|
||||
* 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
|
||||
@ -30,7 +30,7 @@ import java.util.Vector;
|
||||
* Singleton Implementation of a Preference Helper
|
||||
*/
|
||||
public class Preferences {
|
||||
private static Preferences mPreferences;
|
||||
private static Preferences sPreferences;
|
||||
private SharedPreferences mSharedPreferences;
|
||||
|
||||
public static synchronized Preferences getPreferences(Context context) {
|
||||
@ -38,10 +38,10 @@ public class Preferences {
|
||||
}
|
||||
|
||||
public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
|
||||
if (mPreferences == null || forceNew) {
|
||||
mPreferences = new Preferences(context);
|
||||
if (sPreferences == null || forceNew) {
|
||||
sPreferences = new Preferences(context);
|
||||
}
|
||||
return mPreferences;
|
||||
return sPreferences;
|
||||
}
|
||||
|
||||
private Preferences(Context context) {
|
||||
@ -58,8 +58,8 @@ public class Preferences {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public long getPassPhraseCacheTtl() {
|
||||
int ttl = mSharedPreferences.getInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, 180);
|
||||
public long getPassphraseCacheTtl() {
|
||||
int ttl = mSharedPreferences.getInt(Constants.Pref.PASSPHRASE_CACHE_TTL, 180);
|
||||
// fix the value if it was set to "never" in previous versions, which currently is not
|
||||
// supported
|
||||
if (ttl == 0) {
|
||||
@ -68,9 +68,9 @@ public class Preferences {
|
||||
return (long) ttl;
|
||||
}
|
||||
|
||||
public void setPassPhraseCacheTtl(int value) {
|
||||
public void setPassphraseCacheTtl(int value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, value);
|
||||
editor.putInt(Constants.Pref.PASSPHRASE_CACHE_TTL, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
@ -118,13 +118,13 @@ public class Preferences {
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public boolean getDefaultAsciiArmour() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, false);
|
||||
public boolean getDefaultAsciiArmor() {
|
||||
return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, false);
|
||||
}
|
||||
|
||||
public void setDefaultAsciiArmour(boolean value) {
|
||||
public void setDefaultAsciiArmor(boolean value) {
|
||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||
editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, value);
|
||||
editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
|
@ -61,13 +61,32 @@ public class PgpConversionHelper {
|
||||
* @return
|
||||
*/
|
||||
public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
|
||||
PGPSecretKeyRing keyRing = (PGPSecretKeyRing) BytesToPGPKeyRing(keysBytes);
|
||||
PGPObjectFactory factory = new PGPObjectFactory(keysBytes);
|
||||
Object obj = null;
|
||||
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
|
||||
while (itr.hasNext()) {
|
||||
keys.add(itr.next());
|
||||
try {
|
||||
while ((obj = factory.nextObject()) != null) {
|
||||
PGPSecretKey secKey = null;
|
||||
if (obj instanceof PGPSecretKey) {
|
||||
secKey = (PGPSecretKey) obj;
|
||||
if (secKey == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
keys.add(secKey);
|
||||
} else if (obj instanceof PGPSecretKeyRing) { //master keys are sent as keyrings
|
||||
PGPSecretKeyRing keyRing = null;
|
||||
keyRing = (PGPSecretKeyRing) obj;
|
||||
if (keyRing == null) {
|
||||
Log.e(Constants.TAG, "No keys given!");
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
|
||||
while (itr.hasNext()) {
|
||||
keys.add(itr.next());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
return keys;
|
||||
|
@ -18,28 +18,58 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
||||
import org.spongycastle.openpgp.*;
|
||||
import org.spongycastle.openpgp.PGPCompressedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignature;
|
||||
import org.spongycastle.openpgp.PGPOnePassSignatureList;
|
||||
import org.spongycastle.openpgp.PGPPBEEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureList;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||
import org.spongycastle.openpgp.operator.jcajce.*;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.SignatureException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class uses a Builder pattern!
|
||||
@ -50,9 +80,9 @@ public class PgpDecryptVerify {
|
||||
private OutputStream mOutStream;
|
||||
|
||||
private ProgressDialogUpdater mProgressDialogUpdater;
|
||||
private boolean mAssumeSymmetric;
|
||||
private boolean mAllowSymmetricDecryption;
|
||||
private String mPassphrase;
|
||||
private long mEnforcedKeyId;
|
||||
private Set<Long> mAllowedKeyIds;
|
||||
|
||||
private PgpDecryptVerify(Builder builder) {
|
||||
// private Constructor can only be called from Builder
|
||||
@ -61,9 +91,9 @@ public class PgpDecryptVerify {
|
||||
this.mOutStream = builder.mOutStream;
|
||||
|
||||
this.mProgressDialogUpdater = builder.mProgressDialogUpdater;
|
||||
this.mAssumeSymmetric = builder.mAssumeSymmetric;
|
||||
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
|
||||
this.mPassphrase = builder.mPassphrase;
|
||||
this.mEnforcedKeyId = builder.mEnforcedKeyId;
|
||||
this.mAllowedKeyIds = builder.mAllowedKeyIds;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@ -74,9 +104,9 @@ public class PgpDecryptVerify {
|
||||
|
||||
// optional
|
||||
private ProgressDialogUpdater mProgressDialogUpdater = null;
|
||||
private boolean mAssumeSymmetric = false;
|
||||
private String mPassphrase = "";
|
||||
private long mEnforcedKeyId = 0;
|
||||
private boolean mAllowSymmetricDecryption = true;
|
||||
private String mPassphrase = null;
|
||||
private Set<Long> mAllowedKeyIds = null;
|
||||
|
||||
public Builder(Context context, InputData data, OutputStream outStream) {
|
||||
this.mContext = context;
|
||||
@ -89,8 +119,8 @@ public class PgpDecryptVerify {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder assumeSymmetric(boolean assumeSymmetric) {
|
||||
this.mAssumeSymmetric = assumeSymmetric;
|
||||
public Builder allowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
||||
this.mAllowSymmetricDecryption = allowSymmetricDecryption;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -100,14 +130,14 @@ public class PgpDecryptVerify {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow this key id alone for decryption.
|
||||
* This means only ciphertexts encrypted for this private key can be decrypted.
|
||||
* Allow these key ids alone for decryption.
|
||||
* This means only ciphertexts encrypted for one of these private key can be decrypted.
|
||||
*
|
||||
* @param enforcedKeyId
|
||||
* @param allowedKeyIds
|
||||
* @return
|
||||
*/
|
||||
public Builder enforcedKeyId(long enforcedKeyId) {
|
||||
this.mEnforcedKeyId = enforcedKeyId;
|
||||
public Builder allowedKeyIds(Set<Long> allowedKeyIds) {
|
||||
this.mAllowedKeyIds = allowedKeyIds;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -128,35 +158,6 @@ public class PgpDecryptVerify {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasSymmetricEncryption(Context context, InputStream inputStream)
|
||||
throws PgpGeneralException, IOException {
|
||||
InputStream in = PGPUtil.getDecoderStream(inputStream);
|
||||
PGPObjectFactory pgpF = new PGPObjectFactory(in);
|
||||
PGPEncryptedDataList enc;
|
||||
Object o = pgpF.nextObject();
|
||||
|
||||
// the first object might be a PGP marker packet.
|
||||
if (o instanceof PGPEncryptedDataList) {
|
||||
enc = (PGPEncryptedDataList) o;
|
||||
} else {
|
||||
enc = (PGPEncryptedDataList) pgpF.nextObject();
|
||||
}
|
||||
|
||||
if (enc == null) {
|
||||
throw new PgpGeneralException(context.getString(R.string.error_invalid_data));
|
||||
}
|
||||
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPBEEncryptedData) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts and/or verifies data based on parameters of class
|
||||
*
|
||||
@ -221,25 +222,82 @@ public class PgpDecryptVerify {
|
||||
|
||||
currentProgress += 5;
|
||||
|
||||
// TODO: currently we always only look at the first known key or symmetric encryption,
|
||||
// there might be more...
|
||||
if (mAssumeSymmetric) {
|
||||
PGPPBEEncryptedData pbe = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
// find secret key
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPBEEncryptedData) {
|
||||
pbe = (PGPPBEEncryptedData) obj;
|
||||
break;
|
||||
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||
PGPSecretKey secretKey = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
boolean symmetricPacketFound = false;
|
||||
// find secret key
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||
updateProgress(R.string.progress_finding_key, currentProgress, 100);
|
||||
|
||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(mContext,
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(encData.getKeyID()))
|
||||
);
|
||||
PGPSecretKeyRing secretKeyRing = ProviderHelper.getPGPSecretKeyRing(mContext, masterKeyId);
|
||||
if (secretKeyRing == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
}
|
||||
secretKey = secretKeyRing.getSecretKey(encData.getKeyID());
|
||||
if (secretKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
// secret key exists in database
|
||||
|
||||
if (pbe == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_no_symmetric_encryption_packet));
|
||||
}
|
||||
// allow only a specific key for decryption?
|
||||
if (mAllowedKeyIds != null) {
|
||||
Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID());
|
||||
Log.d(Constants.TAG, "allowedKeyIds: " + mAllowedKeyIds);
|
||||
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
||||
|
||||
if (!mAllowedKeyIds.contains(masterKeyId)) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
}
|
||||
|
||||
encryptedDataAsymmetric = encData;
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (mPassphrase == null) {
|
||||
// returns "" if key has no passphrase
|
||||
mPassphrase =
|
||||
PassphraseCacheService.getCachedPassphrase(mContext, masterKeyId);
|
||||
|
||||
// if passphrase was not cached, return here
|
||||
// indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
returnData.setKeyIdPassphraseNeeded(masterKeyId);
|
||||
returnData.setStatus(PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED);
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
|
||||
// break out of while, only get first object here
|
||||
// TODO???: There could be more pgp objects, which are not decrypted!
|
||||
break;
|
||||
} else if (mAllowSymmetricDecryption && obj instanceof PGPPBEEncryptedData) {
|
||||
symmetricPacketFound = true;
|
||||
|
||||
encryptedDataSymmetric = (PGPPBEEncryptedData) obj;
|
||||
|
||||
// if no passphrase is given, return here
|
||||
// indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
returnData.setStatus(PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED);
|
||||
return returnData;
|
||||
}
|
||||
|
||||
// break out of while, only get first object here
|
||||
// TODO???: There could be more pgp objects, which are not decrypted!
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (symmetricPacketFound) {
|
||||
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||
|
||||
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
||||
@ -248,64 +306,11 @@ public class PgpDecryptVerify {
|
||||
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
mPassphrase.toCharArray());
|
||||
|
||||
clear = pbe.getDataStream(decryptorFactory);
|
||||
clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
||||
|
||||
encryptedData = pbe;
|
||||
encryptedData = encryptedDataSymmetric;
|
||||
currentProgress += 5;
|
||||
} else {
|
||||
updateProgress(R.string.progress_finding_key, currentProgress, 100);
|
||||
|
||||
PGPPublicKeyEncryptedData pbe = null;
|
||||
PGPSecretKey secretKey = null;
|
||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||
// find secret key
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) obj;
|
||||
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(mContext, encData.getKeyID());
|
||||
if (secretKey != null) {
|
||||
// secret key exists in database
|
||||
|
||||
// allow only a specific key for decryption?
|
||||
if (mEnforcedKeyId != 0) {
|
||||
// TODO: improve this code! get master key directly!
|
||||
PGPSecretKeyRing secretKeyRing =
|
||||
ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, encData.getKeyID());
|
||||
long masterKeyId = PgpKeyHelper.getMasterKey(secretKeyRing).getKeyID();
|
||||
Log.d(Constants.TAG, "encData.getKeyID():" + encData.getKeyID());
|
||||
Log.d(Constants.TAG, "enforcedKeyId: " + mEnforcedKeyId);
|
||||
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
|
||||
|
||||
if (mEnforcedKeyId != masterKeyId) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
}
|
||||
|
||||
pbe = encData;
|
||||
|
||||
// if no passphrase was explicitly set try to get it from the cache service
|
||||
if (mPassphrase == null) {
|
||||
// returns "" if key has no passphrase
|
||||
mPassphrase =
|
||||
PassphraseCacheService.getCachedPassphrase(mContext, encData.getKeyID());
|
||||
|
||||
// if passphrase was not cached, return here
|
||||
// indicating that a passphrase is missing!
|
||||
if (mPassphrase == null) {
|
||||
returnData.setKeyPassphraseNeeded(true);
|
||||
return returnData;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (secretKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
@ -331,9 +336,9 @@ public class PgpDecryptVerify {
|
||||
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey);
|
||||
|
||||
clear = pbe.getDataStream(decryptorFactory);
|
||||
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||
|
||||
encryptedData = pbe;
|
||||
encryptedData = encryptedDataAsymmetric;
|
||||
currentProgress += 5;
|
||||
}
|
||||
|
||||
@ -363,7 +368,7 @@ public class PgpDecryptVerify {
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
signature = sigList.get(i);
|
||||
signatureKey = ProviderHelper
|
||||
.getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
|
||||
.getPGPPublicKeyRing(mContext, signature.getKeyID()).getPublicKey();
|
||||
if (signatureKeyId == 0) {
|
||||
signatureKeyId = signature.getKeyID();
|
||||
}
|
||||
@ -373,10 +378,10 @@ public class PgpDecryptVerify {
|
||||
signatureIndex = i;
|
||||
signatureKeyId = signature.getKeyID();
|
||||
String userId = null;
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(
|
||||
mContext, signatureKeyId);
|
||||
if (signKeyRing != null) {
|
||||
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
|
||||
userId = PgpKeyHelper.getMainUserId(signKeyRing.getPublicKey());
|
||||
}
|
||||
signatureResult.setUserId(userId);
|
||||
break;
|
||||
@ -388,7 +393,7 @@ public class PgpDecryptVerify {
|
||||
if (signature != null) {
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
signature.init(contentVerifierBuilderProvider, signatureKey);
|
||||
} else {
|
||||
@ -546,25 +551,27 @@ public class PgpDecryptVerify {
|
||||
long signatureKeyId = 0;
|
||||
PGPPublicKey signatureKey = null;
|
||||
for (int i = 0; i < sigList.size(); ++i) {
|
||||
|
||||
signature = sigList.get(i);
|
||||
signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
|
||||
if (signatureKeyId == 0) {
|
||||
signatureKeyId = signature.getKeyID();
|
||||
signatureKeyId = signature.getKeyID();
|
||||
|
||||
// find data about this subkey
|
||||
HashMap<String, Object> data = ProviderHelper.getGenericData(mContext,
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(signature.getKeyID())),
|
||||
new String[] { KeyRings.MASTER_KEY_ID, KeyRings.USER_ID },
|
||||
new int[] { ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_STRING });
|
||||
// any luck? otherwise, try next.
|
||||
if(data.get(KeyRings.MASTER_KEY_ID) == null) {
|
||||
signature = null;
|
||||
// do NOT reset signatureKeyId, that one is shown when no known one is found!
|
||||
continue;
|
||||
}
|
||||
|
||||
if (signatureKey == null) {
|
||||
signature = null;
|
||||
} else {
|
||||
signatureKeyId = signature.getKeyID();
|
||||
String userId = null;
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext,
|
||||
signatureKeyId);
|
||||
if (signKeyRing != null) {
|
||||
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
|
||||
}
|
||||
signatureResult.setUserId(userId);
|
||||
break;
|
||||
}
|
||||
// this one can't fail now (yay database constraints)
|
||||
signatureKey = ProviderHelper.getPGPPublicKeyRing(mContext, (Long) data.get(KeyRings.MASTER_KEY_ID)).getPublicKey();
|
||||
signatureResult.setUserId((String) data.get(KeyRings.USER_ID));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
signatureResult.setKeyId(signatureKeyId);
|
||||
@ -621,11 +628,11 @@ public class PgpDecryptVerify {
|
||||
long signatureKeyId = signature.getKeyID();
|
||||
boolean validKeyBinding = false;
|
||||
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
|
||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(context,
|
||||
signatureKeyId);
|
||||
PGPPublicKey mKey = null;
|
||||
if (signKeyRing != null) {
|
||||
mKey = PgpKeyHelper.getMasterKey(signKeyRing);
|
||||
mKey = signKeyRing.getPublicKey();
|
||||
}
|
||||
|
||||
if (signature.getKeyID() != mKey.getKeyID()) {
|
||||
@ -685,7 +692,8 @@ public class PgpDecryptVerify {
|
||||
}
|
||||
|
||||
private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts,
|
||||
PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
|
||||
PGPPublicKey masterPublicKey,
|
||||
PGPPublicKey signingPublicKey) {
|
||||
boolean validPrimaryKeyBinding = false;
|
||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||
new JcaPGPContentVerifierBuilderProvider()
|
||||
|
@ -19,27 +19,33 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
|
||||
public class PgpDecryptVerifyResult implements Parcelable {
|
||||
boolean mSymmetricPassphraseNeeded;
|
||||
boolean mKeyPassphraseNeeded;
|
||||
public static final int SUCCESS = 1;
|
||||
public static final int KEY_PASSHRASE_NEEDED = 2;
|
||||
public static final int SYMMETRIC_PASSHRASE_NEEDED = 3;
|
||||
|
||||
int mStatus;
|
||||
long mKeyIdPassphraseNeeded;
|
||||
|
||||
OpenPgpSignatureResult mSignatureResult;
|
||||
|
||||
public boolean isSymmetricPassphraseNeeded() {
|
||||
return mSymmetricPassphraseNeeded;
|
||||
public int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) {
|
||||
this.mSymmetricPassphraseNeeded = symmetricPassphraseNeeded;
|
||||
public void setStatus(int mStatus) {
|
||||
this.mStatus = mStatus;
|
||||
}
|
||||
|
||||
public boolean isKeyPassphraseNeeded() {
|
||||
return mKeyPassphraseNeeded;
|
||||
public long getKeyIdPassphraseNeeded() {
|
||||
return mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) {
|
||||
this.mKeyPassphraseNeeded = keyPassphraseNeeded;
|
||||
public void setKeyIdPassphraseNeeded(long mKeyIdPassphraseNeeded) {
|
||||
this.mKeyIdPassphraseNeeded = mKeyIdPassphraseNeeded;
|
||||
}
|
||||
|
||||
public OpenPgpSignatureResult getSignatureResult() {
|
||||
@ -55,8 +61,8 @@ public class PgpDecryptVerifyResult implements Parcelable {
|
||||
}
|
||||
|
||||
public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) {
|
||||
this.mSymmetricPassphraseNeeded = b.mSymmetricPassphraseNeeded;
|
||||
this.mKeyPassphraseNeeded = b.mKeyPassphraseNeeded;
|
||||
this.mStatus = b.mStatus;
|
||||
this.mKeyIdPassphraseNeeded = b.mKeyIdPassphraseNeeded;
|
||||
this.mSignatureResult = b.mSignatureResult;
|
||||
}
|
||||
|
||||
@ -66,16 +72,16 @@ public class PgpDecryptVerifyResult implements Parcelable {
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeByte((byte) (mSymmetricPassphraseNeeded ? 1 : 0));
|
||||
dest.writeByte((byte) (mKeyPassphraseNeeded ? 1 : 0));
|
||||
dest.writeInt(mStatus);
|
||||
dest.writeLong(mKeyIdPassphraseNeeded);
|
||||
dest.writeParcelable(mSignatureResult, 0);
|
||||
}
|
||||
|
||||
public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() {
|
||||
public PgpDecryptVerifyResult createFromParcel(final Parcel source) {
|
||||
PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult();
|
||||
vr.mSymmetricPassphraseNeeded = source.readByte() == 1;
|
||||
vr.mKeyPassphraseNeeded = source.readByte() == 1;
|
||||
vr.mStatus = source.readInt();
|
||||
vr.mKeyIdPassphraseNeeded = source.readLong();
|
||||
vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
||||
return vr;
|
||||
}
|
||||
|
@ -20,7 +20,14 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import org.spongycastle.openpgp.*;
|
||||
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataList;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -43,10 +50,10 @@ public class PgpHelper {
|
||||
public static final Pattern PGP_MESSAGE = Pattern.compile(
|
||||
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
|
||||
|
||||
public static final Pattern PGP_SIGNED_MESSAGE = Pattern
|
||||
.compile(
|
||||
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
|
||||
Pattern.DOTALL);
|
||||
public static final Pattern PGP_CLEARTEXT_SIGNATURE = Pattern
|
||||
.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----" +
|
||||
"BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
|
||||
Pattern.DOTALL);
|
||||
|
||||
public static final Pattern PGP_PUBLIC_KEY = Pattern.compile(
|
||||
".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*",
|
||||
@ -96,7 +103,7 @@ public class PgpHelper {
|
||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||
gotAsymmetricEncryption = true;
|
||||
PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj;
|
||||
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, pbe.getKeyID());
|
||||
secretKey = ProviderHelper.getPGPSecretKeyRing(context, pbe.getKeyID()).getSecretKey();
|
||||
if (secretKey != null) {
|
||||
break;
|
||||
}
|
||||
|
@ -20,8 +20,14 @@ package org.sufficientlysecure.keychain.pgp;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.openpgp.*;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
@ -30,8 +36,12 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.util.*;
|
||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.KeyServer.AddKeyException;
|
||||
import org.sufficientlysecure.keychain.util.KeychainServiceListener;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@ -158,60 +168,69 @@ public class PgpImportExport {
|
||||
return returnData;
|
||||
}
|
||||
|
||||
public Bundle exportKeyRings(ArrayList<Long> keyRingRowIds, int keyType,
|
||||
public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds,
|
||||
ArrayList<Long> secretKeyRingMasterIds,
|
||||
OutputStream outStream) throws PgpGeneralException,
|
||||
PGPException, IOException {
|
||||
Bundle returnData = new Bundle();
|
||||
|
||||
int rowIdsSize = keyRingRowIds.size();
|
||||
int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size();
|
||||
int progress = 0;
|
||||
|
||||
updateProgress(
|
||||
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
|
||||
rowIdsSize), 0, 100);
|
||||
masterKeyIdsSize), 0, 100);
|
||||
|
||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_external_storage_not_ready));
|
||||
}
|
||||
// For each row id
|
||||
for (int i = 0; i < rowIdsSize; ++i) {
|
||||
// For each public masterKey id
|
||||
for (long pubKeyMasterId : publicKeyRingMasterIds) {
|
||||
progress++;
|
||||
// Create an output stream
|
||||
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
|
||||
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
||||
|
||||
// If the keyType is secret get the PGPSecretKeyRing
|
||||
// based on the row id and encode it to the output
|
||||
if (keyType == Id.type.secret_key) {
|
||||
updateProgress(i * 100 / rowIdsSize / 2, 100);
|
||||
PGPSecretKeyRing secretKeyRing =
|
||||
ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i));
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
PGPPublicKeyRing publicKeyRing =
|
||||
ProviderHelper.getPGPPublicKeyRing(mContext, pubKeyMasterId);
|
||||
|
||||
if (secretKeyRing != null) {
|
||||
secretKeyRing.encode(arOutStream);
|
||||
}
|
||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||
arOutStream.close();
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
updateProgress(i * 100 / rowIdsSize, 100);
|
||||
PGPPublicKeyRing publicKeyRing =
|
||||
ProviderHelper.getPGPPublicKeyRingByRowId(mContext, keyRingRowIds.get(i));
|
||||
if (publicKeyRing != null) {
|
||||
publicKeyRing.encode(arOutStream);
|
||||
}
|
||||
|
||||
if (publicKeyRing != null) {
|
||||
publicKeyRing.encode(arOutStream);
|
||||
}
|
||||
|
||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||
arOutStream.close();
|
||||
return null;
|
||||
}
|
||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||
arOutStream.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
arOutStream.close();
|
||||
}
|
||||
|
||||
returnData.putInt(KeychainIntentService.RESULT_EXPORT, rowIdsSize);
|
||||
// For each secret masterKey id
|
||||
for (long secretKeyMasterId : secretKeyRingMasterIds) {
|
||||
progress++;
|
||||
// Create an output stream
|
||||
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
|
||||
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
||||
|
||||
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||
PGPSecretKeyRing secretKeyRing =
|
||||
ProviderHelper.getPGPSecretKeyRing(mContext, secretKeyMasterId);
|
||||
|
||||
if (secretKeyRing != null) {
|
||||
secretKeyRing.encode(arOutStream);
|
||||
}
|
||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||
arOutStream.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
arOutStream.close();
|
||||
}
|
||||
|
||||
returnData.putInt(KeychainIntentService.RESULT_EXPORT, masterKeyIdsSize);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
|
||||
@ -241,7 +260,6 @@ public class PgpImportExport {
|
||||
}
|
||||
|
||||
if (save) {
|
||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
||||
// TODO: preserve certifications
|
||||
// (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?)
|
||||
PGPPublicKeyRing newPubRing = null;
|
||||
@ -256,6 +274,7 @@ public class PgpImportExport {
|
||||
if (newPubRing != null) {
|
||||
ProviderHelper.saveKeyRing(mContext, newPubRing);
|
||||
}
|
||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
||||
// TODO: remove status returns, use exceptions!
|
||||
status = Id.return_value.ok;
|
||||
}
|
||||
|
@ -18,8 +18,18 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.*;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -27,7 +37,14 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.*;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -43,34 +60,6 @@ public class PgpKeyHelper {
|
||||
return key.getPublicKey().getCreationTime();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PGPPublicKey getMasterKey(PGPPublicKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
for (PGPPublicKey key : new IterableIterator<PGPPublicKey>(keyRing.getPublicKeys())) {
|
||||
if (key.isMasterKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PGPSecretKey getMasterKey(PGPSecretKeyRing keyRing) {
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(keyRing.getSecretKeys())) {
|
||||
if (key.isMasterKey()) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) {
|
||||
long cnt = 0;
|
||||
@ -202,9 +191,8 @@ public class PgpKeyHelper {
|
||||
Calendar calendar = GregorianCalendar.getInstance();
|
||||
calendar.setTime(creationDate);
|
||||
calendar.add(Calendar.DATE, key.getValidDays());
|
||||
Date expiryDate = calendar.getTime();
|
||||
|
||||
return expiryDate;
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
public static Date getExpiryDate(PGPSecretKey key) {
|
||||
@ -212,8 +200,7 @@ public class PgpKeyHelper {
|
||||
}
|
||||
|
||||
public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) {
|
||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(context,
|
||||
masterKeyId);
|
||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRing(context, masterKeyId);
|
||||
if (keyRing == null) {
|
||||
Log.e(Constants.TAG, "keyRing is null!");
|
||||
return null;
|
||||
@ -227,8 +214,7 @@ public class PgpKeyHelper {
|
||||
}
|
||||
|
||||
public static PGPSecretKey getCertificationKey(Context context, long masterKeyId) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context,
|
||||
masterKeyId);
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
@ -240,8 +226,7 @@ public class PgpKeyHelper {
|
||||
}
|
||||
|
||||
public static PGPSecretKey getSigningKey(Context context, long masterKeyId) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context,
|
||||
masterKeyId);
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
|
||||
if (keyRing == null) {
|
||||
return null;
|
||||
}
|
||||
@ -284,6 +269,33 @@ public class PgpKeyHelper {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public static int getKeyUsage(PGPSecretKey key) {
|
||||
return getKeyUsage(key.getPublicKey());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static int getKeyUsage(PGPPublicKey key) {
|
||||
int usage = 0;
|
||||
if (key.getVersion() >= 4) {
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
|
||||
if (hashed != null) {
|
||||
usage |= hashed.getKeyFlags();
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
|
||||
if (unhashed != null) {
|
||||
usage |= unhashed.getKeyFlags();
|
||||
}
|
||||
}
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isEncryptionKey(PGPPublicKey key) {
|
||||
if (!key.isEncryptionKey()) {
|
||||
@ -390,6 +402,36 @@ public class PgpKeyHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isAuthenticationKey(PGPSecretKey key) {
|
||||
return isAuthenticationKey(key.getPublicKey());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static boolean isAuthenticationKey(PGPPublicKey key) {
|
||||
if (key.getVersion() <= 3) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignatures())) {
|
||||
if (key.isMasterKey() && sig.getKeyID() != key.getKeyID()) {
|
||||
continue;
|
||||
}
|
||||
PGPSignatureSubpacketVector hashed = sig.getHashedSubPackets();
|
||||
|
||||
if (hashed != null && (hashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketVector unhashed = sig.getUnhashedSubPackets();
|
||||
|
||||
if (unhashed != null && (unhashed.getKeyFlags() & KeyFlags.AUTHENTICATION) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isCertificationKey(PGPSecretKey key) {
|
||||
return isCertificationKey(key.getPublicKey());
|
||||
}
|
||||
@ -403,7 +445,7 @@ public class PgpKeyHelper {
|
||||
}
|
||||
|
||||
public static String getAlgorithmInfo(int algorithm, int keySize) {
|
||||
String algorithmStr = null;
|
||||
String algorithmStr;
|
||||
|
||||
switch (algorithm) {
|
||||
case PGPPublicKey.RSA_ENCRYPT:
|
||||
@ -434,21 +476,6 @@ public class PgpKeyHelper {
|
||||
return algorithmStr;
|
||||
}
|
||||
|
||||
public static String getFingerPrint(Context context, long keyId) {
|
||||
PGPPublicKey key = ProviderHelper.getPGPPublicKeyByKeyId(context, keyId);
|
||||
// if it is no public key get it from your own keys...
|
||||
if (key == null) {
|
||||
PGPSecretKey secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, keyId);
|
||||
if (secretKey == null) {
|
||||
Log.e(Constants.TAG, "Key could not be found!");
|
||||
return null;
|
||||
}
|
||||
key = secretKey.getPublicKey();
|
||||
}
|
||||
|
||||
return convertFingerprintToHex(key.getFingerprint(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts fingerprint to hex (optional: with whitespaces after 4 characters)
|
||||
* <p/>
|
||||
@ -456,14 +483,10 @@ public class PgpKeyHelper {
|
||||
* better differentiate between numbers and letters when letters are lowercase.
|
||||
*
|
||||
* @param fingerprint
|
||||
* @param split split into 4 character chunks
|
||||
* @return
|
||||
*/
|
||||
public static String convertFingerprintToHex(byte[] fingerprint, boolean split) {
|
||||
public static String convertFingerprintToHex(byte[] fingerprint) {
|
||||
String hexString = Hex.toHexString(fingerprint);
|
||||
if (split) {
|
||||
hexString = hexString.replaceAll("(.{4})(?!$)", "$1 ");
|
||||
}
|
||||
|
||||
return hexString;
|
||||
}
|
||||
@ -479,9 +502,18 @@ public class PgpKeyHelper {
|
||||
* @return
|
||||
*/
|
||||
public static String convertKeyIdToHex(long keyId) {
|
||||
long upper = keyId >> 32;
|
||||
if (upper == 0) {
|
||||
// this is a short key id
|
||||
return convertKeyIdToHexShort(keyId);
|
||||
}
|
||||
return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
|
||||
}
|
||||
|
||||
public static String convertKeyIdToHexShort(long keyId) {
|
||||
return "0x" + convertKeyIdToHex32bit(keyId);
|
||||
}
|
||||
|
||||
private static String convertKeyIdToHex32bit(long keyId) {
|
||||
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
|
||||
while (hexString.length() < 8) {
|
||||
@ -490,17 +522,90 @@ public class PgpKeyHelper {
|
||||
return hexString;
|
||||
}
|
||||
|
||||
|
||||
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
|
||||
// split by 4 characters
|
||||
fingerprint = fingerprint.replaceAll("(.{4})(?!$)", "$1 ");
|
||||
|
||||
// add line breaks to have a consistent "image" that can be recognized
|
||||
char[] chars = fingerprint.toCharArray();
|
||||
chars[24] = '\n';
|
||||
fingerprint = String.valueOf(chars);
|
||||
|
||||
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
|
||||
try {
|
||||
// for each 4 characters of the fingerprint + 1 space
|
||||
for (int i = 0; i < fingerprint.length(); i += 5) {
|
||||
int spanEnd = Math.min(i + 4, fingerprint.length());
|
||||
String fourChars = fingerprint.substring(i, spanEnd);
|
||||
|
||||
int raw = Integer.parseInt(fourChars, 16);
|
||||
byte[] bytes = {(byte) ((raw >> 8) & 0xff - 128), (byte) (raw & 0xff - 128)};
|
||||
int[] color = getRgbForData(bytes);
|
||||
int r = color[0];
|
||||
int g = color[1];
|
||||
int b = color[2];
|
||||
|
||||
// we cannot change black by multiplication, so adjust it to an almost-black grey,
|
||||
// which will then be brightened to the minimal brightness level
|
||||
if (r == 0 && g == 0 && b == 0) {
|
||||
r = 1;
|
||||
g = 1;
|
||||
b = 1;
|
||||
}
|
||||
|
||||
// Convert rgb to brightness
|
||||
double brightness = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
|
||||
// If a color is too dark to be seen on black,
|
||||
// then brighten it up to a minimal brightness.
|
||||
if (brightness < 80) {
|
||||
double factor = 80.0 / brightness;
|
||||
r = Math.min(255, (int) (r * factor));
|
||||
g = Math.min(255, (int) (g * factor));
|
||||
b = Math.min(255, (int) (b * factor));
|
||||
|
||||
// If it is too light, then darken it to a respective maximal brightness.
|
||||
} else if (brightness > 180) {
|
||||
double factor = 180.0 / brightness;
|
||||
r = (int) (r * factor);
|
||||
g = (int) (g * factor);
|
||||
b = (int) (b * factor);
|
||||
}
|
||||
|
||||
// Create a foreground color with the 3 digest integers as RGB
|
||||
// and then converting that int to hex to use as a color
|
||||
sb.setSpan(new ForegroundColorSpan(Color.rgb(r, g, b)),
|
||||
i, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Colorization failed", e);
|
||||
// if anything goes wrong, then just display the fingerprint without colour,
|
||||
// instead of partially correct colour or wrong colours
|
||||
return new SpannableStringBuilder(fingerprint);
|
||||
}
|
||||
|
||||
return sb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used in HkpKeyServer to convert hex encoded key ids back to long.
|
||||
* Converts the given bytes to a unique RGB color using SHA1 algorithm
|
||||
*
|
||||
* @param hexString
|
||||
* @return
|
||||
* @param bytes
|
||||
* @return an integer array containing 3 numeric color representations (Red, Green, Black)
|
||||
* @throws java.security.NoSuchAlgorithmException
|
||||
* @throws java.security.DigestException
|
||||
*/
|
||||
public static long convertHexToKeyId(String hexString) {
|
||||
int len = hexString.length();
|
||||
String s2 = hexString.substring(len - 8);
|
||||
String s1 = hexString.substring(0, len - 8);
|
||||
return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16);
|
||||
private static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
|
||||
md.update(bytes);
|
||||
byte[] digest = md.digest();
|
||||
|
||||
int[] result = {((int) digest[0] + 256) % 256,
|
||||
((int) digest[1] + 256) % 256,
|
||||
((int) digest[2] + 256) % 256};
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -17,33 +17,54 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
||||
import org.spongycastle.openpgp.*;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPKeyPair;
|
||||
import org.spongycastle.openpgp.PGPKeyRingGenerator;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.*;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Primes;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
@ -51,8 +72,16 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/** This class is the single place where ALL operations that actually modify a PGP public or secret
|
||||
* key take place.
|
||||
*
|
||||
* Note that no android specific stuff should be done here, ie no imports from com.android.
|
||||
*
|
||||
* All operations support progress reporting to a ProgressDialogUpdater passed on initialization.
|
||||
* This indicator may be null.
|
||||
*
|
||||
*/
|
||||
public class PgpKeyOperation {
|
||||
private Context mContext;
|
||||
private ProgressDialogUpdater mProgress;
|
||||
|
||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
||||
@ -65,19 +94,18 @@ public class PgpKeyOperation {
|
||||
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
|
||||
CompressionAlgorithmTags.ZIP};
|
||||
|
||||
public PgpKeyOperation(Context context, ProgressDialogUpdater progress) {
|
||||
public PgpKeyOperation(ProgressDialogUpdater progress) {
|
||||
super();
|
||||
this.mContext = context;
|
||||
this.mProgress = progress;
|
||||
}
|
||||
|
||||
public void updateProgress(int message, int current, int total) {
|
||||
void updateProgress(int message, int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(message, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateProgress(int current, int total) {
|
||||
void updateProgress(int current, int total) {
|
||||
if (mProgress != null) {
|
||||
mProgress.setProgress(current, total);
|
||||
}
|
||||
@ -90,11 +118,11 @@ public class PgpKeyOperation {
|
||||
* @param keySize
|
||||
* @param passphrase
|
||||
* @param isMasterKey
|
||||
* @return
|
||||
* @return A newly created PGPSecretKey
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws PGPException
|
||||
* @throws NoSuchProviderException
|
||||
* @throws PgpGeneralException
|
||||
* @throws PgpGeneralMsgIdException
|
||||
* @throws InvalidAlgorithmParameterException
|
||||
*/
|
||||
|
||||
@ -102,18 +130,18 @@ public class PgpKeyOperation {
|
||||
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
|
||||
boolean isMasterKey)
|
||||
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||
PgpGeneralException, InvalidAlgorithmParameterException {
|
||||
PgpGeneralMsgIdException, InvalidAlgorithmParameterException {
|
||||
|
||||
if (keySize < 512) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_key_size_minimum512bit));
|
||||
throw new PgpGeneralMsgIdException(R.string.error_key_size_minimum512bit);
|
||||
}
|
||||
|
||||
if (passphrase == null) {
|
||||
passphrase = "";
|
||||
}
|
||||
|
||||
int algorithm = 0;
|
||||
KeyPairGenerator keyGen = null;
|
||||
int algorithm;
|
||||
KeyPairGenerator keyGen;
|
||||
|
||||
switch (algorithmChoice) {
|
||||
case Id.choice.algorithm.dsa: {
|
||||
@ -125,8 +153,7 @@ public class PgpKeyOperation {
|
||||
|
||||
case Id.choice.algorithm.elgamal: {
|
||||
if (isMasterKey) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
|
||||
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
|
||||
}
|
||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
BigInteger p = Primes.getBestPrime(keySize);
|
||||
@ -148,8 +175,7 @@ public class PgpKeyOperation {
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_unknown_algorithm_choice));
|
||||
throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,194 +191,115 @@ public class PgpKeyOperation {
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
|
||||
PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
|
||||
sha1Calc, isMasterKey, keyEncryptor);
|
||||
|
||||
return secKey;
|
||||
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
|
||||
sha1Calc, isMasterKey, keyEncryptor);
|
||||
}
|
||||
|
||||
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
|
||||
String newPassPhrase) throws IOException, PGPException,
|
||||
NoSuchProviderException {
|
||||
public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassphrase,
|
||||
String newPassphrase)
|
||||
throws IOException, PGPException, NoSuchProviderException {
|
||||
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
if (oldPassPhrase == null) {
|
||||
oldPassPhrase = "";
|
||||
if (oldPassphrase == null) {
|
||||
oldPassphrase = "";
|
||||
}
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = "";
|
||||
if (newPassphrase == null) {
|
||||
newPassphrase = "";
|
||||
}
|
||||
|
||||
PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword(
|
||||
keyRing,
|
||||
new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray()),
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray()),
|
||||
new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey()
|
||||
.getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray()));
|
||||
.getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray()));
|
||||
|
||||
updateProgress(R.string.progress_saving_key_ring, 50, 100);
|
||||
|
||||
ProviderHelper.saveKeyRing(mContext, newKeyRing);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
return newKeyRing;
|
||||
|
||||
}
|
||||
|
||||
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
||||
ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates,
|
||||
PGPPublicKey oldPublicKey, String oldPassPhrase,
|
||||
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
|
||||
PGPException, NoSuchAlgorithmException, SignatureException, IOException {
|
||||
private Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildNewSecretKey(
|
||||
ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
||||
ArrayList<GregorianCalendar> keysExpiryDates,
|
||||
ArrayList<Integer> keysUsages,
|
||||
String newPassphrase, String oldPassphrase)
|
||||
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
|
||||
|
||||
Log.d(Constants.TAG, "userIds: " + userIds.toString());
|
||||
int usageId = keysUsages.get(0);
|
||||
boolean canSign;
|
||||
String mainUserId = userIds.get(0);
|
||||
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
PGPSecretKey masterKey = keys.get(0);
|
||||
|
||||
if (oldPassPhrase == null) {
|
||||
oldPassPhrase = "";
|
||||
}
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = "";
|
||||
// this removes all userIds and certifications previously attached to the masterPublicKey
|
||||
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassphrase.toCharArray());
|
||||
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
||||
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
|
||||
for (String userId : userIds) {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_preparing_master_key, 10, 100);
|
||||
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
||||
|
||||
// prepare keyring generator with given master public and secret key
|
||||
PGPKeyRingGenerator keyGen;
|
||||
PGPPublicKey masterPublicKey; {
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
String mainUserId = userIds.get(0);
|
||||
hashedPacketsGen.setKeyFlags(true, usageId);
|
||||
|
||||
// prepare the master key pair
|
||||
PGPKeyPair masterKeyPair; {
|
||||
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
|
||||
PGPSecretKey masterKey = keys.get(0);
|
||||
|
||||
// this removes all userIds and certifications previously attached to the masterPublicKey
|
||||
PGPPublicKey tmpKey = masterKey.getPublicKey();
|
||||
masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
|
||||
tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
|
||||
|
||||
// already done by code above:
|
||||
// PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||
// // Somehow, the PGPPublicKey already has an empty certification attached to it when the
|
||||
// // keyRing is generated the first time, we remove that when it exists, before adding the
|
||||
// new
|
||||
// // ones
|
||||
// PGPPublicKey masterPublicKeyRmCert = PGPPublicKey.removeCertification(masterPublicKey,
|
||||
// "");
|
||||
// if (masterPublicKeyRmCert != null) {
|
||||
// masterPublicKey = masterPublicKeyRmCert;
|
||||
// }
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(oldPassPhrase.toCharArray());
|
||||
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
||||
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
|
||||
// re-add old certificates, or create new ones for new uids
|
||||
for (String userId : userIds) {
|
||||
// re-add certs for this uid, take a note if self-signed cert is in there
|
||||
boolean foundSelfSign = false;
|
||||
Iterator<PGPSignature> it = tmpKey.getSignaturesForID(userId);
|
||||
if(it != null) for(PGPSignature sig : new IterableIterator<PGPSignature>(it)) {
|
||||
if(sig.getKeyID() == masterPublicKey.getKeyID()) {
|
||||
// already have a self sign? skip this other one, then.
|
||||
// note: PGPKeyRingGenerator adds one cert for the main user id, which
|
||||
// will lead to duplicates. unfortunately, if we add any other here
|
||||
// first, that will change the main user id order...
|
||||
if(foundSelfSign)
|
||||
continue;
|
||||
foundSelfSign = true;
|
||||
}
|
||||
Log.d(Constants.TAG, "adding old sig for " + userId + " from "
|
||||
+ PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()));
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, sig);
|
||||
}
|
||||
|
||||
// there was an old self-signed certificate for this uid
|
||||
if(foundSelfSign)
|
||||
continue;
|
||||
|
||||
Log.d(Constants.TAG, "generating self-signed cert for " + userId);
|
||||
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||
}
|
||||
|
||||
masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
||||
if (keysExpiryDates.get(0) != null) {
|
||||
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(masterPublicKey.getCreationTime());
|
||||
GregorianCalendar expiryDate = keysExpiryDates.get(0);
|
||||
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
|
||||
(creationDate.getTimeInMillis() / 86400000);
|
||||
if (numDays <= 0) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||
}
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen;
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen; {
|
||||
|
||||
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
int usageId = keysUsages.get(0);
|
||||
boolean canEncrypt =
|
||||
(usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
|
||||
int keyFlags = KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA;
|
||||
if (canEncrypt) {
|
||||
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||
}
|
||||
hashedPacketsGen.setKeyFlags(true, keyFlags);
|
||||
|
||||
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
|
||||
if (keysExpiryDates.get(0) != null) {
|
||||
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(masterPublicKey.getCreationTime());
|
||||
GregorianCalendar expiryDate = keysExpiryDates.get(0);
|
||||
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||
long numDays =
|
||||
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
||||
if (numDays <= 0) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_expiry_must_come_after_creation));
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||
} else {
|
||||
//do this explicitly, although since we're rebuilding,
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
//this happens anyway
|
||||
}
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_building_master_key, 30, 100);
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
|
||||
HashAlgorithmTags.SHA1);
|
||||
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
newPassPhrase.toCharArray());
|
||||
|
||||
keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
|
||||
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
|
||||
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
|
||||
|
||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||
} else {
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
// do this explicitly, although since we're rebuilding,
|
||||
// this happens anyway
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_building_master_key, 30, 100);
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
|
||||
HashAlgorithmTags.SHA1);
|
||||
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encrypter based on passphrase
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
newPassphrase.toCharArray());
|
||||
|
||||
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
|
||||
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
|
||||
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
|
||||
|
||||
updateProgress(R.string.progress_adding_sub_keys, 40, 100);
|
||||
|
||||
for (int i = 1; i < keys.size(); ++i) {
|
||||
@ -361,27 +308,21 @@ public class PgpKeyOperation {
|
||||
PGPSecretKey subKey = keys.get(i);
|
||||
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
||||
PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
oldPassPhrase.toCharArray());
|
||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor);
|
||||
oldPassphrase.toCharArray());
|
||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
|
||||
|
||||
// TODO: now used without algorithm and creation time?! (APG 1)
|
||||
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
int keyFlags = 0;
|
||||
|
||||
int usageId = keysUsages.get(i);
|
||||
boolean canSign =
|
||||
(usageId == Id.choice.usage.sign_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
boolean canEncrypt =
|
||||
(usageId == Id.choice.usage.encrypt_only || usageId == Id.choice.usage.sign_and_encrypt);
|
||||
usageId = keysUsages.get(i);
|
||||
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
|
||||
if (canSign) {
|
||||
Date todayDate = new Date(); //both sig times the same
|
||||
keyFlags |= KeyFlags.SIGN_DATA;
|
||||
// cross-certify signing keys
|
||||
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
@ -396,10 +337,7 @@ public class PgpKeyOperation {
|
||||
subPublicKey);
|
||||
unhashedPacketsGen.setEmbeddedSignature(false, certification);
|
||||
}
|
||||
if (canEncrypt) {
|
||||
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
||||
}
|
||||
hashedPacketsGen.setKeyFlags(false, keyFlags);
|
||||
hashedPacketsGen.setKeyFlags(false, usageId);
|
||||
|
||||
if (keysExpiryDates.get(i) != null) {
|
||||
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
@ -407,17 +345,16 @@ public class PgpKeyOperation {
|
||||
GregorianCalendar expiryDate = keysExpiryDates.get(i);
|
||||
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||
long numDays =
|
||||
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
|
||||
(creationDate.getTimeInMillis() / 86400000);
|
||||
if (numDays <= 0) {
|
||||
throw new PgpGeneralException
|
||||
(mContext.getString(R.string.error_expiry_must_come_after_creation));
|
||||
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||
} else {
|
||||
//do this explicitly, although since we're rebuilding,
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
//this happens anyway
|
||||
// do this explicitly, although since we're rebuilding,
|
||||
// this happens anyway
|
||||
}
|
||||
|
||||
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
|
||||
@ -426,102 +363,407 @@ public class PgpKeyOperation {
|
||||
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
|
||||
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
|
||||
|
||||
updateProgress(R.string.progress_re_adding_certs, 80, 100);
|
||||
return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(secretKeyRing, publicKeyRing);
|
||||
|
||||
// re-add certificates from old public key
|
||||
// TODO: this only takes care of user id certificates, what about others?
|
||||
PGPPublicKey pubkey = publicKeyRing.getPublicKey();
|
||||
for(String uid : new IterableIterator<String>(pubkey.getUserIDs())) {
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(oldPublicKey.getSignaturesForID(uid), true)) {
|
||||
// but skip self certificates
|
||||
if(sig.getKeyID() == pubkey.getKeyID())
|
||||
continue;
|
||||
pubkey = PGPPublicKey.addCertification(pubkey, uid, sig);
|
||||
}
|
||||
}
|
||||
publicKeyRing = PGPPublicKeyRing.insertPublicKey(publicKeyRing, pubkey);
|
||||
}
|
||||
|
||||
updateProgress(R.string.progress_saving_key_ring, 90, 100);
|
||||
public Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildSecretKey(PGPSecretKeyRing mKR,
|
||||
PGPPublicKeyRing pKR,
|
||||
SaveKeyringParcel saveParcel)
|
||||
throws PgpGeneralMsgIdException, PGPException, SignatureException, IOException {
|
||||
|
||||
/* additional handy debug info
|
||||
Log.d(Constants.TAG, " ------- in private key -------");
|
||||
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
|
||||
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
|
||||
}
|
||||
updateProgress(R.string.progress_building_key, 0, 100);
|
||||
PGPSecretKey masterKey = saveParcel.keys.get(0);
|
||||
|
||||
if (saveParcel.oldPassphrase == null) {
|
||||
saveParcel.oldPassphrase = "";
|
||||
}
|
||||
Log.d(Constants.TAG, " ------- in public key -------");
|
||||
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
|
||||
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
|
||||
}
|
||||
if (saveParcel.newPassphrase == null) {
|
||||
saveParcel.newPassphrase = "";
|
||||
}
|
||||
|
||||
if (mKR == null) {
|
||||
return buildNewSecretKey(saveParcel.userIDs, saveParcel.keys, saveParcel.keysExpiryDates,
|
||||
saveParcel.keysUsages, saveParcel.newPassphrase, saveParcel.oldPassphrase); //new Keyring
|
||||
}
|
||||
|
||||
/*
|
||||
IDs - NB This might not need to happen later, if we change the way the primary ID is chosen
|
||||
remove deleted ids
|
||||
if the primary ID changed we need to:
|
||||
remove all of the IDs from the keyring, saving their certifications
|
||||
add them all in again, updating certs of IDs which have changed
|
||||
else
|
||||
remove changed IDs and add in with new certs
|
||||
|
||||
if the master key changed, we need to remove the primary ID certification, so we can add
|
||||
the new one when it is generated, and they don't conflict
|
||||
|
||||
Keys
|
||||
remove deleted keys
|
||||
if a key is modified, re-sign it
|
||||
do we need to remove and add in?
|
||||
|
||||
Todo
|
||||
identify more things which need to be preserved - e.g. trust levels?
|
||||
user attributes
|
||||
*/
|
||||
|
||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
||||
ProviderHelper.saveKeyRing(mContext, publicKeyRing);
|
||||
if (saveParcel.deletedKeys != null) {
|
||||
for (PGPSecretKey dKey : saveParcel.deletedKeys) {
|
||||
mKR = PGPSecretKeyRing.removeSecretKey(mKR, dKey);
|
||||
}
|
||||
}
|
||||
|
||||
masterKey = mKR.getSecretKey();
|
||||
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||
|
||||
int usageId = saveParcel.keysUsages.get(0);
|
||||
boolean canSign;
|
||||
String mainUserId = saveParcel.userIDs.get(0);
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(saveParcel.oldPassphrase.toCharArray());
|
||||
PGPPrivateKey masterPrivateKey = masterKey.extractPrivateKey(keyDecryptor);
|
||||
|
||||
updateProgress(R.string.progress_certifying_master_key, 20, 100);
|
||||
|
||||
boolean anyIDChanged = false;
|
||||
for (String delID : saveParcel.deletedIDs) {
|
||||
anyIDChanged = true;
|
||||
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, delID);
|
||||
}
|
||||
|
||||
int userIDIndex = 0;
|
||||
|
||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
hashedPacketsGen.setKeyFlags(true, usageId);
|
||||
|
||||
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||
|
||||
if (saveParcel.keysExpiryDates.get(0) != null) {
|
||||
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(masterPublicKey.getCreationTime());
|
||||
GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(0);
|
||||
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
|
||||
(creationDate.getTimeInMillis() / 86400000);
|
||||
if (numDays <= 0) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||
} else {
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
// do this explicitly, although since we're rebuilding,
|
||||
// this happens anyway
|
||||
}
|
||||
|
||||
if (saveParcel.primaryIDChanged ||
|
||||
!saveParcel.originalIDs.get(0).equals(saveParcel.userIDs.get(0))) {
|
||||
anyIDChanged = true;
|
||||
ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
|
||||
for (String userId : saveParcel.userIDs) {
|
||||
String origID = saveParcel.originalIDs.get(userIDIndex);
|
||||
if (origID.equals(userId) && !saveParcel.newIDs[userIDIndex] &&
|
||||
!userId.equals(saveParcel.originalPrimaryID) && userIDIndex != 0) {
|
||||
Iterator<PGPSignature> origSigs = masterPublicKey.getSignaturesForID(origID);
|
||||
// TODO: make sure this iterator only has signatures we are interested in
|
||||
while (origSigs.hasNext()) {
|
||||
PGPSignature origSig = origSigs.next();
|
||||
sigList.add(new Pair<String, PGPSignature>(origID, origSig));
|
||||
}
|
||||
} else {
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
if (userIDIndex == 0) {
|
||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
|
||||
}
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
sigList.add(new Pair<String, PGPSignature>(userId, certification));
|
||||
}
|
||||
if (!saveParcel.newIDs[userIDIndex]) {
|
||||
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
|
||||
}
|
||||
userIDIndex++;
|
||||
}
|
||||
for (Pair<String, PGPSignature> toAdd : sigList) {
|
||||
masterPublicKey =
|
||||
PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
|
||||
}
|
||||
} else {
|
||||
for (String userId : saveParcel.userIDs) {
|
||||
String origID = saveParcel.originalIDs.get(userIDIndex);
|
||||
if (!origID.equals(userId) || saveParcel.newIDs[userIDIndex]) {
|
||||
anyIDChanged = true;
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
|
||||
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
|
||||
if (userIDIndex == 0) {
|
||||
sGen.setHashedSubpackets(hashedPacketsGen.generate());
|
||||
sGen.setUnhashedSubpackets(unhashedPacketsGen.generate());
|
||||
}
|
||||
PGPSignature certification = sGen.generateCertification(userId, masterPublicKey);
|
||||
if (!saveParcel.newIDs[userIDIndex]) {
|
||||
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, origID);
|
||||
}
|
||||
masterPublicKey =
|
||||
PGPPublicKey.addCertification(masterPublicKey, userId, certification);
|
||||
}
|
||||
userIDIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Pair<String, PGPSignature>> sigList = new ArrayList<Pair<String, PGPSignature>>();
|
||||
if (saveParcel.moddedKeys[0]) {
|
||||
userIDIndex = 0;
|
||||
for (String userId : saveParcel.userIDs) {
|
||||
String origID = saveParcel.originalIDs.get(userIDIndex);
|
||||
if (!(origID.equals(saveParcel.originalPrimaryID) && !saveParcel.primaryIDChanged)) {
|
||||
Iterator<PGPSignature> sigs = masterPublicKey.getSignaturesForID(userId);
|
||||
// TODO: make sure this iterator only has signatures we are interested in
|
||||
while (sigs.hasNext()) {
|
||||
PGPSignature sig = sigs.next();
|
||||
sigList.add(new Pair<String, PGPSignature>(userId, sig));
|
||||
}
|
||||
}
|
||||
masterPublicKey = PGPPublicKey.removeCertification(masterPublicKey, userId);
|
||||
userIDIndex++;
|
||||
}
|
||||
anyIDChanged = true;
|
||||
}
|
||||
|
||||
//update the keyring with the new ID information
|
||||
if (anyIDChanged) {
|
||||
pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
|
||||
mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
|
||||
}
|
||||
|
||||
PGPKeyPair masterKeyPair = new PGPKeyPair(masterPublicKey, masterPrivateKey);
|
||||
|
||||
updateProgress(R.string.progress_building_master_key, 30, 100);
|
||||
|
||||
// define hashing and signing algos
|
||||
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(
|
||||
HashAlgorithmTags.SHA1);
|
||||
PGPContentSignerBuilder certificationSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
masterKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1);
|
||||
|
||||
// Build key encryptor based on old passphrase, as some keys may be unchanged
|
||||
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.oldPassphrase.toCharArray());
|
||||
|
||||
//this generates one more signature than necessary...
|
||||
PGPKeyRingGenerator keyGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
|
||||
masterKeyPair, mainUserId, sha1Calc, hashedPacketsGen.generate(),
|
||||
unhashedPacketsGen.generate(), certificationSignerBuilder, keyEncryptor);
|
||||
|
||||
for (int i = 1; i < saveParcel.keys.size(); ++i) {
|
||||
updateProgress(40 + 50 * i / saveParcel.keys.size(), 100);
|
||||
if (saveParcel.moddedKeys[i]) {
|
||||
PGPSecretKey subKey = saveParcel.keys.get(i);
|
||||
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor2;
|
||||
if (saveParcel.newKeys[i]) {
|
||||
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
"".toCharArray());
|
||||
} else {
|
||||
keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.oldPassphrase.toCharArray());
|
||||
}
|
||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
|
||||
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
|
||||
|
||||
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
|
||||
usageId = saveParcel.keysUsages.get(i);
|
||||
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
|
||||
if (canSign) {
|
||||
Date todayDate = new Date(); //both sig times the same
|
||||
// cross-certify signing keys
|
||||
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
|
||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||
subHashedPacketsGen.setSignatureCreationTime(false, todayDate); //set inner creation time
|
||||
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
|
||||
subPublicKey.getAlgorithm(), PGPUtil.SHA1)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
|
||||
sGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
|
||||
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
|
||||
PGPSignature certification = sGen.generateCertification(masterPublicKey,
|
||||
subPublicKey);
|
||||
unhashedPacketsGen.setEmbeddedSignature(false, certification);
|
||||
}
|
||||
hashedPacketsGen.setKeyFlags(false, usageId);
|
||||
|
||||
if (saveParcel.keysExpiryDates.get(i) != null) {
|
||||
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||
creationDate.setTime(subPublicKey.getCreationTime());
|
||||
GregorianCalendar expiryDate = saveParcel.keysExpiryDates.get(i);
|
||||
// note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||
// here we purposefully ignore partial days in each date - long type has
|
||||
// no fractional part!
|
||||
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
|
||||
(creationDate.getTimeInMillis() / 86400000);
|
||||
if (numDays <= 0) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||
}
|
||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||
} else {
|
||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||
// do this explicitly, although since we're rebuilding,
|
||||
// this happens anyway
|
||||
}
|
||||
|
||||
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
|
||||
// certifications will be discarded if the key is changed, because I think, for a start,
|
||||
// they will be invalid. Binding certs are regenerated anyway, and other certs which
|
||||
// need to be kept are on IDs and attributes
|
||||
// TODO: don't let revoked keys be edited, other than removed - changing one would
|
||||
// result in the revocation being wrong?
|
||||
}
|
||||
}
|
||||
|
||||
PGPSecretKeyRing updatedSecretKeyRing = keyGen.generateSecretKeyRing();
|
||||
//finally, update the keyrings
|
||||
Iterator<PGPSecretKey> itr = updatedSecretKeyRing.getSecretKeys();
|
||||
while (itr.hasNext()) {
|
||||
PGPSecretKey theNextKey = itr.next();
|
||||
if ((theNextKey.isMasterKey() && saveParcel.moddedKeys[0]) || !theNextKey.isMasterKey()) {
|
||||
mKR = PGPSecretKeyRing.insertSecretKey(mKR, theNextKey);
|
||||
pKR = PGPPublicKeyRing.insertPublicKey(pKR, theNextKey.getPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
//replace lost IDs
|
||||
if (saveParcel.moddedKeys[0]) {
|
||||
masterPublicKey = mKR.getPublicKey();
|
||||
for (Pair<String, PGPSignature> toAdd : sigList) {
|
||||
masterPublicKey = PGPPublicKey.addCertification(masterPublicKey, toAdd.first, toAdd.second);
|
||||
}
|
||||
pKR = PGPPublicKeyRing.insertPublicKey(pKR, masterPublicKey);
|
||||
mKR = PGPSecretKeyRing.replacePublicKeys(mKR, pKR);
|
||||
}
|
||||
|
||||
// Build key encryptor based on new passphrase
|
||||
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
|
||||
PGPEncryptedData.CAST5, sha1Calc)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||
saveParcel.newPassphrase.toCharArray());
|
||||
|
||||
//update the passphrase
|
||||
mKR = PGPSecretKeyRing.copyWithNewPassword(mKR, keyDecryptor, keyEncryptorNew);
|
||||
|
||||
/* additional handy debug info
|
||||
|
||||
Log.d(Constants.TAG, " ------- in private key -------");
|
||||
|
||||
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(
|
||||
secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
|
||||
Log.d(Constants.TAG, "sig: " +
|
||||
PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, " ------- in public key -------");
|
||||
|
||||
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
|
||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(
|
||||
publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
|
||||
Log.d(Constants.TAG, "sig: " +
|
||||
PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
return new Pair<PGPSecretKeyRing, PGPPublicKeyRing>(mKR, pKR);
|
||||
|
||||
updateProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Certify the given pubkeyid with the given masterkeyid.
|
||||
*
|
||||
* @param masterKeyId Certifying key, must be available as secret key
|
||||
* @param pubKeyId ID of public key to certify
|
||||
* @param certificationKey Certifying key
|
||||
* @param publicKey public key to certify
|
||||
* @param userIds User IDs to certify, must not be null or empty
|
||||
* @param passphrase Passphrase of the secret key
|
||||
* @return A keyring with added certifications
|
||||
*/
|
||||
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
|
||||
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
PGPException, SignatureException {
|
||||
if (passphrase == null) {
|
||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||
} else {
|
||||
public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey,
|
||||
List<String> userIds, String passphrase)
|
||||
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||
PGPException, SignatureException {
|
||||
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator; {
|
||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||
PGPSignatureGenerator signatureGenerator; {
|
||||
|
||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
||||
if (certificationKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
}
|
||||
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralException(
|
||||
mContext.getString(R.string.error_could_not_extract_private_key));
|
||||
}
|
||||
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
|
||||
if (certificationKey == null) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_signature_failed);
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||
if (signaturePrivateKey == null) {
|
||||
throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key);
|
||||
}
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
PGPPublicKeyRing pubring = ProviderHelper
|
||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
||||
PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId);
|
||||
for(String userId : new IterableIterator<String>(userIds.iterator())) {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(userId, signedKey);
|
||||
signedKey = PGPPublicKey.addCertification(signedKey, userId, sig);
|
||||
}
|
||||
pubring = PGPPublicKeyRing.insertPublicKey(pubring, signedKey);
|
||||
// TODO: SHA256 fixed?
|
||||
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||
|
||||
return pubring;
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(PGPSignature.DEFAULT_CERTIFICATION, signaturePrivateKey);
|
||||
}
|
||||
|
||||
{ // supply signatureGenerator with a SubpacketVector
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
||||
signatureGenerator.setHashedSubpackets(packetVector);
|
||||
}
|
||||
|
||||
// fetch public key ring, add the certification and return it
|
||||
for (String userId : new IterableIterator<String>(userIds.iterator())) {
|
||||
PGPSignature sig = signatureGenerator.generateCertification(userId, publicKey);
|
||||
publicKey = PGPPublicKey.addCertification(publicKey, userId, sig);
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/** Simple static subclass that stores two values.
|
||||
*
|
||||
* This is only used to return a pair of values in one function above. We specifically don't use
|
||||
* com.android.Pair to keep this class free from android dependencies.
|
||||
*/
|
||||
public static class Pair<K, V> {
|
||||
public final K first;
|
||||
public final V second;
|
||||
public Pair(K first, V second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,28 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||
import org.spongycastle.bcpg.BCPGOutputStream;
|
||||
import org.spongycastle.openpgp.*;
|
||||
import org.spongycastle.openpgp.PGPCompressedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPEncryptedDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPLiteralData;
|
||||
import org.spongycastle.openpgp.PGPLiteralDataGenerator;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSignature;
|
||||
import org.spongycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
|
||||
import org.spongycastle.openpgp.PGPV3SignatureGenerator;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.*;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -32,7 +49,11 @@ import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
@ -50,7 +71,7 @@ public class PgpSignEncrypt {
|
||||
private boolean mEnableAsciiArmorOutput;
|
||||
private int mCompressionId;
|
||||
private long[] mEncryptionKeyIds;
|
||||
private String mEncryptionPassphrase;
|
||||
private String mSymmetricPassphrase;
|
||||
private int mSymmetricEncryptionAlgorithm;
|
||||
private long mSignatureKeyId;
|
||||
private int mSignatureHashAlgorithm;
|
||||
@ -67,7 +88,7 @@ public class PgpSignEncrypt {
|
||||
this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
|
||||
this.mCompressionId = builder.mCompressionId;
|
||||
this.mEncryptionKeyIds = builder.mEncryptionKeyIds;
|
||||
this.mEncryptionPassphrase = builder.mEncryptionPassphrase;
|
||||
this.mSymmetricPassphrase = builder.mSymmetricPassphrase;
|
||||
this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
|
||||
this.mSignatureKeyId = builder.mSignatureKeyId;
|
||||
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
|
||||
@ -85,8 +106,8 @@ public class PgpSignEncrypt {
|
||||
private ProgressDialogUpdater mProgress = null;
|
||||
private boolean mEnableAsciiArmorOutput = false;
|
||||
private int mCompressionId = Id.choice.compression.none;
|
||||
private long[] mEncryptionKeyIds = new long[0];
|
||||
private String mEncryptionPassphrase = null;
|
||||
private long[] mEncryptionKeyIds = null;
|
||||
private String mSymmetricPassphrase = null;
|
||||
private int mSymmetricEncryptionAlgorithm = 0;
|
||||
private long mSignatureKeyId = Id.key.none;
|
||||
private int mSignatureHashAlgorithm = 0;
|
||||
@ -119,8 +140,8 @@ public class PgpSignEncrypt {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder encryptionPassphrase(String encryptionPassphrase) {
|
||||
this.mEncryptionPassphrase = encryptionPassphrase;
|
||||
public Builder symmetricPassphrase(String symmetricPassphrase) {
|
||||
this.mSymmetricPassphrase = symmetricPassphrase;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -181,7 +202,8 @@ public class PgpSignEncrypt {
|
||||
NoSuchAlgorithmException, SignatureException {
|
||||
|
||||
boolean enableSignature = mSignatureKeyId != Id.key.none;
|
||||
boolean enableEncryption = (mEncryptionKeyIds.length != 0 || mEncryptionPassphrase != null);
|
||||
boolean enableEncryption = ((mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0)
|
||||
|| mSymmetricPassphrase != null);
|
||||
boolean enableCompression = (enableEncryption && mCompressionId != Id.choice.compression.none);
|
||||
|
||||
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
||||
@ -212,7 +234,7 @@ public class PgpSignEncrypt {
|
||||
PGPSecretKeyRing signingKeyRing = null;
|
||||
PGPPrivateKey signaturePrivateKey = null;
|
||||
if (enableSignature) {
|
||||
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
|
||||
signingKeyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId);
|
||||
signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
|
||||
if (signingKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
@ -246,12 +268,12 @@ public class PgpSignEncrypt {
|
||||
|
||||
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
|
||||
|
||||
if (mEncryptionKeyIds.length == 0) {
|
||||
if (mSymmetricPassphrase != null) {
|
||||
// Symmetric encryption
|
||||
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
|
||||
|
||||
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
|
||||
new JcePBEKeyEncryptionMethodGenerator(mEncryptionPassphrase.toCharArray());
|
||||
new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray());
|
||||
cPk.addMethod(symmetricEncryptionGenerator);
|
||||
} else {
|
||||
// Asymmetric encryption
|
||||
@ -284,7 +306,7 @@ public class PgpSignEncrypt {
|
||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||
signatureGenerator.init(signatureType, signaturePrivateKey);
|
||||
|
||||
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
|
||||
String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey());
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
spGen.setSignerUserID(false, userId);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
@ -442,7 +464,7 @@ public class PgpSignEncrypt {
|
||||
}
|
||||
|
||||
PGPSecretKeyRing signingKeyRing =
|
||||
ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
|
||||
ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId);
|
||||
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
|
||||
if (signingKey == null) {
|
||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||
@ -483,7 +505,7 @@ public class PgpSignEncrypt {
|
||||
signatureGenerator.init(type, signaturePrivateKey);
|
||||
|
||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
|
||||
String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey());
|
||||
spGen.setSignerUserID(false, userId);
|
||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||
}
|
||||
|
@ -18,7 +18,13 @@
|
||||
package org.sufficientlysecure.keychain.pgp;
|
||||
|
||||
import org.spongycastle.asn1.DERObjectIdentifier;
|
||||
import org.spongycastle.asn1.x509.*;
|
||||
import org.spongycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||
import org.spongycastle.asn1.x509.BasicConstraints;
|
||||
import org.spongycastle.asn1.x509.GeneralName;
|
||||
import org.spongycastle.asn1.x509.GeneralNames;
|
||||
import org.spongycastle.asn1.x509.SubjectKeyIdentifier;
|
||||
import org.spongycastle.asn1.x509.X509Extensions;
|
||||
import org.spongycastle.asn1.x509.X509Name;
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
@ -29,13 +35,14 @@ import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.DateFormat;
|
||||
@ -43,6 +50,11 @@ import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Vector;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.callback.PasswordCallback;
|
||||
import javax.security.auth.callback.UnsupportedCallbackException;
|
||||
|
||||
public class PgpToX509 {
|
||||
public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
|
||||
public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert";
|
||||
@ -71,9 +83,10 @@ public class PgpToX509 {
|
||||
* @throws Exception
|
||||
* @author Bruno Harbulot
|
||||
*/
|
||||
public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey,
|
||||
X509Name subject, Date startDate, Date endDate, String subjAltNameURI)
|
||||
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
|
||||
public static X509Certificate createSelfSignedCert(
|
||||
PublicKey pubKey, PrivateKey privKey, X509Name subject, Date startDate, Date endDate,
|
||||
String subjAltNameURI)
|
||||
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
|
||||
SignatureException, CertificateException, NoSuchProviderException {
|
||||
|
||||
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
|
||||
@ -170,10 +183,10 @@ public class PgpToX509 {
|
||||
/**
|
||||
* Creates a self-signed certificate from a PGP Secret Key.
|
||||
*
|
||||
* @param pgpSecKey PGP Secret Key (from which one can extract the public and private keys and other
|
||||
* attributes).
|
||||
* @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks should be done
|
||||
* before calling this method)
|
||||
* @param pgpSecKey PGP Secret Key (from which one can extract the public and private
|
||||
* keys and other attributes).
|
||||
* @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks
|
||||
* should be done before calling this method)
|
||||
* @param subjAltNameURI optional URI to embed in the subject alternative-name
|
||||
* @return self-signed certificate
|
||||
* @throws PGPException
|
||||
@ -184,9 +197,9 @@ public class PgpToX509 {
|
||||
* @throws CertificateException
|
||||
* @author Bruno Harbulot
|
||||
*/
|
||||
public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey,
|
||||
PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException,
|
||||
NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
|
||||
public static X509Certificate createSelfSignedCert(
|
||||
PGPSecretKey pgpSecKey, PGPPrivateKey pgpPrivKey, String subjAltNameURI)
|
||||
throws PGPException, NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
|
||||
SignatureException, CertificateException {
|
||||
// get public key from secret key
|
||||
PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey();
|
||||
|
@ -23,4 +23,7 @@ public class PgpGeneralException extends Exception {
|
||||
public PgpGeneralException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public PgpGeneralException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* 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.pgp.exception;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class PgpGeneralMsgIdException extends Exception {
|
||||
static final long serialVersionUID = 0xf812773343L;
|
||||
|
||||
private final int mMessageId;
|
||||
|
||||
public PgpGeneralMsgIdException(int messageId) {
|
||||
super("msg[" + messageId + "]");
|
||||
mMessageId = messageId;
|
||||
}
|
||||
|
||||
public PgpGeneralException getContextualized(Context context) {
|
||||
return new PgpGeneralException(context.getString(mMessageId), this);
|
||||
}
|
||||
}
|
@ -19,46 +19,47 @@ package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
|
||||
public class KeychainContract {
|
||||
|
||||
interface KeyRingsColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String TYPE = "type"; // see KeyTypes
|
||||
String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
|
||||
}
|
||||
|
||||
interface KeysColumns {
|
||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
||||
String RANK = "rank";
|
||||
|
||||
String KEY_ID = "key_id"; // not a database id
|
||||
String TYPE = "type"; // see KeyTypes
|
||||
String IS_MASTER_KEY = "is_master_key";
|
||||
String ALGORITHM = "algorithm";
|
||||
String FINGERPRINT = "fingerprint";
|
||||
|
||||
String KEY_SIZE = "key_size";
|
||||
String CAN_CERTIFY = "can_certify";
|
||||
String CAN_SIGN = "can_sign";
|
||||
String CAN_ENCRYPT = "can_encrypt";
|
||||
String CAN_CERTIFY = "can_certify";
|
||||
String IS_REVOKED = "is_revoked";
|
||||
|
||||
String CREATION = "creation";
|
||||
String EXPIRY = "expiry";
|
||||
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
|
||||
String KEY_DATA = "key_data"; // PGPPublicKey/PGPSecretKey blob
|
||||
String RANK = "rank";
|
||||
String FINGERPRINT = "fingerprint";
|
||||
}
|
||||
|
||||
interface UserIdsColumns {
|
||||
String KEY_RING_ROW_ID = "key_ring_row_id"; // foreign key to key_rings._ID
|
||||
String MASTER_KEY_ID = "master_key_id"; // foreign key to key_rings._ID
|
||||
String USER_ID = "user_id"; // not a database id
|
||||
String RANK = "rank";
|
||||
String RANK = "rank"; // ONLY used for sorting! no key, no nothing!
|
||||
String IS_PRIMARY = "is_primary";
|
||||
}
|
||||
|
||||
interface CertsColumns {
|
||||
String KEY_RING_ROW_ID = "key_ring_row_id"; // verified id, foreign key to key_rings._ID
|
||||
String MASTER_KEY_ID = "master_key_id"; // verified id, foreign key to key_rings._ID
|
||||
String RANK = "rank"; // rank of verified key
|
||||
String KEY_ID = "key_id"; // verified id, not a database id
|
||||
String KEY_ID_CERTIFIER = "key_id_certifier"; // verifying id, not a database id
|
||||
String CREATION = "creation";
|
||||
String EXPIRY = "expiry";
|
||||
String VERIFIED = "verified";
|
||||
String KEY_DATA = "key_data"; // certification blob
|
||||
}
|
||||
@ -66,10 +67,15 @@ public class KeychainContract {
|
||||
interface ApiAppsColumns {
|
||||
String PACKAGE_NAME = "package_name";
|
||||
String PACKAGE_SIGNATURE = "package_signature";
|
||||
}
|
||||
|
||||
interface ApiAppsAccountsColumns {
|
||||
String ACCOUNT_NAME = "account_name";
|
||||
String KEY_ID = "key_id"; // not a database id
|
||||
String ENCRYPTION_ALGORITHM = "encryption_algorithm";
|
||||
String HASH_ALORITHM = "hash_algorithm";
|
||||
String COMPRESSION = "compression";
|
||||
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||
}
|
||||
|
||||
public static final class KeyTypes {
|
||||
@ -85,97 +91,81 @@ public class KeychainContract {
|
||||
public static final String BASE_KEY_RINGS = "key_rings";
|
||||
public static final String BASE_DATA = "data";
|
||||
|
||||
public static final String PATH_UNIFIED = "unified";
|
||||
|
||||
public static final String PATH_FIND = "find";
|
||||
public static final String PATH_BY_EMAIL = "email";
|
||||
public static final String PATH_BY_SUBKEY = "subkey";
|
||||
|
||||
public static final String PATH_PUBLIC = "public";
|
||||
public static final String PATH_SECRET = "secret";
|
||||
|
||||
public static final String PATH_BY_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String PATH_BY_KEY_ID = "key_id";
|
||||
public static final String PATH_BY_KEY_ROW_ID = "key_row_id";
|
||||
public static final String PATH_BY_CERTIFIER_ID = "certifier_id";
|
||||
public static final String PATH_BY_EMAILS = "emails";
|
||||
public static final String PATH_BY_LIKE_EMAIL = "like_email";
|
||||
|
||||
public static final String PATH_USER_IDS = "user_ids";
|
||||
public static final String PATH_KEYS = "keys";
|
||||
public static final String PATH_CERTS = "certs";
|
||||
|
||||
public static final String BASE_API_APPS = "api_apps";
|
||||
public static final String PATH_BY_PACKAGE_NAME = "package_name";
|
||||
public static final String PATH_ACCOUNTS = "accounts";
|
||||
|
||||
public static final String BASE_CERTS = "certs";
|
||||
public static class KeyRings implements BaseColumns, KeysColumns, UserIdsColumns {
|
||||
public static final String MASTER_KEY_ID = "master_key_id";
|
||||
public static final String HAS_SECRET = "has_secret";
|
||||
|
||||
public static class KeyRings implements KeyRingsColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
/**
|
||||
* Use if multiple items get returned
|
||||
*/
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key_ring";
|
||||
|
||||
/**
|
||||
* Use if a single item is returned
|
||||
*/
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key_ring";
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring";
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring";
|
||||
|
||||
public static Uri buildUnifiedKeyRingsUri() {
|
||||
return CONTENT_URI;
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_UNIFIED).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsUri() {
|
||||
public static Uri buildGenericKeyRingUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).build();
|
||||
}
|
||||
public static Uri buildUnifiedKeyRingUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_UNIFIED).build();
|
||||
}
|
||||
public static Uri buildUnifiedKeyRingUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_UNIFIED).build();
|
||||
}
|
||||
|
||||
public static Uri buildUnifiedKeyRingsFindByEmailUri(String email) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_EMAIL).appendPath(email).build();
|
||||
}
|
||||
public static Uri buildUnifiedKeyRingsFindBySubkeyUri(String subkey) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_FIND).appendPath(PATH_BY_SUBKEY).appendPath(subkey).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class KeyRingData implements KeyRingsColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring_data";
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring_data";
|
||||
|
||||
public static Uri buildPublicKeyRingUri() {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId).build();
|
||||
public static Uri buildPublicKeyRingUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build();
|
||||
}
|
||||
public static Uri buildPublicKeyRingUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_PUBLIC).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByMasterKeyIdUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC)
|
||||
.appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByKeyIdUri(String keyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_KEY_ID)
|
||||
.appendPath(keyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByEmailsUri(String emails) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_EMAILS)
|
||||
.appendPath(emails).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeyRingsByLikeEmailUri(String emails) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(PATH_BY_LIKE_EMAIL)
|
||||
.appendPath(emails).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsUri() {
|
||||
public static Uri buildSecretKeyRingUri() {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId).build();
|
||||
public static Uri buildSecretKeyRingUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build();
|
||||
}
|
||||
public static Uri buildSecretKeyRingUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_SECRET).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByMasterKeyIdUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET)
|
||||
.appendPath(PATH_BY_MASTER_KEY_ID).appendPath(masterKeyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByKeyIdUri(String keyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_KEY_ID)
|
||||
.appendPath(keyId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByEmailsUri(String emails) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_EMAILS)
|
||||
.appendPath(emails).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeyRingsByLikeEmails(String emails) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(PATH_BY_LIKE_EMAIL)
|
||||
.appendPath(emails).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Keys implements KeysColumns, BaseColumns {
|
||||
@ -185,82 +175,42 @@ public class KeychainContract {
|
||||
/**
|
||||
* Use if multiple items get returned
|
||||
*/
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.key";
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key";
|
||||
|
||||
/**
|
||||
* Use if a single item is returned
|
||||
*/
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.key";
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key";
|
||||
|
||||
public static Uri buildPublicKeysUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).build();
|
||||
public static Uri buildKeysUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_KEYS).build();
|
||||
}
|
||||
public static Uri buildKeysUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_KEYS).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicKeysUri(String keyRingRowId, String keyRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).appendPath(keyRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeysUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretKeysUri(String keyRingRowId, String keyRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_KEYS).appendPath(keyRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildKeysUri(Uri keyRingUri) {
|
||||
return keyRingUri.buildUpon().appendPath(PATH_KEYS).build();
|
||||
}
|
||||
|
||||
public static Uri buildKeysUri(Uri keyRingUri, String keyRowId) {
|
||||
return keyRingUri.buildUpon().appendPath(PATH_KEYS).appendPath(keyRowId).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class UserIds implements UserIdsColumns, BaseColumns {
|
||||
public static final String VERIFIED = "verified";
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
/**
|
||||
* Use if multiple items get returned
|
||||
*/
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.user_id";
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.user_id";
|
||||
|
||||
/**
|
||||
* Use if a single item is returned
|
||||
*/
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.user_id";
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.user_id";
|
||||
|
||||
public static Uri buildPublicUserIdsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).build();
|
||||
public static Uri buildUserIdsUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
|
||||
public static Uri buildPublicUserIdsUri(String keyRingRowId, String userIdRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretUserIdsUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
|
||||
public static Uri buildSecretUserIdsUri(String keyRingRowId, String userIdRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId)
|
||||
.appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildUserIdsUri(Uri keyRingUri) {
|
||||
return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
|
||||
public static Uri buildUserIdsUri(Uri keyRingUri, String userIdRowId) {
|
||||
return keyRingUri.buildUpon().appendPath(PATH_USER_IDS).appendPath(userIdRowId).build();
|
||||
public static Uri buildUserIdsUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,45 +221,55 @@ public class KeychainContract {
|
||||
/**
|
||||
* Use if multiple items get returned
|
||||
*/
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.api_apps";
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.api_apps";
|
||||
|
||||
/**
|
||||
* Use if a single item is returned
|
||||
*/
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.api_apps";
|
||||
|
||||
public static Uri buildIdUri(String rowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(rowId).build();
|
||||
}
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app";
|
||||
|
||||
public static Uri buildByPackageNameUri(String packageName) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_BY_PACKAGE_NAME).appendPath(packageName)
|
||||
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiAccounts implements ApiAppsAccountsColumns, BaseColumns {
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_API_APPS).build();
|
||||
|
||||
/**
|
||||
* Use if multiple items get returned
|
||||
*/
|
||||
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.api_app.accounts";
|
||||
|
||||
/**
|
||||
* Use if a single item is returned
|
||||
*/
|
||||
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app.account";
|
||||
|
||||
public static Uri buildBaseUri(String packageName) {
|
||||
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri buildByPackageAndAccountUri(String packageName, String accountName) {
|
||||
return CONTENT_URI.buildUpon().appendEncodedPath(packageName).appendPath(PATH_ACCOUNTS)
|
||||
.appendEncodedPath(accountName).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Certs implements CertsColumns, BaseColumns {
|
||||
public static final String USER_ID = UserIdsColumns.USER_ID;
|
||||
public static final String SIGNER_UID = "signer_user_id";
|
||||
|
||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||
.appendPath(BASE_CERTS).build();
|
||||
.appendPath(BASE_KEY_RINGS).build();
|
||||
|
||||
// do we even need this one...? just using it as default for database insert notifications~
|
||||
public static Uri buildCertsUri(String rowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(rowId).build();
|
||||
public static Uri buildCertsUri(String masterKeyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build();
|
||||
}
|
||||
|
||||
public static Uri buildCertsByKeyRowIdUri(String keyRingRowId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ROW_ID)
|
||||
.appendPath(keyRingRowId).build();
|
||||
}
|
||||
|
||||
public static Uri buildCertsByKeyIdUri(String keyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_BY_KEY_ID).appendPath(keyId)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static Uri buildCertsByCertifierKeyIdUri(String keyId) {
|
||||
return CONTENT_URI.buildUpon().appendPath(PATH_BY_CERTIFIER_ID).appendPath(keyId)
|
||||
.build();
|
||||
public static Uri buildCertsUri(Uri uri) {
|
||||
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,97 +18,156 @@
|
||||
package org.sufficientlysecure.keychain.provider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsAccountsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "apg.db";
|
||||
private static final int DATABASE_VERSION = 8;
|
||||
private static final String DATABASE_NAME = "openkeychain.db";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
static Boolean apg_hack = false;
|
||||
|
||||
public interface Tables {
|
||||
String KEY_RINGS = "key_rings";
|
||||
String KEY_RINGS_PUBLIC = "keyrings_public";
|
||||
String KEY_RINGS_SECRET = "keyrings_secret";
|
||||
String KEYS = "keys";
|
||||
String USER_IDS = "user_ids";
|
||||
String API_APPS = "api_apps";
|
||||
String CERTS = "certs";
|
||||
String API_APPS = "api_apps";
|
||||
String API_ACCOUNTS = "api_accounts";
|
||||
}
|
||||
|
||||
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
|
||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ KeyRingsColumns.MASTER_KEY_ID + " INT64, "
|
||||
+ KeyRingsColumns.TYPE + " INTEGER, "
|
||||
+ KeyRingsColumns.KEY_RING_DATA + " BLOB)";
|
||||
private static final String CREATE_KEYRINGS_PUBLIC =
|
||||
"CREATE TABLE IF NOT EXISTS keyrings_public ("
|
||||
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
|
||||
+ KeyRingsColumns.KEY_RING_DATA + " BLOB"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ KeysColumns.KEY_ID + " INT64, "
|
||||
+ KeysColumns.TYPE + " INTEGER, "
|
||||
+ KeysColumns.IS_MASTER_KEY + " INTEGER, "
|
||||
+ KeysColumns.ALGORITHM + " INTEGER, "
|
||||
+ KeysColumns.KEY_SIZE + " INTEGER, "
|
||||
+ KeysColumns.CAN_CERTIFY + " INTEGER, "
|
||||
+ KeysColumns.CAN_SIGN + " INTEGER, "
|
||||
+ KeysColumns.CAN_ENCRYPT + " INTEGER, "
|
||||
+ KeysColumns.IS_REVOKED + " INTEGER, "
|
||||
+ KeysColumns.CREATION + " INTEGER, "
|
||||
+ KeysColumns.EXPIRY + " INTEGER, "
|
||||
+ KeysColumns.KEY_DATA + " BLOB,"
|
||||
+ KeysColumns.RANK + " INTEGER, "
|
||||
+ KeysColumns.FINGERPRINT + " BLOB, "
|
||||
+ KeysColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
|
||||
+ KeysColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
||||
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
||||
private static final String CREATE_KEYRINGS_SECRET =
|
||||
"CREATE TABLE IF NOT EXISTS keyrings_secret ("
|
||||
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
|
||||
+ KeyRingsColumns.KEY_RING_DATA + " BLOB,"
|
||||
+ "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") "
|
||||
+ "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
|
||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ UserIdsColumns.USER_ID + " TEXT, "
|
||||
+ UserIdsColumns.RANK + " INTEGER, "
|
||||
+ UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
|
||||
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
||||
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
||||
private static final String CREATE_KEYS =
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
||||
+ KeysColumns.MASTER_KEY_ID + " INTEGER, "
|
||||
+ KeysColumns.RANK + " INTEGER, "
|
||||
|
||||
+ KeysColumns.KEY_ID + " INTEGER, "
|
||||
+ KeysColumns.KEY_SIZE + " INTEGER, "
|
||||
+ KeysColumns.ALGORITHM + " INTEGER, "
|
||||
+ KeysColumns.FINGERPRINT + " BLOB, "
|
||||
|
||||
+ KeysColumns.CAN_CERTIFY + " BOOLEAN, "
|
||||
+ KeysColumns.CAN_SIGN + " BOOLEAN, "
|
||||
+ KeysColumns.CAN_ENCRYPT + " BOOLEAN, "
|
||||
+ KeysColumns.IS_REVOKED + " BOOLEAN, "
|
||||
|
||||
+ KeysColumns.CREATION + " INTEGER, "
|
||||
+ KeysColumns.EXPIRY + " INTEGER, "
|
||||
|
||||
+ "PRIMARY KEY(" + KeysColumns.MASTER_KEY_ID + ", " + KeysColumns.RANK + "),"
|
||||
+ "FOREIGN KEY(" + KeysColumns.MASTER_KEY_ID + ") REFERENCES "
|
||||
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_USER_IDS =
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS + "("
|
||||
+ UserIdsColumns.MASTER_KEY_ID + " INTEGER, "
|
||||
+ UserIdsColumns.USER_ID + " CHARMANDER, "
|
||||
|
||||
+ UserIdsColumns.IS_PRIMARY + " BOOLEAN, "
|
||||
+ UserIdsColumns.RANK+ " INTEGER, "
|
||||
|
||||
+ "PRIMARY KEY(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.USER_ID + "), "
|
||||
+ "UNIQUE (" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + "), "
|
||||
+ "FOREIGN KEY(" + UserIdsColumns.MASTER_KEY_ID + ") REFERENCES "
|
||||
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_CERTS =
|
||||
"CREATE TABLE IF NOT EXISTS " + Tables.CERTS + "("
|
||||
+ CertsColumns.MASTER_KEY_ID + " INTEGER,"
|
||||
+ CertsColumns.RANK + " INTEGER, " // rank of certified uid
|
||||
|
||||
+ CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key
|
||||
+ CertsColumns.CREATION + " INTEGER, "
|
||||
+ CertsColumns.EXPIRY + " INTEGER, "
|
||||
+ CertsColumns.VERIFIED + " INTEGER, "
|
||||
|
||||
+ CertsColumns.KEY_DATA + " BLOB,"
|
||||
+ "PRIMARY KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ", "
|
||||
+ CertsColumns.KEY_ID_CERTIFIER + "), "
|
||||
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ") REFERENCES "
|
||||
+ Tables.KEY_RINGS_PUBLIC + "(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE,"
|
||||
+ "FOREIGN KEY(" + CertsColumns.MASTER_KEY_ID + ", " + CertsColumns.RANK + ") REFERENCES "
|
||||
+ Tables.USER_IDS + "(" + UserIdsColumns.MASTER_KEY_ID + ", " + UserIdsColumns.RANK + ") ON DELETE CASCADE"
|
||||
+ ")";
|
||||
|
||||
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
|
||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, "
|
||||
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB, "
|
||||
+ ApiAppsColumns.KEY_ID + " INT64, "
|
||||
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
|
||||
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
|
||||
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
|
||||
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
|
||||
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB)";
|
||||
|
||||
private static final String CREATE_CERTS = "CREATE TABLE IF NOT EXISTS " + Tables.CERTS
|
||||
private static final String CREATE_API_APPS_ACCOUNTS = "CREATE TABLE IF NOT EXISTS " + Tables.API_ACCOUNTS
|
||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ CertsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL "
|
||||
+ " REFERENCES " + Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE, "
|
||||
+ CertsColumns.KEY_ID + " INTEGER, " // certified key
|
||||
+ CertsColumns.RANK + " INTEGER, " // key rank of certified uid
|
||||
+ CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key
|
||||
+ CertsColumns.CREATION + " INTEGER, "
|
||||
+ CertsColumns.VERIFIED + " INTEGER, "
|
||||
+ CertsColumns.KEY_DATA + " BLOB)";
|
||||
|
||||
+ ApiAppsAccountsColumns.ACCOUNT_NAME + " TEXT NOT NULL, "
|
||||
+ ApiAppsAccountsColumns.KEY_ID + " INT64, "
|
||||
+ ApiAppsAccountsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
|
||||
+ ApiAppsAccountsColumns.HASH_ALORITHM + " INTEGER, "
|
||||
+ ApiAppsAccountsColumns.COMPRESSION + " INTEGER, "
|
||||
+ ApiAppsAccountsColumns.PACKAGE_NAME + " TEXT NOT NULL, "
|
||||
+ "UNIQUE(" + ApiAppsAccountsColumns.ACCOUNT_NAME + ", "
|
||||
+ ApiAppsAccountsColumns.PACKAGE_NAME + "), "
|
||||
+ "FOREIGN KEY(" + ApiAppsAccountsColumns.PACKAGE_NAME + ") REFERENCES "
|
||||
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE)";
|
||||
|
||||
KeychainDatabase(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
|
||||
// make sure this is only done once, on the first instance!
|
||||
boolean iAmIt = false;
|
||||
synchronized(apg_hack) {
|
||||
if(!apg_hack) {
|
||||
iAmIt = true;
|
||||
apg_hack = true;
|
||||
}
|
||||
}
|
||||
// if it's us, do the import
|
||||
if(iAmIt)
|
||||
checkAndImportApg(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.w(Constants.TAG, "Creating database...");
|
||||
|
||||
db.execSQL(CREATE_KEY_RINGS);
|
||||
db.execSQL(CREATE_KEYRINGS_PUBLIC);
|
||||
db.execSQL(CREATE_KEYRINGS_SECRET);
|
||||
db.execSQL(CREATE_KEYS);
|
||||
db.execSQL(CREATE_USER_IDS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
db.execSQL(CREATE_CERTS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
db.execSQL(CREATE_API_APPS_ACCOUNTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,47 +176,100 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
||||
if (!db.isReadOnly()) {
|
||||
// Enable foreign key constraints
|
||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
||||
// TODO remove, once we remove the "always migrate" debug stuff
|
||||
// db.execSQL("DROP TABLE certs;");
|
||||
// db.execSQL("DROP TABLE user_ids;");
|
||||
db.execSQL(CREATE_USER_IDS);
|
||||
db.execSQL(CREATE_CERTS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
|
||||
public void onUpgrade(SQLiteDatabase db, int old, int nu) {
|
||||
// don't care (this is version 1)
|
||||
}
|
||||
|
||||
// Upgrade from oldVersion through all cases to newest one
|
||||
for (int version = oldVersion; version < newVersion; ++version) {
|
||||
Log.w(Constants.TAG, "Upgrading database to version " + version);
|
||||
/** This method tries to import data from a provided database.
|
||||
*
|
||||
* The sole assumptions made on this db are that there is a key_rings table
|
||||
* with a key_ring_data and a type column, the latter of which should be bigger
|
||||
* for secret keys.
|
||||
*/
|
||||
public void checkAndImportApg(Context context) {
|
||||
|
||||
switch (version) {
|
||||
case 3:
|
||||
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY
|
||||
+ " INTEGER DEFAULT 0;");
|
||||
db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY
|
||||
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
|
||||
boolean hasApgDb = false; {
|
||||
// It's the Java way =(
|
||||
String[] dbs = context.databaseList();
|
||||
for(String db : dbs) {
|
||||
if(db.equals("apg.db")) {
|
||||
hasApgDb = true;
|
||||
break;
|
||||
case 4:
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
break;
|
||||
case 5:
|
||||
// new column: package_signature
|
||||
db.execSQL("DROP TABLE IF EXISTS " + Tables.API_APPS);
|
||||
db.execSQL(CREATE_API_APPS);
|
||||
break;
|
||||
case 6:
|
||||
// new column: fingerprint
|
||||
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.FINGERPRINT
|
||||
+ " BLOB;");
|
||||
break;
|
||||
case 7:
|
||||
// new table: certs
|
||||
db.execSQL(CREATE_CERTS);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!hasApgDb)
|
||||
return;
|
||||
|
||||
Log.d(Constants.TAG, "apg.db exists! Importing...");
|
||||
|
||||
SQLiteDatabase db = new SQLiteOpenHelper(context, "apg.db", null, 1) {
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
// should never happen
|
||||
assert false;
|
||||
}
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int old, int nu) {
|
||||
// don't care
|
||||
}
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int old, int nu) {
|
||||
// don't care either
|
||||
}
|
||||
}.getReadableDatabase();
|
||||
|
||||
// kill current!
|
||||
{ // TODO don't kill current.
|
||||
Log.d(Constants.TAG, "Truncating db...");
|
||||
SQLiteDatabase d = getWritableDatabase();
|
||||
d.execSQL("DELETE FROM keyrings_public");
|
||||
d.close();
|
||||
Log.d(Constants.TAG, "Ok.");
|
||||
}
|
||||
|
||||
Cursor c = db.rawQuery("SELECT key_ring_data FROM key_rings ORDER BY type ASC", null);
|
||||
try {
|
||||
// import from old database
|
||||
Log.d(Constants.TAG, "Importing " + c.getCount() + " keyrings from apg.db...");
|
||||
for(int i = 0; i < c.getCount(); i++) {
|
||||
c.moveToPosition(i);
|
||||
byte[] data = c.getBlob(0);
|
||||
PGPKeyRing ring = PgpConversionHelper.BytesToPGPKeyRing(data);
|
||||
if(ring instanceof PGPPublicKeyRing)
|
||||
ProviderHelper.saveKeyRing(context, (PGPPublicKeyRing) ring);
|
||||
else if(ring instanceof PGPSecretKeyRing)
|
||||
ProviderHelper.saveKeyRing(context, (PGPSecretKeyRing) ring);
|
||||
else {
|
||||
Log.e(Constants.TAG, "Unknown blob data type!");
|
||||
}
|
||||
}
|
||||
|
||||
} catch(IOException e) {
|
||||
Log.e(Constants.TAG, "Error importing apg db!", e);
|
||||
return;
|
||||
} finally {
|
||||
if(c != null)
|
||||
c.close();
|
||||
if(db != null)
|
||||
db.close();
|
||||
}
|
||||
|
||||
// TODO delete old db, if we are sure this works
|
||||
// context.deleteDatabase("apg.db");
|
||||
Log.d(Constants.TAG, "All done, (not) deleting apg.db");
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ import android.provider.BaseColumns;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
|
||||
|
||||
public class KeychainServiceBlobDatabase extends SQLiteOpenHelper {
|
||||
private static final String DATABASE_NAME = "apg_blob.db";
|
||||
private static final String DATABASE_NAME = "openkeychain_blob.db";
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
|
||||
public static final String TABLE = "data";
|
||||
|
@ -38,7 +38,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class KeychainServiceBlobProvider extends ContentProvider {
|
||||
private static final String STORE_PATH = Constants.Path.APP_DIR + "/ApgBlobs";
|
||||
private static final String STORE_PATH = Constants.Path.APP_DIR + "/KeychainBlobs";
|
||||
|
||||
private KeychainServiceBlobDatabase mBlobDatabase = null;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,48 +15,39 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
|
||||
public class AppSettings {
|
||||
private String mPackageName;
|
||||
private byte[] mPackageSignature;
|
||||
public class AccountSettings {
|
||||
private String mAccountName;
|
||||
private long mKeyId = Id.key.none;
|
||||
private int mEncryptionAlgorithm;
|
||||
private int mHashAlgorithm;
|
||||
private int mCompression;
|
||||
|
||||
public AppSettings() {
|
||||
public AccountSettings() {
|
||||
|
||||
}
|
||||
|
||||
public AppSettings(String packageName, byte[] packageSignature) {
|
||||
public AccountSettings(String accountName) {
|
||||
super();
|
||||
this.mPackageName = packageName;
|
||||
this.mPackageSignature = packageSignature;
|
||||
this.mAccountName = accountName;
|
||||
|
||||
// defaults:
|
||||
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
|
||||
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
|
||||
this.mCompression = Id.choice.compression.zlib;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
public String getAccountName() {
|
||||
return mAccountName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.mPackageName = packageName;
|
||||
}
|
||||
|
||||
public byte[] getPackageSignature() {
|
||||
return mPackageSignature;
|
||||
}
|
||||
|
||||
public void setPackageSignature(byte[] packageSignature) {
|
||||
this.mPackageSignature = packageSignature;
|
||||
public void setAccountName(String mAccountName) {
|
||||
this.mAccountName = mAccountName;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.remote;
|
||||
|
||||
public class AppSettings {
|
||||
private String mPackageName;
|
||||
private byte[] mPackageSignature;
|
||||
|
||||
public AppSettings() {
|
||||
|
||||
}
|
||||
|
||||
public AppSettings(String packageName, byte[] packageSignature) {
|
||||
super();
|
||||
this.mPackageName = packageName;
|
||||
this.mPackageSignature = packageSignature;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.mPackageName = packageName;
|
||||
}
|
||||
|
||||
public byte[] getPackageSignature() {
|
||||
return mPackageSignature;
|
||||
}
|
||||
|
||||
public void setPackageSignature(byte[] packageSignature) {
|
||||
this.mPackageSignature = packageSignature;
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
@ -23,6 +23,7 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import org.openintents.openpgp.IOpenPgpService;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
@ -34,22 +35,22 @@ import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAccounts;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
public class OpenPgpService extends RemoteService {
|
||||
|
||||
private static final int PRIVATE_REQUEST_CODE_PASSPHRASE = 551;
|
||||
private static final int PRIVATE_REQUEST_CODE_USER_IDS = 552;
|
||||
private static final int PRIVATE_REQUEST_CODE_GET_KEYS = 553;
|
||||
|
||||
/**
|
||||
* Search database for key ids based on emails.
|
||||
*
|
||||
@ -61,15 +62,15 @@ public class OpenPgpService extends RemoteService {
|
||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||
|
||||
boolean missingUserIdsCheck = false;
|
||||
boolean dublicateUserIdsCheck = false;
|
||||
boolean duplicateUserIdsCheck = false;
|
||||
ArrayList<String> missingUserIds = new ArrayList<String>();
|
||||
ArrayList<String> dublicateUserIds = new ArrayList<String>();
|
||||
ArrayList<String> duplicateUserIds = new ArrayList<String>();
|
||||
|
||||
for (String email : encryptionUserIds) {
|
||||
Uri uri = KeychainContract.KeyRings.buildPublicKeyRingsByEmailsUri(email);
|
||||
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||
Cursor cur = getContentResolver().query(uri, null, null, null, null);
|
||||
if (cur.moveToFirst()) {
|
||||
long id = cur.getLong(cur.getColumnIndex(KeychainContract.KeyRings.MASTER_KEY_ID));
|
||||
long id = cur.getLong(cur.getColumnIndex(KeyRings.MASTER_KEY_ID));
|
||||
keyIds.add(id);
|
||||
} else {
|
||||
missingUserIdsCheck = true;
|
||||
@ -77,8 +78,8 @@ public class OpenPgpService extends RemoteService {
|
||||
Log.d(Constants.TAG, "user id missing");
|
||||
}
|
||||
if (cur.moveToNext()) {
|
||||
dublicateUserIdsCheck = true;
|
||||
dublicateUserIds.add(email);
|
||||
duplicateUserIdsCheck = true;
|
||||
duplicateUserIds.add(email);
|
||||
Log.d(Constants.TAG, "more than one user id with the same email");
|
||||
}
|
||||
}
|
||||
@ -90,17 +91,18 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
// allow the user to verify pub key selection
|
||||
if (missingUserIdsCheck || dublicateUserIdsCheck) {
|
||||
if (missingUserIdsCheck || duplicateUserIdsCheck) {
|
||||
// build PendingIntent
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, dublicateUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DUBLICATE_USER_IDS, duplicateUserIds);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity
|
||||
(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
@ -126,8 +128,9 @@ public class OpenPgpService extends RemoteService {
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
|
||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
PendingIntent pi = PendingIntent.getActivity
|
||||
(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
@ -137,7 +140,7 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
private Intent signImpl(Intent data, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AppSettings appSettings) {
|
||||
ParcelFileDescriptor output, AccountSettings accSettings) {
|
||||
try {
|
||||
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
@ -146,11 +149,11 @@ public class OpenPgpService extends RemoteService {
|
||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||
} else {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId());
|
||||
}
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
|
||||
Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
|
||||
return passphraseBundle;
|
||||
}
|
||||
|
||||
@ -164,9 +167,9 @@ public class OpenPgpService extends RemoteService {
|
||||
// sign-only
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
||||
builder.enableAsciiArmorOutput(asciiArmor)
|
||||
.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
||||
.signatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.signatureForceV3(false)
|
||||
.signatureKeyId(appSettings.getKeyId())
|
||||
.signatureKeyId(accSettings.getKeyId())
|
||||
.signaturePassphrase(passphrase);
|
||||
builder.build().execute();
|
||||
} finally {
|
||||
@ -187,7 +190,8 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AppSettings appSettings, boolean sign) {
|
||||
ParcelFileDescriptor output, AccountSettings accSettings,
|
||||
boolean sign) {
|
||||
try {
|
||||
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
|
||||
@ -210,14 +214,14 @@ public class OpenPgpService extends RemoteService {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||
new OpenPgpError(OpenPgpError.GENERIC_ERROR,
|
||||
"Missing parameter user_ids or key_ids!"));
|
||||
"Missing parameter user_ids or key_ids!"));
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
}
|
||||
|
||||
// add own key for encryption
|
||||
keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
|
||||
keyIds[keyIds.length - 1] = appSettings.getKeyId();
|
||||
keyIds[keyIds.length - 1] = accSettings.getKeyId();
|
||||
|
||||
// build InputData and write into OutputStream
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
@ -229,8 +233,8 @@ public class OpenPgpService extends RemoteService {
|
||||
|
||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
||||
builder.enableAsciiArmorOutput(asciiArmor)
|
||||
.compressionId(appSettings.getCompression())
|
||||
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm())
|
||||
.compressionId(accSettings.getCompression())
|
||||
.symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
|
||||
.encryptionKeyIds(keyIds);
|
||||
|
||||
if (sign) {
|
||||
@ -239,18 +243,18 @@ public class OpenPgpService extends RemoteService {
|
||||
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||
} else {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
|
||||
appSettings.getKeyId());
|
||||
accSettings.getKeyId());
|
||||
}
|
||||
if (passphrase == null) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
|
||||
Intent passphraseBundle = getPassphraseBundleIntent(data, accSettings.getKeyId());
|
||||
return passphraseBundle;
|
||||
}
|
||||
|
||||
// sign and encrypt
|
||||
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
||||
builder.signatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||
.signatureForceV3(false)
|
||||
.signatureKeyId(appSettings.getKeyId())
|
||||
.signatureKeyId(accSettings.getKeyId())
|
||||
.signaturePassphrase(passphrase);
|
||||
} else {
|
||||
// encrypt only
|
||||
@ -276,7 +280,7 @@ public class OpenPgpService extends RemoteService {
|
||||
}
|
||||
|
||||
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
|
||||
ParcelFileDescriptor output, AppSettings appSettings) {
|
||||
ParcelFileDescriptor output, Set<Long> allowedKeyIds) {
|
||||
try {
|
||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||
@ -290,19 +294,21 @@ public class OpenPgpService extends RemoteService {
|
||||
InputData inputData = new InputData(is, inputLength);
|
||||
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
|
||||
builder.assumeSymmetric(false) // no support for symmetric encryption
|
||||
// allow only the private key for this app for decryption
|
||||
.enforcedKeyId(appSettings.getKeyId())
|
||||
builder.allowSymmetricDecryption(false) // no support for symmetric encryption
|
||||
.allowedKeyIds(allowedKeyIds) // allow only private keys associated with
|
||||
// accounts of this app
|
||||
.passphrase(passphrase);
|
||||
|
||||
// TODO: currently does not support binary signed-only content
|
||||
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
|
||||
if (decryptVerifyResult.isKeyPassphraseNeeded()) {
|
||||
if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
|
||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
||||
Intent passphraseBundle = getPassphraseBundleIntent(data, appSettings.getKeyId());
|
||||
Intent passphraseBundle =
|
||||
getPassphraseBundleIntent(data, decryptVerifyResult.getKeyIdPassphraseNeeded());
|
||||
return passphraseBundle;
|
||||
} else if (decryptVerifyResult.isSymmetricPassphraseNeeded()) {
|
||||
} else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
|
||||
decryptVerifyResult.getStatus()) {
|
||||
throw new PgpGeneralException("Decryption of symmetric content not supported by API!");
|
||||
}
|
||||
|
||||
@ -311,14 +317,14 @@ public class OpenPgpService extends RemoteService {
|
||||
if (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) {
|
||||
// If signature is unknown we return an _additional_ PendingIntent
|
||||
// to retrieve the missing key
|
||||
// TODO!!!
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId());
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
||||
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
}
|
||||
@ -346,19 +352,19 @@ public class OpenPgpService extends RemoteService {
|
||||
try {
|
||||
long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
|
||||
|
||||
if (ProviderHelper.getPGPPublicKeyByKeyId(this, keyId) == null) {
|
||||
if (ProviderHelper.getPGPPublicKeyRing(this, keyId) == null) {
|
||||
Intent result = new Intent();
|
||||
|
||||
// If keys are not in db we return an additional PendingIntent
|
||||
// to retrieve the missing key
|
||||
// TODO!!!
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, keyId);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
||||
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
@ -366,6 +372,9 @@ public class OpenPgpService extends RemoteService {
|
||||
} else {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
|
||||
// TODO: also return PendingIntent that opens the key view activity
|
||||
|
||||
return result;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@ -407,7 +416,7 @@ public class OpenPgpService extends RemoteService {
|
||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
|
||||
Intent result = new Intent();
|
||||
OpenPgpError error = new OpenPgpError
|
||||
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
|
||||
(OpenPgpError.INCOMPATIBLE_API_VERSIONS, "Incompatible API versions!");
|
||||
result.putExtra(OpenPgpApi.RESULT_ERROR, error);
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||
return result;
|
||||
@ -432,17 +441,30 @@ public class OpenPgpService extends RemoteService {
|
||||
return errorResult;
|
||||
}
|
||||
|
||||
final AppSettings appSettings = getAppSettings();
|
||||
String accName;
|
||||
if (data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME) != null) {
|
||||
accName = data.getStringExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME);
|
||||
} else {
|
||||
accName = "default";
|
||||
}
|
||||
final AccountSettings accSettings = getAccSettings(accName);
|
||||
if (accSettings == null) {
|
||||
return getCreateAccountIntent(data, accName);
|
||||
}
|
||||
|
||||
String action = data.getAction();
|
||||
if (OpenPgpApi.ACTION_SIGN.equals(action)) {
|
||||
return signImpl(data, input, output, appSettings);
|
||||
return signImpl(data, input, output, accSettings);
|
||||
} else if (OpenPgpApi.ACTION_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, appSettings, false);
|
||||
return encryptAndSignImpl(data, input, output, accSettings, false);
|
||||
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(action)) {
|
||||
return encryptAndSignImpl(data, input, output, appSettings, true);
|
||||
return encryptAndSignImpl(data, input, output, accSettings, true);
|
||||
} else if (OpenPgpApi.ACTION_DECRYPT_VERIFY.equals(action)) {
|
||||
return decryptAndVerifyImpl(data, input, output, appSettings);
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Set<Long> allowedKeyIds =
|
||||
ProviderHelper.getAllKeyIdsForApp(mContext,
|
||||
ApiAccounts.buildBaseUri(currentPkg));
|
||||
return decryptAndVerifyImpl(data, input, output, allowedKeyIds);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
|
||||
return getKeyImpl(data);
|
||||
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
@ -27,12 +27,14 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -44,10 +46,6 @@ import java.util.Arrays;
|
||||
public abstract class RemoteService extends Service {
|
||||
Context mContext;
|
||||
|
||||
private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
|
||||
private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
|
||||
|
||||
|
||||
public Context getContext() {
|
||||
return mContext;
|
||||
}
|
||||
@ -55,13 +53,10 @@ public abstract class RemoteService extends Service {
|
||||
protected Intent isAllowed(Intent data) {
|
||||
try {
|
||||
if (isCallerAllowed(false)) {
|
||||
|
||||
return null;
|
||||
} else {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(
|
||||
Binder.getCallingUid());
|
||||
// TODO: currently simply uses first entry
|
||||
String packageName = callingPackages[0];
|
||||
String packageName = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
|
||||
|
||||
byte[] packageSignature;
|
||||
try {
|
||||
@ -83,8 +78,9 @@ public abstract class RemoteService extends Service {
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
||||
PRIVATE_REQUEST_CODE_REGISTER, intent, 0);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
@ -99,11 +95,12 @@ public abstract class RemoteService extends Service {
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE,
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
getString(R.string.api_error_wrong_signature));
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
||||
PRIVATE_REQUEST_CODE_ERROR, intent, 0);
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
@ -125,27 +122,57 @@ public abstract class RemoteService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves AppSettings from database for the application calling this remote service
|
||||
* Returns package name associated with the UID, which is assigned to the process that sent you the
|
||||
* current transaction that is being processed :)
|
||||
*
|
||||
* @return package name
|
||||
*/
|
||||
protected String getCurrentCallingPackage() {
|
||||
// TODO:
|
||||
// callingPackages contains more than one entry when sharedUserId has been used...
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
||||
String currentPkg = callingPackages[0];
|
||||
Log.d(Constants.TAG, "currentPkg: " + currentPkg);
|
||||
|
||||
return currentPkg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves AccountSettings from database for the application calling this remote service
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected AppSettings getAppSettings() {
|
||||
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
||||
protected AccountSettings getAccSettings(String accountName) {
|
||||
String currentPkg = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "accountName: " + accountName);
|
||||
|
||||
// get app settings for this package
|
||||
for (int i = 0; i < callingPackages.length; i++) {
|
||||
String currentPkg = callingPackages[i];
|
||||
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
||||
|
||||
Uri uri = KeychainContract.ApiApps.buildByPackageNameUri(currentPkg);
|
||||
AccountSettings settings = ProviderHelper.getApiAccountSettings(this, uri);
|
||||
|
||||
AppSettings settings = ProviderHelper.getApiAppSettings(this, uri);
|
||||
return settings; // can be null!
|
||||
}
|
||||
|
||||
if (settings != null) {
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
protected Intent getCreateAccountIntent(Intent data, String accountName) {
|
||||
String packageName = getCurrentCallingPackage();
|
||||
Log.d(Constants.TAG, "accountName: " + accountName);
|
||||
|
||||
return null;
|
||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||
intent.setAction(RemoteServiceActivity.ACTION_CREATE_ACCOUNT);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_ACC_NAME, accountName);
|
||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||
|
||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
|
||||
// return PendingIntent to be executed by client
|
||||
Intent result = new Intent();
|
||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,7 +207,7 @@ public abstract class RemoteService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "Caller is NOT allowed!");
|
||||
Log.d(Constants.TAG, "Uid is NOT allowed!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -192,7 +219,7 @@ public abstract class RemoteService extends Service {
|
||||
* @throws WrongPackageSignatureException
|
||||
*/
|
||||
private boolean isPackageAllowed(String packageName) throws WrongPackageSignatureException {
|
||||
Log.d(Constants.TAG, "packageName: " + packageName);
|
||||
Log.d(Constants.TAG, "isPackageAllowed packageName: " + packageName);
|
||||
|
||||
ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this);
|
||||
Log.d(Constants.TAG, "allowed: " + allowedPkgs);
|
||||
@ -216,10 +243,12 @@ public abstract class RemoteService extends Service {
|
||||
return true;
|
||||
} else {
|
||||
throw new WrongPackageSignatureException(
|
||||
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not equals signature from database)");
|
||||
"PACKAGE NOT ALLOWED! Signature wrong! (Signature not " +
|
||||
"equals signature from database)");
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "Package is NOT allowed! packageName: " + packageName);
|
||||
return false;
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
public class WrongPackageSignatureException extends Exception {
|
||||
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@ -24,16 +24,18 @@ import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AppSettingsActivity extends ActionBarActivity {
|
||||
private Uri mAppUri;
|
||||
public class AccountSettingsActivity extends ActionBarActivity {
|
||||
private Uri mAccountUri;
|
||||
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
private AccountSettingsFragment mAccountSettingsFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -50,57 +52,58 @@ public class AppSettingsActivity extends ActionBarActivity {
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_app_settings_activity);
|
||||
setContentView(R.layout.api_account_settings_activity);
|
||||
|
||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_account_settings_fragment);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mAppUri = intent.getData();
|
||||
if (mAppUri == null) {
|
||||
mAccountUri = intent.getData();
|
||||
if (mAccountUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mAppUri);
|
||||
loadData(mAppUri);
|
||||
Log.d(Constants.TAG, "uri: " + mAccountUri);
|
||||
loadData(savedInstanceState, mAccountUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.api_app_settings, menu);
|
||||
getMenuInflater().inflate(R.menu.api_account_settings, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_api_settings_revoke:
|
||||
revokeAccess();
|
||||
case R.id.menu_account_settings_delete:
|
||||
deleteAccount();
|
||||
return true;
|
||||
case R.id.menu_api_settings_cancel:
|
||||
case R.id.menu_account_settings_cancel:
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void loadData(Uri appUri) {
|
||||
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
private void loadData(Bundle savedInstanceState, Uri accountUri) {
|
||||
// TODO: load this also like other fragment with newInstance arguments?
|
||||
AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri);
|
||||
mAccountSettingsFragment.setAccSettings(settings);
|
||||
}
|
||||
|
||||
private void revokeAccess() {
|
||||
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
|
||||
private void deleteAccount() {
|
||||
if (getContentResolver().delete(mAccountUri, null, null) <= 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri);
|
||||
ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri);
|
||||
|
||||
finish();
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.ui.EditKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
import org.sufficientlysecure.keychain.util.AlgorithmNames;
|
||||
|
||||
public class AccountSettingsFragment extends Fragment implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
|
||||
|
||||
private static final int REQUEST_CODE_CREATE_KEY = 0x00008884;
|
||||
|
||||
// model
|
||||
private AccountSettings mAccSettings;
|
||||
|
||||
// view
|
||||
private TextView mAccNameView;
|
||||
private Spinner mEncryptionAlgorithm;
|
||||
private Spinner mHashAlgorithm;
|
||||
private Spinner mCompression;
|
||||
|
||||
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
|
||||
private BootstrapButton mCreateKeyButton;
|
||||
|
||||
KeyValueSpinnerAdapter mEncryptionAdapter;
|
||||
KeyValueSpinnerAdapter mHashAdapter;
|
||||
KeyValueSpinnerAdapter mCompressionAdapter;
|
||||
|
||||
public AccountSettings getAccSettings() {
|
||||
return mAccSettings;
|
||||
}
|
||||
|
||||
public void setAccSettings(AccountSettings accountSettings) {
|
||||
this.mAccSettings = accountSettings;
|
||||
|
||||
mAccNameView.setText(accountSettings.getAccountName());
|
||||
mSelectKeyFragment.selectKey(accountSettings.getKeyId());
|
||||
mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(accountSettings
|
||||
.getEncryptionAlgorithm()));
|
||||
mHashAlgorithm.setSelection(mHashAdapter.getPosition(accountSettings.getHashAlgorithm()));
|
||||
mCompression.setSelection(mCompressionAdapter.getPosition(accountSettings.getCompression()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.api_account_settings_fragment, container, false);
|
||||
initView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error String on key selection
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
public void setErrorOnSelectKeyFragment(String error) {
|
||||
mSelectKeyFragment.setError(error);
|
||||
}
|
||||
|
||||
private void initView(View view) {
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
|
||||
R.id.api_account_settings_select_key_fragment);
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
|
||||
mAccNameView = (TextView) view.findViewById(R.id.api_account_settings_acc_name);
|
||||
mEncryptionAlgorithm = (Spinner) view
|
||||
.findViewById(R.id.api_account_settings_encryption_algorithm);
|
||||
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_account_settings_hash_algorithm);
|
||||
mCompression = (Spinner) view.findViewById(R.id.api_account_settings_compression);
|
||||
mCreateKeyButton = (BootstrapButton) view.findViewById(R.id.api_account_settings_create_key);
|
||||
|
||||
mCreateKeyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
createKey();
|
||||
}
|
||||
});
|
||||
|
||||
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
|
||||
|
||||
mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
|
||||
algorithmNames.getEncryptionNames());
|
||||
mEncryptionAlgorithm.setAdapter(mEncryptionAdapter);
|
||||
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAccSettings.setEncryptionAlgorithm((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
|
||||
mHashAlgorithm.setAdapter(mHashAdapter);
|
||||
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAccSettings.setHashAlgorithm((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
|
||||
algorithmNames.getCompressionNames());
|
||||
mCompression.setAdapter(mCompressionAdapter);
|
||||
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAccSettings.setCompression((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createKey() {
|
||||
Intent intent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
intent.setAction(EditKeyActivity.ACTION_CREATE_KEY);
|
||||
intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true);
|
||||
// set default user id to account name TODO: not working currently in EditKey
|
||||
intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, mAccSettings.getAccountName());
|
||||
startActivityForResult(intent, REQUEST_CODE_CREATE_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_CREATE_KEY: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// select newly created key
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), data.getData());
|
||||
mSelectKeyFragment.selectKey(masterKeyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from select secret key fragment
|
||||
*/
|
||||
@Override
|
||||
public void onKeySelected(long secretKeyId) {
|
||||
mAccSettings.setKeyId(secretKeyId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.remote.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AccountsListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
|
||||
// This is the Adapter being used to display the list's data.
|
||||
AccountsAdapter mAdapter;
|
||||
|
||||
private Uri mDataUri;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static AccountsListFragment newInstance(Uri dataUri) {
|
||||
AccountsListFragment frag = new AccountsListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View layout = super.onCreateView(inflater, container,
|
||||
savedInstanceState);
|
||||
ListView lv = (ListView) layout.findViewById(android.R.id.list);
|
||||
ViewGroup parent = (ViewGroup) lv.getParent();
|
||||
|
||||
/*
|
||||
* http://stackoverflow.com/a/15880684
|
||||
* Remove ListView and add FixedListView in its place.
|
||||
* This is done here programatically to be still able to use the progressBar of ListFragment.
|
||||
*
|
||||
* We want FixedListView to be able to put this ListFragment inside a ScrollView
|
||||
*/
|
||||
int lvIndex = parent.indexOfChild(lv);
|
||||
parent.removeViewAt(lvIndex);
|
||||
FixedListView newLv = new FixedListView(getActivity());
|
||||
newLv.setId(android.R.id.list);
|
||||
parent.addView(newLv, lvIndex, lv.getLayoutParams());
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
|
||||
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
String selectedAccountName = mAdapter.getItemAccountName(position);
|
||||
Uri accountUri = mDataUri.buildUpon().appendEncodedPath(selectedAccountName).build();
|
||||
Log.d(Constants.TAG, "accountUri: " + accountUri);
|
||||
|
||||
// edit account settings
|
||||
Intent intent = new Intent(getActivity(), AccountSettingsActivity.class);
|
||||
intent.setData(accountUri);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.api_settings_accounts_empty));
|
||||
|
||||
// We have a menu item to show in action bar.
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// Create an empty adapter we will use to display the loaded data.
|
||||
mAdapter = new AccountsAdapter(getActivity(), null, 0);
|
||||
setListAdapter(mAdapter);
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
// These are the Contacts rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[]{
|
||||
KeychainContract.ApiAccounts._ID, // 0
|
||||
KeychainContract.ApiAccounts.ACCOUNT_NAME // 1
|
||||
};
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null,
|
||||
KeychainContract.ApiAccounts.ACCOUNT_NAME + " COLLATE LOCALIZED ASC");
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
private class AccountsAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
public AccountsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to CursorAdapter.getItemId().
|
||||
* Required to build Uris for api app view, which is not based on row ids
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public String getItemAccountName(int position) {
|
||||
if (mDataValid && mCursor != null) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
return mCursor.getString(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView text = (TextView) view.findViewById(R.id.api_accounts_adapter_item_name);
|
||||
|
||||
String accountName = cursor.getString(1);
|
||||
text.setText(accountName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.api_accounts_adapter_list_item, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.remote.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class AppSettingsActivity extends ActionBarActivity {
|
||||
private Uri mAppUri;
|
||||
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
private AccountsListFragment mAccountsListFragment;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// let the actionbar look like Android's contact app
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setIcon(android.R.color.transparent);
|
||||
actionBar.setHomeButtonEnabled(true);
|
||||
|
||||
setContentView(R.layout.api_app_settings_activity);
|
||||
|
||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mAppUri = intent.getData();
|
||||
if (mAppUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mAppUri);
|
||||
loadData(savedInstanceState, mAppUri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.api_app_settings, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_api_settings_revoke:
|
||||
revokeAccess();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
||||
// TODO: load this also like other fragment with newInstance arguments?
|
||||
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
|
||||
String appName;
|
||||
PackageManager pm = getPackageManager();
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(settings.getPackageName(), 0);
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = settings.getPackageName();
|
||||
}
|
||||
setTitle(appName);
|
||||
|
||||
Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build();
|
||||
Log.d(Constants.TAG, "accountsUri: " + accountsUri);
|
||||
startListFragment(savedInstanceState, accountsUri);
|
||||
}
|
||||
|
||||
private void startListFragment(Bundle savedInstanceState, Uri dataUri) {
|
||||
// However, if we're being restored from a previous state,
|
||||
// then we don't need to do anything and should return or else
|
||||
// we could end up with overlapping fragments.
|
||||
if (savedInstanceState != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an instance of the fragment
|
||||
mAccountsListFragment = AccountsListFragment.newInstance(dataUri);
|
||||
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.api_accounts_list_fragment, mAccountsListFragment)
|
||||
.commitAllowingStateLoss();
|
||||
// do it immediately!
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
private void revokeAccess() {
|
||||
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class AppSettingsFragment extends Fragment {
|
||||
|
||||
// model
|
||||
private AppSettings mAppSettings;
|
||||
|
||||
// view
|
||||
private TextView mAppNameView;
|
||||
private ImageView mAppIconView;
|
||||
private TextView mPackageName;
|
||||
private TextView mPackageSignature;
|
||||
|
||||
public AppSettings getAppSettings() {
|
||||
return mAppSettings;
|
||||
}
|
||||
|
||||
public void setAppSettings(AppSettings appSettings) {
|
||||
this.mAppSettings = appSettings;
|
||||
updateView(appSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
|
||||
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
|
||||
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
|
||||
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
|
||||
mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void updateView(AppSettings appSettings) {
|
||||
// get application name and icon from package manager
|
||||
String appName;
|
||||
Drawable appIcon = null;
|
||||
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0);
|
||||
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
appIcon = pm.getApplicationIcon(ai);
|
||||
} catch (NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = appSettings.getPackageName();
|
||||
}
|
||||
mAppNameView.setText(appName);
|
||||
mAppIconView.setImageDrawable(appIcon);
|
||||
|
||||
// advanced info: package name
|
||||
mPackageName.setText(appSettings.getPackageName());
|
||||
|
||||
// advanced info: package signature SHA-256
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(appSettings.getPackageSignature());
|
||||
byte[] digest = md.digest();
|
||||
String signature = new String(Hex.encode(digest));
|
||||
|
||||
mPackageSignature.setText(signature);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(Constants.TAG, "Should not happen!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -15,13 +15,13 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.os.Bundle;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.DrawerActivity;
|
||||
|
||||
public class RegisteredAppsListActivity extends DrawerActivity {
|
||||
public class AppsListActivity extends DrawerActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
@ -15,10 +15,12 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -26,14 +28,20 @@ import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
|
||||
public class RegisteredAppsListFragment extends ListFragment implements
|
||||
public class AppsListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
// This is the Adapter being used to display the list's data.
|
||||
@ -46,9 +54,10 @@ public class RegisteredAppsListFragment extends ListFragment implements
|
||||
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
String selectedPackageName = mAdapter.getItemPackageName(position);
|
||||
// edit app settings
|
||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||
intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
@ -70,7 +79,10 @@ public class RegisteredAppsListFragment extends ListFragment implements
|
||||
}
|
||||
|
||||
// These are the Contacts rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[]{ApiApps._ID, ApiApps.PACKAGE_NAME};
|
||||
static final String[] PROJECTION = new String[]{
|
||||
ApiApps._ID, // 0
|
||||
ApiApps.PACKAGE_NAME // 1
|
||||
};
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
@ -98,4 +110,65 @@ public class RegisteredAppsListFragment extends ListFragment implements
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
private class RegisteredAppsAdapter extends CursorAdapter {
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private PackageManager mPM;
|
||||
|
||||
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mPM = context.getApplicationContext().getPackageManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to CursorAdapter.getItemId().
|
||||
* Required to build Uris for api app view, which is not based on row ids
|
||||
*
|
||||
* @param position
|
||||
* @return
|
||||
*/
|
||||
public String getItemPackageName(int position) {
|
||||
if (mDataValid && mCursor != null) {
|
||||
if (mCursor.moveToPosition(position)) {
|
||||
return mCursor.getString(1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
|
||||
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
|
||||
|
||||
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
|
||||
if (packageName != null) {
|
||||
// get application name
|
||||
try {
|
||||
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
|
||||
|
||||
text.setText(mPM.getApplicationLabel(ai));
|
||||
icon.setImageDrawable(mPM.getApplicationIcon(ai));
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
// fallback
|
||||
text.setText(packageName);
|
||||
}
|
||||
} else {
|
||||
// fallback
|
||||
text.setText(packageName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@ -24,6 +24,7 @@ import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
@ -31,7 +32,10 @@ import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -41,6 +45,8 @@ import java.util.ArrayList;
|
||||
public class RemoteServiceActivity extends ActionBarActivity {
|
||||
|
||||
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
|
||||
public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX
|
||||
+ "API_ACTIVITY_CREATE_ACCOUNT";
|
||||
public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX
|
||||
+ "API_ACTIVITY_CACHE_PASSPHRASE";
|
||||
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
|
||||
@ -57,6 +63,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
// register action
|
||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
|
||||
// create acc action
|
||||
public static final String EXTRA_ACC_NAME = "acc_name";
|
||||
// select pub keys action
|
||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
||||
public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids";
|
||||
@ -65,7 +73,9 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||
|
||||
// register view
|
||||
private AppSettingsFragment mSettingsFragment;
|
||||
private AppSettingsFragment mAppSettingsFragment;
|
||||
// create acc view
|
||||
private AccountSettingsFragment mAccSettingsFragment;
|
||||
// select pub keys view
|
||||
private SelectPublicKeyFragment mSelectFragment;
|
||||
|
||||
@ -85,6 +95,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
if (ACTION_REGISTER.equals(action)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||
Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
@ -94,13 +105,52 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
public void onClick(View v) {
|
||||
// Allow
|
||||
|
||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
||||
mAppSettingsFragment.getAppSettings());
|
||||
|
||||
// give data through for new service call
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}, R.string.api_register_disallow, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.api_remote_register_app);
|
||||
|
||||
mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
|
||||
AppSettings settings = new AppSettings(packageName, packageSignature);
|
||||
mAppSettingsFragment.setAppSettings(settings);
|
||||
} else if (ACTION_CREATE_ACCOUNT.equals(action)) {
|
||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||
final String accName = extras.getString(EXTRA_ACC_NAME);
|
||||
|
||||
// Inflate a "Done"/"Cancel" custom action bar view
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||
R.string.api_settings_save, R.drawable.ic_action_done,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Save
|
||||
|
||||
// user needs to select a key!
|
||||
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
|
||||
mSettingsFragment.setErrorOnSelectKeyFragment(
|
||||
if (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) {
|
||||
mAccSettingsFragment.setErrorOnSelectKeyFragment(
|
||||
getString(R.string.api_register_error_select_key));
|
||||
} else {
|
||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
||||
mSettingsFragment.getAppSettings());
|
||||
ProviderHelper.insertApiAccount(RemoteServiceActivity.this,
|
||||
KeychainContract.ApiAccounts.buildBaseUri(packageName),
|
||||
mAccSettingsFragment.getAccSettings());
|
||||
|
||||
// give data through for new service call
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
@ -108,29 +158,43 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
}, R.string.api_register_disallow, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Disallow
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}, R.string.api_settings_cancel, R.drawable.ic_action_cancel,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Cancel
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.api_app_register_activity);
|
||||
setContentView(R.layout.api_remote_create_account);
|
||||
|
||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_fragment);
|
||||
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||
R.id.api_account_settings_fragment);
|
||||
|
||||
AppSettings settings = new AppSettings(packageName, packageSignature);
|
||||
mSettingsFragment.setAppSettings(settings);
|
||||
AccountSettings settings = new AccountSettings(accName);
|
||||
mAccSettingsFragment.setAccSettings(settings);
|
||||
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
|
||||
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
|
||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
final Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||
|
||||
PassphraseDialogFragment.show(this, secretKeyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
// return given params again, for calling the service method again
|
||||
RemoteServiceActivity.this.setResult(RESULT_OK, resultData);
|
||||
} else {
|
||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
RemoteServiceActivity.this.finish();
|
||||
}
|
||||
});
|
||||
|
||||
showPassphraseDialog(resultData, secretKeyId);
|
||||
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
|
||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||
ArrayList<String> missingUserIds = intent
|
||||
@ -185,7 +249,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
}
|
||||
);
|
||||
|
||||
setContentView(R.layout.api_app_select_pub_keys_activity);
|
||||
setContentView(R.layout.api_remote_select_pub_keys);
|
||||
|
||||
// set text on view
|
||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text);
|
||||
@ -227,7 +291,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
||||
}
|
||||
});
|
||||
|
||||
setContentView(R.layout.api_app_error_message);
|
||||
setContentView(R.layout.api_remote_error_message);
|
||||
|
||||
// set text on view
|
||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
|
@ -18,30 +18,61 @@
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import org.spongycastle.openpgp.*;
|
||||
|
||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPObjectFactory;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.PGPUtil;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.*;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpImportExport;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyOperation;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.DataStream;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.util.*;
|
||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.KeychainServiceListener;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||
import org.sufficientlysecure.keychain.util.ProgressScaler;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -84,35 +115,26 @@ public class KeychainIntentService extends IntentService
|
||||
// possible targets:
|
||||
public static final int TARGET_BYTES = 1;
|
||||
public static final int TARGET_URI = 2;
|
||||
public static final int TARGET_STREAM = 3;
|
||||
|
||||
// encrypt
|
||||
public static final String ENCRYPT_SECRET_KEY_ID = "secret_key_id";
|
||||
public static final String ENCRYPT_SIGNATURE_KEY_ID = "secret_key_id";
|
||||
public static final String ENCRYPT_USE_ASCII_ARMOR = "use_ascii_armor";
|
||||
public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids";
|
||||
public static final String ENCRYPT_COMPRESSION_ID = "compression_id";
|
||||
public static final String ENCRYPT_GENERATE_SIGNATURE = "generate_signature";
|
||||
public static final String ENCRYPT_SIGN_ONLY = "sign_only";
|
||||
public static final String ENCRYPT_MESSAGE_BYTES = "message_bytes";
|
||||
public static final String ENCRYPT_INPUT_FILE = "input_file";
|
||||
public static final String ENCRYPT_OUTPUT_FILE = "output_file";
|
||||
public static final String ENCRYPT_PROVIDER_URI = "provider_uri";
|
||||
public static final String ENCRYPT_SYMMETRIC_PASSPHRASE = "passphrase";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String DECRYPT_RETURN_BYTES = "return_binary";
|
||||
public static final String DECRYPT_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
|
||||
public static final String DECRYPT_PASSPHRASE = "passphrase";
|
||||
|
||||
// save keyring
|
||||
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
|
||||
public static final String SAVE_KEYRING_CURRENT_PASSPHRASE = "current_passphrase";
|
||||
public static final String SAVE_KEYRING_USER_IDS = "user_ids";
|
||||
public static final String SAVE_KEYRING_KEYS = "keys";
|
||||
public static final String SAVE_KEYRING_KEYS_USAGES = "keys_usages";
|
||||
public static final String SAVE_KEYRING_KEYS_EXPIRY_DATES = "keys_expiry_dates";
|
||||
public static final String SAVE_KEYRING_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String SAVE_KEYRING_PARCEL = "save_parcel";
|
||||
public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
|
||||
|
||||
|
||||
// generate key
|
||||
public static final String GENERATE_KEY_ALGORITHM = "algorithm";
|
||||
public static final String GENERATE_KEY_KEY_SIZE = "key_size";
|
||||
@ -128,10 +150,9 @@ public class KeychainIntentService extends IntentService
|
||||
// export key
|
||||
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
||||
public static final String EXPORT_FILENAME = "export_filename";
|
||||
public static final String EXPORT_KEY_TYPE = "export_key_type";
|
||||
public static final String EXPORT_SECRET = "export_secret";
|
||||
public static final String EXPORT_ALL = "export_all";
|
||||
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
||||
public static final String EXPORT_KEY_RING_ROW_ID = "export_key_rind_row_id";
|
||||
|
||||
// upload key
|
||||
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
|
||||
@ -150,17 +171,12 @@ public class KeychainIntentService extends IntentService
|
||||
*/
|
||||
// keys
|
||||
public static final String RESULT_NEW_KEY = "new_key";
|
||||
public static final String RESULT_NEW_KEY2 = "new_key2";
|
||||
public static final String RESULT_KEY_USAGES = "new_key_usages";
|
||||
|
||||
// encrypt
|
||||
public static final String RESULT_SIGNATURE_BYTES = "signature_data";
|
||||
public static final String RESULT_SIGNATURE_STRING = "signature_text";
|
||||
public static final String RESULT_ENCRYPTED_STRING = "encrypted_message";
|
||||
public static final String RESULT_ENCRYPTED_BYTES = "encrypted_data";
|
||||
public static final String RESULT_URI = "result_uri";
|
||||
public static final String RESULT_BYTES = "encrypted_data";
|
||||
|
||||
// decrypt/verify
|
||||
public static final String RESULT_DECRYPTED_STRING = "decrypted_message";
|
||||
public static final String RESULT_DECRYPTED_BYTES = "decrypted_data";
|
||||
public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature";
|
||||
|
||||
@ -172,10 +188,6 @@ public class KeychainIntentService extends IntentService
|
||||
// export
|
||||
public static final String RESULT_EXPORT = "exported";
|
||||
|
||||
// query
|
||||
public static final String RESULT_QUERY_KEY_DATA = "query_key_data";
|
||||
public static final String RESULT_QUERY_KEY_SEARCH_RESULT = "query_key_search_result";
|
||||
|
||||
Messenger mMessenger;
|
||||
|
||||
private boolean mIsCanceled;
|
||||
@ -225,20 +237,17 @@ public class KeychainIntentService extends IntentService
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
||||
String encryptionPassphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
||||
long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID);
|
||||
String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
|
||||
|
||||
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
||||
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
||||
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
|
||||
boolean generateSignature = data.getBoolean(ENCRYPT_GENERATE_SIGNATURE);
|
||||
boolean signOnly = data.getBoolean(ENCRYPT_SIGN_ONLY);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
OutputStream outStream = null;
|
||||
String streamFilename = null;
|
||||
InputStream inStream;
|
||||
long inLength;
|
||||
InputData inputData;
|
||||
OutputStream outStream;
|
||||
// String streamFilename = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* encrypting bytes directly */
|
||||
byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES);
|
||||
@ -270,29 +279,30 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM: /* Encrypting stream from content uri */
|
||||
Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||
|
||||
// InputStream
|
||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
inLength = PgpHelper.getLengthOfStream(in);
|
||||
inputData = new InputData(in, inLength);
|
||||
|
||||
// OutputStream
|
||||
try {
|
||||
while (true) {
|
||||
streamFilename = PgpHelper.generateRandomFilename(32);
|
||||
if (streamFilename == null) {
|
||||
throw new PgpGeneralException("couldn't generate random file name");
|
||||
}
|
||||
openFileInput(streamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
|
||||
break;
|
||||
// TODO: not used currently
|
||||
// case TARGET_STREAM: /* Encrypting stream from content uri */
|
||||
// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||
//
|
||||
// // InputStream
|
||||
// InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
// inLength = PgpHelper.getLengthOfStream(in);
|
||||
// inputData = new InputData(in, inLength);
|
||||
//
|
||||
// // OutputStream
|
||||
// try {
|
||||
// while (true) {
|
||||
// streamFilename = PgpHelper.generateRandomFilename(32);
|
||||
// if (streamFilename == null) {
|
||||
// throw new PgpGeneralException("couldn't generate random file name");
|
||||
// }
|
||||
// openFileInput(streamFilename).close();
|
||||
// }
|
||||
// } catch (FileNotFoundException e) {
|
||||
// // found a name that isn't used yet
|
||||
// }
|
||||
// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
//
|
||||
// break;
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
@ -304,45 +314,20 @@ public class KeychainIntentService extends IntentService
|
||||
new PgpSignEncrypt.Builder(this, inputData, outStream);
|
||||
builder.progress(this);
|
||||
|
||||
if (generateSignature) {
|
||||
Log.d(Constants.TAG, "generating signature...");
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.compressionId(compressionId)
|
||||
.symmetricEncryptionAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.encryptionKeyIds(encryptionKeyIds)
|
||||
.symmetricPassphrase(symmetricPassphrase)
|
||||
.signatureKeyId(signatureKeyId)
|
||||
.signatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(
|
||||
PassphraseCacheService.getCachedPassphrase(this, signatureKeyId));
|
||||
|
||||
builder.build().generateSignature();
|
||||
} else if (signOnly) {
|
||||
Log.d(Constants.TAG, "sign only...");
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().execute();
|
||||
} else {
|
||||
Log.d(Constants.TAG, "encrypt...");
|
||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||
.compressionId(compressionId)
|
||||
.symmetricEncryptionAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||
.encryptionKeyIds(encryptionKeyIds)
|
||||
.encryptionPassphrase(encryptionPassphrase)
|
||||
.signatureKeyId(secretKeyId)
|
||||
.signatureHashAlgorithm(
|
||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||
.signaturePassphrase(
|
||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
|
||||
builder.build().execute();
|
||||
}
|
||||
builder.build().execute();
|
||||
|
||||
outStream.close();
|
||||
|
||||
@ -352,33 +337,20 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
switch (target) {
|
||||
case TARGET_BYTES:
|
||||
if (useAsciiArmor) {
|
||||
String output = new String(
|
||||
((ByteArrayOutputStream) outStream).toByteArray());
|
||||
if (generateSignature) {
|
||||
resultData.putString(RESULT_SIGNATURE_STRING, output);
|
||||
} else {
|
||||
resultData.putString(RESULT_ENCRYPTED_STRING, output);
|
||||
}
|
||||
} else {
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
if (generateSignature) {
|
||||
resultData.putByteArray(RESULT_SIGNATURE_BYTES, output);
|
||||
} else {
|
||||
resultData.putByteArray(RESULT_ENCRYPTED_BYTES, output);
|
||||
}
|
||||
}
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
|
||||
resultData.putByteArray(RESULT_BYTES, output);
|
||||
|
||||
break;
|
||||
case TARGET_URI:
|
||||
// nothing, file was written, just send okay
|
||||
|
||||
break;
|
||||
case TARGET_STREAM:
|
||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
resultData.putString(RESULT_URI, uri);
|
||||
|
||||
break;
|
||||
// case TARGET_STREAM:
|
||||
// String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
// resultData.putString(RESULT_URI, uri);
|
||||
//
|
||||
// break;
|
||||
}
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
@ -392,15 +364,13 @@ public class KeychainIntentService extends IntentService
|
||||
/* Input */
|
||||
int target = data.getInt(TARGET);
|
||||
|
||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
||||
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
|
||||
boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
|
||||
boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
|
||||
String passphrase = data.getString(DECRYPT_PASSPHRASE);
|
||||
|
||||
InputStream inStream = null;
|
||||
long inLength = -1;
|
||||
InputData inputData = null;
|
||||
OutputStream outStream = null;
|
||||
InputStream inStream;
|
||||
long inLength;
|
||||
InputData inputData;
|
||||
OutputStream outStream;
|
||||
String streamFilename = null;
|
||||
switch (target) {
|
||||
case TARGET_BYTES: /* decrypting bytes directly */
|
||||
@ -435,29 +405,30 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
break;
|
||||
|
||||
case TARGET_STREAM: /* decrypting stream from content uri */
|
||||
Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||
|
||||
// InputStream
|
||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
inLength = PgpHelper.getLengthOfStream(in);
|
||||
inputData = new InputData(in, inLength);
|
||||
|
||||
// OutputStream
|
||||
try {
|
||||
while (true) {
|
||||
streamFilename = PgpHelper.generateRandomFilename(32);
|
||||
if (streamFilename == null) {
|
||||
throw new PgpGeneralException("couldn't generate random file name");
|
||||
}
|
||||
openFileInput(streamFilename).close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// found a name that isn't used yet
|
||||
}
|
||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
|
||||
break;
|
||||
// TODO: not used, maybe contains code useful for new decrypt method for files?
|
||||
// case TARGET_STREAM: /* decrypting stream from content uri */
|
||||
// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||
//
|
||||
// // InputStream
|
||||
// InputStream in = getContentResolver().openInputStream(providerUri);
|
||||
// inLength = PgpHelper.getLengthOfStream(in);
|
||||
// inputData = new InputData(in, inLength);
|
||||
//
|
||||
// // OutputStream
|
||||
// try {
|
||||
// while (true) {
|
||||
// streamFilename = PgpHelper.generateRandomFilename(32);
|
||||
// if (streamFilename == null) {
|
||||
// throw new PgpGeneralException("couldn't generate random file name");
|
||||
// }
|
||||
// openFileInput(streamFilename).close();
|
||||
// }
|
||||
// } catch (FileNotFoundException e) {
|
||||
// // found a name that isn't used yet
|
||||
// }
|
||||
// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||
//
|
||||
// break;
|
||||
|
||||
default:
|
||||
throw new PgpGeneralException("No target choosen!");
|
||||
@ -473,8 +444,8 @@ public class KeychainIntentService extends IntentService
|
||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
|
||||
builder.progressDialogUpdater(this);
|
||||
|
||||
builder.assumeSymmetric(assumeSymmetricEncryption)
|
||||
.passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
||||
builder.allowSymmetricDecryption(true)
|
||||
.passphrase(passphrase);
|
||||
|
||||
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||
|
||||
@ -486,25 +457,18 @@ public class KeychainIntentService extends IntentService
|
||||
|
||||
switch (target) {
|
||||
case TARGET_BYTES:
|
||||
if (returnBytes) {
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
|
||||
} else {
|
||||
String output = new String(
|
||||
((ByteArrayOutputStream) outStream).toByteArray());
|
||||
resultData.putString(RESULT_DECRYPTED_STRING, output);
|
||||
}
|
||||
|
||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||
resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
|
||||
break;
|
||||
case TARGET_URI:
|
||||
// nothing, file was written, just send okay and verification bundle
|
||||
|
||||
break;
|
||||
case TARGET_STREAM:
|
||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
resultData.putString(RESULT_URI, uri);
|
||||
|
||||
break;
|
||||
// case TARGET_STREAM:
|
||||
// String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||
// resultData.putString(RESULT_URI, uri);
|
||||
//
|
||||
// break;
|
||||
}
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
@ -516,38 +480,42 @@ public class KeychainIntentService extends IntentService
|
||||
} else if (ACTION_SAVE_KEYRING.equals(action)) {
|
||||
try {
|
||||
/* Input */
|
||||
String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE);
|
||||
String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE);
|
||||
SaveKeyringParcel saveParams = data.getParcelable(SAVE_KEYRING_PARCEL);
|
||||
String oldPassphrase = saveParams.oldPassphrase;
|
||||
String newPassphrase = saveParams.newPassphrase;
|
||||
boolean canSign = true;
|
||||
|
||||
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
|
||||
canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
|
||||
}
|
||||
|
||||
if (newPassPhrase == null) {
|
||||
newPassPhrase = oldPassPhrase;
|
||||
if (newPassphrase == null) {
|
||||
newPassphrase = oldPassphrase;
|
||||
}
|
||||
ArrayList<String> userIds = data.getStringArrayList(SAVE_KEYRING_USER_IDS);
|
||||
ArrayList<PGPSecretKey> keys = PgpConversionHelper.BytesToPGPSecretKeyList(data
|
||||
.getByteArray(SAVE_KEYRING_KEYS));
|
||||
ArrayList<Integer> keysUsages = data.getIntegerArrayList(SAVE_KEYRING_KEYS_USAGES);
|
||||
ArrayList<GregorianCalendar> keysExpiryDates =
|
||||
(ArrayList<GregorianCalendar>) data.getSerializable(SAVE_KEYRING_KEYS_EXPIRY_DATES);
|
||||
|
||||
long masterKeyId = data.getLong(SAVE_KEYRING_MASTER_KEY_ID);
|
||||
long masterKeyId = saveParams.keys.get(0).getKeyID();
|
||||
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
||||
/* Operation */
|
||||
if (!canSign) {
|
||||
keyOperations.changeSecretKeyPassphrase(
|
||||
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
|
||||
oldPassPhrase, newPassPhrase);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 50, 100));
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
||||
keyRing = keyOperations.changeSecretKeyPassphrase(keyRing,
|
||||
oldPassphrase, newPassphrase);
|
||||
setProgress(R.string.progress_saving_key_ring, 50, 100);
|
||||
ProviderHelper.saveKeyRing(this, keyRing);
|
||||
setProgress(R.string.progress_done, 100, 100);
|
||||
} else {
|
||||
PGPPublicKey pubkey = ProviderHelper.getPGPPublicKeyByKeyId(this, masterKeyId);
|
||||
keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates,
|
||||
pubkey, oldPassPhrase, newPassPhrase);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100));
|
||||
PGPSecretKeyRing privkey = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
||||
PGPPublicKeyRing pubkey = ProviderHelper.getPGPPublicKeyRing(this, masterKeyId);
|
||||
PgpKeyOperation.Pair<PGPSecretKeyRing,PGPPublicKeyRing> pair =
|
||||
keyOperations.buildSecretKey(privkey, pubkey, saveParams);
|
||||
setProgress(R.string.progress_saving_key_ring, 90, 100);
|
||||
// save the pair
|
||||
ProviderHelper.saveKeyRing(this, pair.second, pair.first);
|
||||
setProgress(R.string.progress_done, 100, 100);
|
||||
}
|
||||
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase);
|
||||
PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassphrase);
|
||||
|
||||
/* Output */
|
||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||
@ -563,7 +531,7 @@ public class KeychainIntentService extends IntentService
|
||||
boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
|
||||
|
||||
/* Operation */
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||
PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize,
|
||||
passphrase, masterKey);
|
||||
|
||||
@ -583,24 +551,37 @@ public class KeychainIntentService extends IntentService
|
||||
try {
|
||||
/* Input */
|
||||
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
||||
ArrayList<PGPSecretKey> newKeys = new ArrayList<PGPSecretKey>();
|
||||
ArrayList<Integer> keyUsageList = new ArrayList<Integer>();
|
||||
|
||||
/* Operation */
|
||||
int keysTotal = 2;
|
||||
int keysTotal = 3;
|
||||
int keysCreated = 0;
|
||||
setProgress(
|
||||
getApplicationContext().getResources().
|
||||
getQuantityString(R.plurals.progress_generating, keysTotal),
|
||||
keysCreated,
|
||||
keysTotal);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
||||
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||
|
||||
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||
4096, passphrase, true);
|
||||
newKeys.add(masterKey);
|
||||
keyUsageList.add(KeyFlags.CERTIFY_OTHER);
|
||||
keysCreated++;
|
||||
setProgress(keysCreated, keysTotal);
|
||||
|
||||
PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||
4096, passphrase, false);
|
||||
newKeys.add(subKey);
|
||||
keyUsageList.add(KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
|
||||
keysCreated++;
|
||||
setProgress(keysCreated, keysTotal);
|
||||
|
||||
subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||
4096, passphrase, false);
|
||||
newKeys.add(subKey);
|
||||
keyUsageList.add(KeyFlags.SIGN_DATA);
|
||||
keysCreated++;
|
||||
setProgress(keysCreated, keysTotal);
|
||||
|
||||
@ -608,11 +589,11 @@ public class KeychainIntentService extends IntentService
|
||||
// for sign
|
||||
|
||||
/* Output */
|
||||
|
||||
Bundle resultData = new Bundle();
|
||||
resultData.putByteArray(RESULT_NEW_KEY,
|
||||
PgpConversionHelper.PGPSecretKeyToBytes(masterKey));
|
||||
resultData.putByteArray(RESULT_NEW_KEY2,
|
||||
PgpConversionHelper.PGPSecretKeyToBytes(subKey));
|
||||
PgpConversionHelper.PGPSecretKeyArrayListToBytes(newKeys));
|
||||
resultData.putIntegerArrayList(RESULT_KEY_USAGES, keyUsageList);
|
||||
|
||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||
|
||||
@ -657,53 +638,50 @@ public class KeychainIntentService extends IntentService
|
||||
} else if (ACTION_EXPORT_KEYRING.equals(action)) {
|
||||
try {
|
||||
|
||||
/* Input */
|
||||
int keyType = Id.type.public_key;
|
||||
if (data.containsKey(EXPORT_KEY_TYPE)) {
|
||||
keyType = data.getInt(EXPORT_KEY_TYPE);
|
||||
}
|
||||
|
||||
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
||||
long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||
String outputFile = data.getString(EXPORT_FILENAME);
|
||||
|
||||
long[] rowIds = new long[0];
|
||||
|
||||
// If not exporting all keys get the rowIds of the keys to export from the intent
|
||||
// If not exporting all keys get the masterKeyIds of the keys to export from the intent
|
||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||
if (!exportAll) {
|
||||
rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID);
|
||||
}
|
||||
|
||||
/* Operation */
|
||||
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(outputFile)) {
|
||||
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
|
||||
}
|
||||
|
||||
// OutputStream
|
||||
FileOutputStream outStream = new FileOutputStream(outputFile);
|
||||
ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
|
||||
ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>();
|
||||
|
||||
ArrayList<Long> keyRingRowIds = new ArrayList<Long>();
|
||||
if (exportAll) {
|
||||
|
||||
// get all key ring row ids based on export type
|
||||
if (keyType == Id.type.public_key) {
|
||||
keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this);
|
||||
} else {
|
||||
keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this);
|
||||
}
|
||||
} else {
|
||||
for (long rowId : rowIds) {
|
||||
keyRingRowIds.add(rowId);
|
||||
String selection = null;
|
||||
if(!exportAll) {
|
||||
selection = KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN( ";
|
||||
for(long l : masterKeyIds) {
|
||||
selection += Long.toString(l) + ",";
|
||||
}
|
||||
selection = selection.substring(0, selection.length()-1) + " )";
|
||||
}
|
||||
|
||||
Bundle resultData;
|
||||
Cursor cursor = getContentResolver().query(KeyRings.buildUnifiedKeyRingsUri(),
|
||||
new String[]{ KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET },
|
||||
selection, null, null);
|
||||
try {
|
||||
cursor.moveToFirst();
|
||||
do {
|
||||
// export public either way
|
||||
publicMasterKeyIds.add(cursor.getLong(0));
|
||||
// add secret if available (and requested)
|
||||
if(exportSecret && cursor.getInt(1) != 0)
|
||||
secretMasterKeyIds.add(cursor.getLong(0));
|
||||
} while(cursor.moveToNext());
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, this, this);
|
||||
|
||||
resultData = pgpImportExport
|
||||
.exportKeyRings(keyRingRowIds, keyType, outStream);
|
||||
Bundle resultData = pgpImportExport
|
||||
.exportKeyRings(publicMasterKeyIds, secretMasterKeyIds,
|
||||
new FileOutputStream(outputFile));
|
||||
|
||||
if (mIsCanceled) {
|
||||
boolean isDeleted = new File(outputFile).delete();
|
||||
@ -747,45 +725,54 @@ public class KeychainIntentService extends IntentService
|
||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||
|
||||
for (ImportKeysListEntry entry : entries) {
|
||||
byte[] downloadedKey = server.get(entry.getKeyId()).getBytes();
|
||||
// if available use complete fingerprint for get request
|
||||
byte[] downloadedKeyBytes;
|
||||
if (entry.getFingerPrintHex() != null) {
|
||||
downloadedKeyBytes = server.get("0x" + entry.getFingerPrintHex()).getBytes();
|
||||
} else {
|
||||
downloadedKeyBytes = server.get(entry.getKeyIdHex()).getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: copied from ImportKeysListLoader
|
||||
*
|
||||
*
|
||||
* this parses the downloaded key
|
||||
*/
|
||||
// need to have access to the bufferedInput, so we can reuse it for the possible
|
||||
// PGPObject chunks after the first one, e.g. files with several consecutive ASCII
|
||||
// armor blocks
|
||||
// create PGPKeyRing object based on downloaded armored key
|
||||
PGPKeyRing downloadedKey = null;
|
||||
BufferedInputStream bufferedInput =
|
||||
new BufferedInputStream(new ByteArrayInputStream(downloadedKey));
|
||||
try {
|
||||
new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
|
||||
if (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
|
||||
// read all available blocks... (asc files can contain many blocks with BEGIN END)
|
||||
while (bufferedInput.available() > 0) {
|
||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
||||
// get first object in block
|
||||
Object obj;
|
||||
if ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
|
||||
// go through all objects in this block
|
||||
Object obj;
|
||||
while ((obj = objectFactory.nextObject()) != null) {
|
||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
PGPKeyRing newKeyring = (PGPKeyRing) obj;
|
||||
|
||||
entry.setBytes(newKeyring.getEncoded());
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
if (obj instanceof PGPKeyRing) {
|
||||
downloadedKey = (PGPKeyRing) obj;
|
||||
} else {
|
||||
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(Constants.TAG, "Exception on parsing key file!", e);
|
||||
}
|
||||
|
||||
// verify downloaded key by comparing fingerprints
|
||||
if (entry.getFingerPrintHex() != null) {
|
||||
String downloadedKeyFp = PgpKeyHelper.convertFingerprintToHex(
|
||||
downloadedKey.getPublicKey().getFingerprint());
|
||||
if (downloadedKeyFp.equals(entry.getFingerPrintHex())) {
|
||||
Log.d(Constants.TAG, "fingerprint of downloaded key is the same as " +
|
||||
"the requested fingerprint!");
|
||||
} else {
|
||||
throw new PgpGeneralException("fingerprint of downloaded key is " +
|
||||
"NOT the same as the requested fingerprint!");
|
||||
}
|
||||
}
|
||||
|
||||
// save key bytes in entry object for doing the
|
||||
// actual import afterwards
|
||||
entry.setBytes(downloadedKey.getEncoded());
|
||||
}
|
||||
|
||||
|
||||
Intent importIntent = new Intent(this, KeychainIntentService.class);
|
||||
importIntent.setAction(ACTION_IMPORT_KEYRING);
|
||||
Bundle importData = new Bundle();
|
||||
@ -809,16 +796,24 @@ public class KeychainIntentService extends IntentService
|
||||
ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
|
||||
|
||||
/* Operation */
|
||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||
String signaturePassphrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||
masterKeyId);
|
||||
if (signaturePassphrase == null) {
|
||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||
}
|
||||
|
||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
||||
userIds, signaturePassPhrase);
|
||||
PgpKeyOperation keyOperation = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||
PGPPublicKeyRing publicRing = ProviderHelper.getPGPPublicKeyRing(this, pubKeyId);
|
||||
PGPPublicKey publicKey = publicRing.getPublicKey(pubKeyId);
|
||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(this,
|
||||
masterKeyId);
|
||||
publicKey = keyOperation.certifyKey(certificationKey, publicKey,
|
||||
userIds, signaturePassphrase);
|
||||
publicRing = PGPPublicKeyRing.insertPublicKey(publicRing, publicKey);
|
||||
|
||||
// store the signed key in our local cache
|
||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
||||
int retval = pgpImportExport.storeKeyRingInCache(signedPubKeyRing);
|
||||
int retval = pgpImportExport.storeKeyRingInCache(publicRing);
|
||||
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||
throw new PgpGeneralException("Failed to store signed key in local cache");
|
||||
}
|
||||
@ -835,6 +830,10 @@ public class KeychainIntentService extends IntentService
|
||||
if (this.mIsCanceled) {
|
||||
return;
|
||||
}
|
||||
// contextualize the exception, if necessary
|
||||
if (e instanceof PgpGeneralMsgIdException) {
|
||||
e = ((PgpGeneralMsgIdException) e).getContextualized(this);
|
||||
}
|
||||
Log.e(Constants.TAG, "ApgService Exception: ", e);
|
||||
e.printStackTrace();
|
||||
|
||||
|
@ -24,19 +24,29 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.*;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import org.spongycastle.openpgp.PGPException;
|
||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
import java.util.Date;
|
||||
@ -86,7 +96,7 @@ public class PassphraseCacheService extends Service {
|
||||
|
||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl());
|
||||
intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl());
|
||||
intent.putExtra(EXTRA_PASSPHRASE, passphrase);
|
||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
|
||||
@ -161,15 +171,11 @@ public class PassphraseCacheService extends Service {
|
||||
// try to get master key id which is used as an identifier for cached passphrases
|
||||
long masterKeyId = keyId;
|
||||
if (masterKeyId != Id.key.symmetric) {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(this, keyId);
|
||||
if (keyRing == null) {
|
||||
masterKeyId = ProviderHelper.getMasterKeyId(this,
|
||||
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)));
|
||||
// Failure
|
||||
if(masterKeyId == 0)
|
||||
return null;
|
||||
}
|
||||
PGPSecretKey masterKey = PgpKeyHelper.getMasterKey(keyRing);
|
||||
if (masterKey == null) {
|
||||
return null;
|
||||
}
|
||||
masterKeyId = masterKey.getKeyID();
|
||||
}
|
||||
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
|
||||
|
||||
@ -202,8 +208,7 @@ public class PassphraseCacheService extends Service {
|
||||
public static boolean hasPassphrase(Context context, long secretKeyId) {
|
||||
// check if the key has no passphrase
|
||||
try {
|
||||
PGPSecretKeyRing secRing = ProviderHelper
|
||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId);
|
||||
PGPSecretKeyRing secRing = ProviderHelper.getPGPSecretKeyRing(context, secretKeyId);
|
||||
PGPSecretKey secretKey = null;
|
||||
boolean foundValidKey = false;
|
||||
for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) {
|
||||
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Ash Hughes <ashes-iontach@hotmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
package org.sufficientlysecure.keychain.service;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
public class SaveKeyringParcel implements Parcelable {
|
||||
|
||||
public ArrayList<String> userIDs;
|
||||
public ArrayList<String> originalIDs;
|
||||
public ArrayList<String> deletedIDs;
|
||||
public boolean[] newIDs;
|
||||
public boolean primaryIDChanged;
|
||||
public boolean[] moddedKeys;
|
||||
public ArrayList<PGPSecretKey> deletedKeys;
|
||||
public ArrayList<GregorianCalendar> keysExpiryDates;
|
||||
public ArrayList<Integer> keysUsages;
|
||||
public String newPassphrase;
|
||||
public String oldPassphrase;
|
||||
public boolean[] newKeys;
|
||||
public ArrayList<PGPSecretKey> keys;
|
||||
public String originalPrimaryID;
|
||||
|
||||
public SaveKeyringParcel() {}
|
||||
|
||||
private SaveKeyringParcel(Parcel source) {
|
||||
userIDs = (ArrayList<String>) source.readSerializable();
|
||||
originalIDs = (ArrayList<String>) source.readSerializable();
|
||||
deletedIDs = (ArrayList<String>) source.readSerializable();
|
||||
newIDs = source.createBooleanArray();
|
||||
primaryIDChanged = source.readByte() != 0;
|
||||
moddedKeys = source.createBooleanArray();
|
||||
byte[] tmp = source.createByteArray();
|
||||
if (tmp == null) {
|
||||
deletedKeys = null;
|
||||
} else {
|
||||
deletedKeys = PgpConversionHelper.BytesToPGPSecretKeyList(tmp);
|
||||
}
|
||||
keysExpiryDates = (ArrayList<GregorianCalendar>) source.readSerializable();
|
||||
keysUsages = source.readArrayList(Integer.class.getClassLoader());
|
||||
newPassphrase = source.readString();
|
||||
oldPassphrase = source.readString();
|
||||
newKeys = source.createBooleanArray();
|
||||
keys = PgpConversionHelper.BytesToPGPSecretKeyList(source.createByteArray());
|
||||
originalPrimaryID = source.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel destination, int flags) {
|
||||
destination.writeSerializable(userIDs); //might not be the best method to store.
|
||||
destination.writeSerializable(originalIDs);
|
||||
destination.writeSerializable(deletedIDs);
|
||||
destination.writeBooleanArray(newIDs);
|
||||
destination.writeByte((byte) (primaryIDChanged ? 1 : 0));
|
||||
destination.writeBooleanArray(moddedKeys);
|
||||
byte[] tmp = null;
|
||||
if (deletedKeys.size() != 0) {
|
||||
tmp = PgpConversionHelper.PGPSecretKeyArrayListToBytes(deletedKeys);
|
||||
}
|
||||
destination.writeByteArray(tmp);
|
||||
destination.writeSerializable(keysExpiryDates);
|
||||
destination.writeList(keysUsages);
|
||||
destination.writeString(newPassphrase);
|
||||
destination.writeString(oldPassphrase);
|
||||
destination.writeBooleanArray(newKeys);
|
||||
destination.writeByteArray(PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
|
||||
destination.writeString(originalPrimaryID);
|
||||
}
|
||||
|
||||
public static final Creator<SaveKeyringParcel> CREATOR = new Creator<SaveKeyringParcel>() {
|
||||
public SaveKeyringParcel createFromParcel(final Parcel source) {
|
||||
return new SaveKeyringParcel(source);
|
||||
}
|
||||
|
||||
public SaveKeyringParcel[] newArray(final int size) {
|
||||
return new SaveKeyringParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -1,237 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.service.remote;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.*;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter;
|
||||
import org.sufficientlysecure.keychain.util.AlgorithmNames;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class AppSettingsFragment extends Fragment implements
|
||||
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback {
|
||||
|
||||
// model
|
||||
private AppSettings mAppSettings;
|
||||
|
||||
// view
|
||||
private LinearLayout mAdvancedSettingsContainer;
|
||||
private BootstrapButton mAdvancedSettingsButton;
|
||||
private TextView mAppNameView;
|
||||
private ImageView mAppIconView;
|
||||
private Spinner mEncryptionAlgorithm;
|
||||
private Spinner mHashAlgorithm;
|
||||
private Spinner mCompression;
|
||||
private TextView mPackageName;
|
||||
private TextView mPackageSignature;
|
||||
|
||||
private SelectSecretKeyLayoutFragment mSelectKeyFragment;
|
||||
|
||||
KeyValueSpinnerAdapter mEncryptionAdapter;
|
||||
KeyValueSpinnerAdapter mHashAdapter;
|
||||
KeyValueSpinnerAdapter mCompressionAdapter;
|
||||
|
||||
public AppSettings getAppSettings() {
|
||||
return mAppSettings;
|
||||
}
|
||||
|
||||
public void setAppSettings(AppSettings appSettings) {
|
||||
this.mAppSettings = appSettings;
|
||||
setPackage(appSettings.getPackageName());
|
||||
mPackageName.setText(appSettings.getPackageName());
|
||||
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(appSettings.getPackageSignature());
|
||||
byte[] digest = md.digest();
|
||||
String signature = new String(Hex.encode(digest));
|
||||
|
||||
mPackageSignature.setText(signature);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(Constants.TAG, "Should not happen!", e);
|
||||
}
|
||||
|
||||
mSelectKeyFragment.selectKey(appSettings.getKeyId());
|
||||
mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(appSettings
|
||||
.getEncryptionAlgorithm()));
|
||||
mHashAlgorithm.setSelection(mHashAdapter.getPosition(appSettings.getHashAlgorithm()));
|
||||
mCompression.setSelection(mCompressionAdapter.getPosition(appSettings.getCompression()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
|
||||
initView(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error String on key selection
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
public void setErrorOnSelectKeyFragment(String error) {
|
||||
mSelectKeyFragment.setError(error);
|
||||
}
|
||||
|
||||
private void initView(View view) {
|
||||
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById(
|
||||
R.id.api_app_settings_select_key_fragment);
|
||||
mSelectKeyFragment.setCallback(this);
|
||||
|
||||
mAdvancedSettingsButton = (BootstrapButton) view
|
||||
.findViewById(R.id.api_app_settings_advanced_button);
|
||||
mAdvancedSettingsContainer = (LinearLayout) view
|
||||
.findViewById(R.id.api_app_settings_advanced);
|
||||
|
||||
mAppNameView = (TextView) view.findViewById(R.id.api_app_settings_app_name);
|
||||
mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon);
|
||||
mEncryptionAlgorithm = (Spinner) view
|
||||
.findViewById(R.id.api_app_settings_encryption_algorithm);
|
||||
mHashAlgorithm = (Spinner) view.findViewById(R.id.api_app_settings_hash_algorithm);
|
||||
mCompression = (Spinner) view.findViewById(R.id.api_app_settings_compression);
|
||||
mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name);
|
||||
mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature);
|
||||
|
||||
AlgorithmNames algorithmNames = new AlgorithmNames(getActivity());
|
||||
|
||||
mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(),
|
||||
algorithmNames.getEncryptionNames());
|
||||
mEncryptionAlgorithm.setAdapter(mEncryptionAdapter);
|
||||
mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAppSettings.setEncryptionAlgorithm((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames());
|
||||
mHashAlgorithm.setAdapter(mHashAdapter);
|
||||
mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAppSettings.setHashAlgorithm((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(),
|
||||
algorithmNames.getCompressionNames());
|
||||
mCompression.setAdapter(mCompressionAdapter);
|
||||
mCompression.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
mAppSettings.setCompression((int) id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
|
||||
final Animation visibleAnimation = new AlphaAnimation(0.0f, 1.0f);
|
||||
visibleAnimation.setDuration(250);
|
||||
final Animation invisibleAnimation = new AlphaAnimation(1.0f, 0.0f);
|
||||
invisibleAnimation.setDuration(250);
|
||||
|
||||
// TODO: Better: collapse/expand animation
|
||||
// final Animation animation2 = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, -1.0f,
|
||||
// Animation.RELATIVE_TO_SELF, 0.0f);u
|
||||
// animation2.setDuration(150);
|
||||
|
||||
mAdvancedSettingsButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mAdvancedSettingsContainer.getVisibility() == View.VISIBLE) {
|
||||
mAdvancedSettingsContainer.startAnimation(invisibleAnimation);
|
||||
mAdvancedSettingsContainer.setVisibility(View.GONE);
|
||||
mAdvancedSettingsButton.setText(getString(R.string.api_settings_show_advanced));
|
||||
mAdvancedSettingsButton.setLeftIcon("fa-caret-up");
|
||||
} else {
|
||||
mAdvancedSettingsContainer.startAnimation(visibleAnimation);
|
||||
mAdvancedSettingsContainer.setVisibility(View.VISIBLE);
|
||||
mAdvancedSettingsButton.setText(getString(R.string.api_settings_hide_advanced));
|
||||
mAdvancedSettingsButton.setLeftIcon("fa-caret-down");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setPackage(String packageName) {
|
||||
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
|
||||
|
||||
// get application name and icon from package manager
|
||||
String appName = null;
|
||||
Drawable appIcon = null;
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
|
||||
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
appIcon = pm.getApplicationIcon(ai);
|
||||
} catch (final NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = packageName;
|
||||
}
|
||||
mAppNameView.setText(appName);
|
||||
mAppIconView.setImageDrawable(appIcon);
|
||||
}
|
||||
|
||||
/**
|
||||
* callback from select secret key fragment
|
||||
*/
|
||||
@Override
|
||||
public void onKeySelected(long secretKeyId) {
|
||||
mAppSettings.setKeyId(secretKeyId);
|
||||
}
|
||||
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.service.remote;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
|
||||
public class RegisteredAppsAdapter extends CursorAdapter {
|
||||
|
||||
private LayoutInflater mInflater;
|
||||
private PackageManager mPM;
|
||||
|
||||
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mPM = context.getApplicationContext().getPackageManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name);
|
||||
ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon);
|
||||
|
||||
String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME));
|
||||
if (packageName != null) {
|
||||
// get application name
|
||||
try {
|
||||
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
|
||||
|
||||
text.setText(mPM.getApplicationLabel(ai));
|
||||
icon.setImageDrawable(mPM.getApplicationIcon(ai));
|
||||
} catch (final NameNotFoundException e) {
|
||||
// fallback
|
||||
text.setText(packageName);
|
||||
}
|
||||
} else {
|
||||
// fallback
|
||||
text.setText(packageName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
*
|
||||
* 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
|
||||
@ -32,16 +32,20 @@ import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.*;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
@ -140,8 +144,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
Log.e(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri);
|
||||
|
||||
mUserIds = (ListView) findViewById(R.id.user_ids);
|
||||
|
||||
mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true);
|
||||
@ -150,20 +152,12 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
|
||||
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
|
||||
if (signKey != null) {
|
||||
mPubKeyId = PgpKeyHelper.getMasterKey(signKey).getKeyID();
|
||||
}
|
||||
if (mPubKeyId == 0) {
|
||||
Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static final String[] KEYRING_PROJECTION =
|
||||
new String[] {
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.Keys.MASTER_KEY_ID,
|
||||
KeychainContract.Keys.FINGERPRINT,
|
||||
KeychainContract.UserIds.USER_ID,
|
||||
};
|
||||
@ -184,11 +178,13 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
switch(id) {
|
||||
case LOADER_ID_KEYRING:
|
||||
return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null);
|
||||
case LOADER_ID_KEYRING: {
|
||||
Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
return new CursorLoader(this, uri, KEYRING_PROJECTION, null, null, null);
|
||||
}
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER);
|
||||
Uri uri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(this, uri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -200,20 +196,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
case LOADER_ID_KEYRING:
|
||||
// the first key here is our master key
|
||||
if (data.moveToFirst()) {
|
||||
long keyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
// TODO: put findViewById in onCreate!
|
||||
mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(mPubKeyId);
|
||||
((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
|
||||
|
||||
String mainUserId = data.getString(INDEX_USER_ID);
|
||||
((TextView) findViewById(R.id.main_user_id)).setText(mainUserId);
|
||||
|
||||
byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
|
||||
if (fingerprintBlob == null) {
|
||||
// FALLBACK for old database entries
|
||||
fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri);
|
||||
}
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
||||
((TextView) findViewById(R.id.fingerprint)).setText(OtherHelper.colorizeFingerprint(fingerprint));
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
|
||||
((TextView) findViewById(R.id.fingerprint))
|
||||
.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
|
||||
}
|
||||
break;
|
||||
case LOADER_ID_USER_IDS:
|
||||
@ -231,37 +225,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
}
|
||||
}
|
||||
|
||||
private void showPassphraseDialog(final long secretKeyId) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
startSigning();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, secretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key!");
|
||||
// send message to handler to start certification directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handles the UI bits of the signing process on the UI thread
|
||||
*/
|
||||
private void initiateSigning() {
|
||||
PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, mPubKeyId);
|
||||
PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRing(this, mPubKeyId);
|
||||
if (pubring != null) {
|
||||
// if we have already signed this key, dont bother doing it again
|
||||
boolean alreadySigned = false;
|
||||
@ -284,7 +252,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
*/
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(mMasterKeyId);
|
||||
PassphraseDialogFragment.show(this, mMasterKeyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
startSigning();
|
||||
}
|
||||
}
|
||||
});
|
||||
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||
return;
|
||||
} else {
|
||||
@ -307,7 +283,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
// Bail out if there is not at least one user id selected
|
||||
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
|
||||
if(userIds.isEmpty()) {
|
||||
if (userIds.isEmpty()) {
|
||||
Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
@ -327,11 +303,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after signing is done in ApgService
|
||||
// Message is received after signing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
@ -380,11 +356,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in ApgService
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -17,180 +17,48 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.*;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import android.support.v4.view.PagerTabStrip;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.NoAsymmetricEncryptionException;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
/* Intents */
|
||||
// without permission
|
||||
public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
|
||||
|
||||
/* EXTRA keys for input */
|
||||
public static final String EXTRA_TEXT = "text";
|
||||
|
||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||
private static final int RESULT_CODE_FILE = 0x00007003;
|
||||
ViewPager mViewPager;
|
||||
PagerTabStrip mPagerTabStrip;
|
||||
PagerTabStripAdapter mTabsAdapter;
|
||||
|
||||
private long mSignatureKeyId = 0;
|
||||
Bundle mMessageFragmentBundle = new Bundle();
|
||||
Bundle mFileFragmentBundle = new Bundle();
|
||||
int mSwitchToTab = PAGER_TAB_MESSAGE;
|
||||
|
||||
private boolean mReturnResult = false;
|
||||
|
||||
// TODO: replace signed only checks with something more intelligent
|
||||
// PgpDecryptVerify should handle all automatically!!!
|
||||
private boolean mSignedOnly = false;
|
||||
private boolean mAssumeSymmetricEncryption = false;
|
||||
|
||||
private EditText mMessage = null;
|
||||
private RelativeLayout mSignatureLayout = null;
|
||||
private ImageView mSignatureStatusImage = null;
|
||||
private TextView mUserId = null;
|
||||
private TextView mUserIdRest = null;
|
||||
|
||||
private ViewFlipper mSource = null;
|
||||
private TextView mSourceLabel = null;
|
||||
private ImageView mSourcePrevious = null;
|
||||
private ImageView mSourceNext = null;
|
||||
|
||||
private int mDecryptTarget;
|
||||
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private BootstrapButton mBrowse = null;
|
||||
private BootstrapButton mLookupKey = null;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
private Uri mContentUri = null;
|
||||
private boolean mReturnBinary = false;
|
||||
|
||||
private long mSecretKeyId = Id.key.none;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
private boolean mDecryptImmediately = false;
|
||||
|
||||
private BootstrapButton mDecryptButton;
|
||||
private static final int PAGER_TAB_MESSAGE = 0;
|
||||
private static final int PAGER_TAB_FILE = 1;
|
||||
|
||||
private void initView() {
|
||||
mSource = (ViewFlipper) findViewById(R.id.source);
|
||||
mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
|
||||
mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
|
||||
mSourceNext = (ImageView) findViewById(R.id.sourceNext);
|
||||
mViewPager = (ViewPager) findViewById(R.id.decrypt_pager);
|
||||
mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip);
|
||||
|
||||
mSourcePrevious.setClickable(true);
|
||||
mSourcePrevious.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_right_out));
|
||||
mSource.showPrevious();
|
||||
updateSource();
|
||||
}
|
||||
});
|
||||
|
||||
mSourceNext.setClickable(true);
|
||||
OnClickListener nextSourceClickListener = new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
mSource.setInAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_in));
|
||||
mSource.setOutAnimation(AnimationUtils.loadAnimation(DecryptActivity.this,
|
||||
R.anim.push_left_out));
|
||||
mSource.showNext();
|
||||
updateSource();
|
||||
}
|
||||
};
|
||||
mSourceNext.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mSourceLabel.setClickable(true);
|
||||
mSourceLabel.setOnClickListener(nextSourceClickListener);
|
||||
|
||||
mMessage = (EditText) findViewById(R.id.message);
|
||||
mSignatureLayout = (RelativeLayout) findViewById(R.id.signature);
|
||||
mSignatureStatusImage = (ImageView) findViewById(R.id.ic_signature_status);
|
||||
mUserId = (TextView) findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) findViewById(R.id.mainUserIdRest);
|
||||
|
||||
// measure the height of the source_file view and set the message view's min height to that,
|
||||
// so it fills mSource fully... bit of a hack.
|
||||
View tmp = findViewById(R.id.sourceFile);
|
||||
tmp.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
int height = tmp.getMeasuredHeight();
|
||||
mMessage.setMinimumHeight(height);
|
||||
|
||||
mFilename = (EditText) findViewById(R.id.filename);
|
||||
mBrowse = (BootstrapButton) findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
FileHelper.openFile(DecryptActivity.this, mFilename.getText().toString(), "*/*",
|
||||
RESULT_CODE_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
mLookupKey = (BootstrapButton) findViewById(R.id.lookup_key);
|
||||
mLookupKey.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
lookupUnknownKey(mSignatureKeyId);
|
||||
}
|
||||
});
|
||||
|
||||
mDeleteAfter = (CheckBox) findViewById(R.id.deleteAfterDecryption);
|
||||
|
||||
// default: message source
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceMessage) {
|
||||
mSource.showNext();
|
||||
}
|
||||
|
||||
mDecryptButton = (BootstrapButton) findViewById(R.id.action_decrypt);
|
||||
mDecryptButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
decryptClicked();
|
||||
}
|
||||
});
|
||||
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||
mViewPager.setAdapter(mTabsAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,68 +74,17 @@ public class DecryptActivity extends DrawerActivity {
|
||||
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
|
||||
// Handle intent actions
|
||||
// Handle intent actions, maybe changes the bundles
|
||||
handleActions(getIntent());
|
||||
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
||||
&& mMessage.getText().length() == 0) {
|
||||
|
||||
CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
|
||||
|
||||
String data = "";
|
||||
if (clipboardText != null) {
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
|
||||
if (!matcher.matches()) {
|
||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(clipboardText);
|
||||
}
|
||||
if (matcher.matches()) {
|
||||
data = matcher.group(1);
|
||||
mMessage.setText(data);
|
||||
AppMsg.makeText(this, R.string.using_clipboard_content, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mSignatureLayout.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
if (mSignatureKeyId == 0) {
|
||||
return;
|
||||
}
|
||||
PGPPublicKeyRing key = ProviderHelper.getPGPPublicKeyRingByKeyId(
|
||||
DecryptActivity.this, mSignatureKeyId);
|
||||
if (key != null) {
|
||||
Intent intent = new Intent(DecryptActivity.this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, mSignatureKeyId);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (mReturnResult) {
|
||||
mSourcePrevious.setClickable(false);
|
||||
mSourcePrevious.setEnabled(false);
|
||||
mSourcePrevious.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceNext.setClickable(false);
|
||||
mSourceNext.setEnabled(false);
|
||||
mSourceNext.setVisibility(View.INVISIBLE);
|
||||
|
||||
mSourceLabel.setClickable(false);
|
||||
mSourceLabel.setEnabled(false);
|
||||
}
|
||||
|
||||
updateSource();
|
||||
|
||||
if (mDecryptImmediately
|
||||
|| (mSource.getCurrentView().getId() == R.id.sourceMessage && (mMessage.getText()
|
||||
.length() > 0 || mContentUri != null))) {
|
||||
decryptClicked();
|
||||
}
|
||||
mTabsAdapter.addTab(DecryptMessageFragment.class,
|
||||
mMessageFragmentBundle, getString(R.string.label_message));
|
||||
mTabsAdapter.addTab(DecryptFileFragment.class,
|
||||
mFileFragmentBundle, getString(R.string.label_file));
|
||||
mViewPager.setCurrentItem(mSwitchToTab);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles all actions with this intent
|
||||
*
|
||||
@ -316,22 +133,26 @@ public class DecryptActivity extends DrawerActivity {
|
||||
* Main Actions
|
||||
*/
|
||||
if (ACTION_DECRYPT.equals(action) && textData != null) {
|
||||
Log.d(Constants.TAG, "textData null, matching text ...");
|
||||
Log.d(Constants.TAG, "textData not null, matching text ...");
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||
textData = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
textData = textData.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(textData);
|
||||
|
||||
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
|
||||
mSwitchToTab = PAGER_TAB_MESSAGE;
|
||||
} else {
|
||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(textData);
|
||||
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData);
|
||||
if (matcher.matches()) {
|
||||
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
||||
Log.d(Constants.TAG, "PGP_CLEARTEXT_SIGNATURE matched");
|
||||
textData = matcher.group(1);
|
||||
// replace non breakable spaces
|
||||
textData = textData.replaceAll("\\xa0", " ");
|
||||
mMessage.setText(textData);
|
||||
|
||||
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
|
||||
mSwitchToTab = PAGER_TAB_MESSAGE;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "Nothing matched!");
|
||||
}
|
||||
@ -341,17 +162,12 @@ public class DecryptActivity extends DrawerActivity {
|
||||
String path = FileHelper.getPath(this, uri);
|
||||
|
||||
if (path != null) {
|
||||
mInputFilename = path;
|
||||
mFilename.setText(mInputFilename);
|
||||
guessOutputFilename();
|
||||
mSource.setInAnimation(null);
|
||||
mSource.setOutAnimation(null);
|
||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
||||
mSource.showNext();
|
||||
}
|
||||
mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path);
|
||||
mSwitchToTab = PAGER_TAB_FILE;
|
||||
} else {
|
||||
Log.e(Constants.TAG,
|
||||
"Direct binary data without actual file in filesystem is not supported. Please use the Remote Service API!");
|
||||
"Direct binary data without actual file in filesystem is not supported. " +
|
||||
"Please use the Remote Service API!");
|
||||
Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
// end activity
|
||||
@ -363,428 +179,4 @@ public class DecryptActivity extends DrawerActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void guessOutputFilename() {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
File file = new File(mInputFilename);
|
||||
String filename = file.getName();
|
||||
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
|
||||
filename = filename.substring(0, filename.length() - 4);
|
||||
}
|
||||
mOutputFilename = Constants.Path.APP_DIR + "/" + filename;
|
||||
}
|
||||
|
||||
private void updateSource() {
|
||||
switch (mSource.getCurrentView().getId()) {
|
||||
case R.id.sourceFile: {
|
||||
mSourceLabel.setText(R.string.label_file);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
|
||||
case R.id.sourceMessage: {
|
||||
mSourceLabel.setText(R.string.label_message);
|
||||
mDecryptButton.setText(getString(R.string.btn_decrypt));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptClicked() {
|
||||
if (mSource.getCurrentView().getId() == R.id.sourceFile) {
|
||||
mDecryptTarget = Id.target.file;
|
||||
} else {
|
||||
mDecryptTarget = Id.target.message;
|
||||
}
|
||||
initiateDecryption();
|
||||
}
|
||||
|
||||
private void initiateDecryption() {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
AppMsg.makeText(this, R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
AppMsg.makeText(
|
||||
this,
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mDecryptTarget == Id.target.message) {
|
||||
String messageData = mMessage.getText().toString();
|
||||
Matcher matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(messageData);
|
||||
if (matcher.matches()) {
|
||||
mSignedOnly = true;
|
||||
decryptStart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// else treat it as an decrypted message/file
|
||||
mSignedOnly = false;
|
||||
|
||||
getDecryptionKeyFromInputStream();
|
||||
|
||||
// if we need a symmetric passphrase or a passphrase to use a secret key ask for it
|
||||
if (mSecretKeyId == Id.key.symmetric
|
||||
|| PassphraseCacheService.getCachedPassphrase(this, mSecretKeyId) == null) {
|
||||
showPassphraseDialog();
|
||||
} else {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else { // mDecryptTarget == Id.target.message
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
private void showPassphraseDialog() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
if (mDecryptTarget == Id.target.file) {
|
||||
askForOutputFilename();
|
||||
} else {
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(this,
|
||||
messenger, mSecretKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Rework function, remove global variables
|
||||
*/
|
||||
private void getDecryptionKeyFromInputStream() {
|
||||
InputStream inStream = null;
|
||||
if (mContentUri != null) {
|
||||
try {
|
||||
inStream = getContentResolver().openInputStream(mContentUri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found!", e);
|
||||
AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
// check if storage is ready
|
||||
if (!FileHelper.isStorageMounted(mInputFilename)) {
|
||||
AppMsg.makeText(this, getString(R.string.error_external_storage_not_ready),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
inStream = new BufferedInputStream(new FileInputStream(mInputFilename));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(Constants.TAG, "File not found!", e);
|
||||
AppMsg.makeText(this, getString(R.string.error_file_not_found, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
} finally {
|
||||
try {
|
||||
if (inStream != null) {
|
||||
inStream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inStream = new ByteArrayInputStream(mMessage.getText().toString().getBytes());
|
||||
}
|
||||
|
||||
// get decryption key for this inStream
|
||||
try {
|
||||
try {
|
||||
if (inStream.markSupported()) {
|
||||
inStream.mark(200); // should probably set this to the max size of two pgpF
|
||||
// objects, if it even needs to be anything other than 0.
|
||||
}
|
||||
mSecretKeyId = PgpHelper.getDecryptionKeyId(this, inStream);
|
||||
if (mSecretKeyId == Id.key.none) {
|
||||
throw new PgpGeneralException(getString(R.string.error_no_secret_key_found));
|
||||
}
|
||||
mAssumeSymmetricEncryption = false;
|
||||
} catch (NoAsymmetricEncryptionException e) {
|
||||
if (inStream.markSupported()) {
|
||||
inStream.reset();
|
||||
}
|
||||
mSecretKeyId = Id.key.symmetric;
|
||||
if (!PgpDecryptVerify.hasSymmetricEncryption(this, inStream)) {
|
||||
throw new PgpGeneralException(
|
||||
getString(R.string.error_no_known_encryption_found));
|
||||
}
|
||||
mAssumeSymmetricEncryption = true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
AppMsg.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void replyClicked() {
|
||||
Intent intent = new Intent(this, EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
String data = mMessage.getText().toString();
|
||||
data = data.replaceAll("(?m)^", "> ");
|
||||
data = "\n\n" + data;
|
||||
intent.putExtra(EncryptActivity.EXTRA_TEXT, data);
|
||||
intent.putExtra(EncryptActivity.EXTRA_SIGNATURE_KEY_ID, mSecretKeyId);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, new long[]{mSignatureKeyId});
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
decryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger,
|
||||
getString(R.string.title_decrypt_to_file),
|
||||
getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null);
|
||||
|
||||
mFileDialog.show(getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
Intent intent = new Intent(this, ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId);
|
||||
startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY);
|
||||
}
|
||||
|
||||
private void decryptStart() {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(this, KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// choose action based on input: decrypt stream, file or bytes
|
||||
if (mContentUri != null) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_STREAM);
|
||||
|
||||
data.putParcelable(KeychainIntentService.ENCRYPT_PROVIDER_URI, mContentUri);
|
||||
} else if (mDecryptTarget == Id.target.file) {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI);
|
||||
|
||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
||||
+ mOutputFilename);
|
||||
|
||||
data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
|
||||
data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
|
||||
} else {
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
|
||||
|
||||
String message = mMessage.getText().toString();
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, message.getBytes());
|
||||
}
|
||||
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SECRET_KEY_ID, mSecretKeyId);
|
||||
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_RETURN_BYTES, mReturnBinary);
|
||||
data.putBoolean(KeychainIntentService.DECRYPT_ASSUME_SYMMETRIC, mAssumeSymmetricEncryption);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in ApgService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
|
||||
AppMsg.makeText(DecryptActivity.this, R.string.decryption_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
if (mReturnResult) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtras(returnData);
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mDecryptTarget) {
|
||||
case Id.target.message:
|
||||
String decryptedMessage = returnData
|
||||
.getString(KeychainIntentService.RESULT_DECRYPTED_STRING);
|
||||
mMessage.setText(decryptedMessage);
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
|
||||
break;
|
||||
|
||||
case Id.target.file:
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// shouldn't happen
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
PgpDecryptVerifyResult decryptVerifyResult =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
|
||||
|
||||
OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
|
||||
|
||||
if (signatureResult != null) {
|
||||
|
||||
String userId = signatureResult.getUserId();
|
||||
mSignatureKeyId = signatureResult.getKeyId();
|
||||
mUserIdRest.setText("id: "
|
||||
+ PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
|
||||
if (userId == null) {
|
||||
userId = getResources().getString(R.string.user_id_no_name);
|
||||
}
|
||||
String chunks[] = userId.split(" <", 2);
|
||||
userId = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
mUserIdRest.setText("<" + chunks[1]);
|
||||
}
|
||||
mUserId.setText(userId);
|
||||
|
||||
switch (signatureResult.getStatus()) {
|
||||
case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO!
|
||||
// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: {
|
||||
// break;
|
||||
// }
|
||||
|
||||
case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.VISIBLE);
|
||||
AppMsg.makeText(DecryptActivity.this,
|
||||
R.string.unknown_signature,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(this);
|
||||
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_CODE_FILE: {
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(this, data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// this request is returned after LookupUnknownKeyDialogFragment started
|
||||
// ImportKeysActivity and user looked uo key
|
||||
case RESULT_CODE_LOOKUP_KEY: {
|
||||
Log.d(Constants.TAG, "Returning from Lookup Key...");
|
||||
if (resultCode == RESULT_OK) {
|
||||
// decrypt again
|
||||
decryptStart();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DecryptFileFragment extends DecryptFragment {
|
||||
public static final String ARG_FILENAME = "filename";
|
||||
|
||||
private static final int RESULT_CODE_FILE = 0x00007003;
|
||||
|
||||
// view
|
||||
private EditText mFilename;
|
||||
private CheckBox mDeleteAfter;
|
||||
private BootstrapButton mBrowse;
|
||||
private BootstrapButton mDecryptButton;
|
||||
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.decrypt_file_fragment, container, false);
|
||||
|
||||
mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename);
|
||||
mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse);
|
||||
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption);
|
||||
mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*",
|
||||
RESULT_CODE_FILE);
|
||||
}
|
||||
});
|
||||
mDecryptButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
decryptAction();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String filename = getArguments().getString(ARG_FILENAME);
|
||||
if (filename != null) {
|
||||
mFilename.setText(filename);
|
||||
}
|
||||
}
|
||||
|
||||
private void guessOutputFilename() {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
File file = new File(mInputFilename);
|
||||
String filename = file.getName();
|
||||
if (filename.endsWith(".asc") || filename.endsWith(".gpg") || filename.endsWith(".pgp")) {
|
||||
filename = filename.substring(0, filename.length() - 4);
|
||||
}
|
||||
mOutputFilename = Constants.Path.APP_DIR + "/" + filename;
|
||||
}
|
||||
|
||||
private void decryptAction() {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
guessOutputFilename();
|
||||
}
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mInputFilename.startsWith("file")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
AppMsg.makeText(
|
||||
getActivity(),
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
askForOutputFilename();
|
||||
}
|
||||
|
||||
private void askForOutputFilename() {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
decryptStart(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger,
|
||||
getString(R.string.title_decrypt_to_file),
|
||||
getString(R.string.specify_file_to_decrypt_to), mOutputFilename, null);
|
||||
|
||||
mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decryptStart(String passphrase) {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// data
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI);
|
||||
|
||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
||||
+ mOutputFilename);
|
||||
|
||||
data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
|
||||
data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
|
||||
|
||||
data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
PgpDecryptVerifyResult decryptVerifyResult =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
|
||||
|
||||
if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
|
||||
showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded());
|
||||
} else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
|
||||
decryptVerifyResult.getStatus()) {
|
||||
showPassphraseDialog(Id.key.symmetric);
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.decryption_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
|
||||
OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
|
||||
|
||||
// display signature result in activity
|
||||
onSignatureResult(signatureResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_CODE_FILE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(getActivity(), data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
|
||||
public class DecryptFragment extends Fragment {
|
||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
||||
|
||||
protected long mSignatureKeyId = 0;
|
||||
|
||||
protected RelativeLayout mSignatureLayout = null;
|
||||
protected ImageView mSignatureStatusImage = null;
|
||||
protected TextView mUserId = null;
|
||||
protected TextView mUserIdRest = null;
|
||||
|
||||
protected BootstrapButton mLookupKey = null;
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mSignatureLayout = (RelativeLayout) getView().findViewById(R.id.signature);
|
||||
mSignatureStatusImage = (ImageView) getView().findViewById(R.id.ic_signature_status);
|
||||
mUserId = (TextView) getView().findViewById(R.id.mainUserId);
|
||||
mUserIdRest = (TextView) getView().findViewById(R.id.mainUserIdRest);
|
||||
mLookupKey = (BootstrapButton) getView().findViewById(R.id.lookup_key);
|
||||
mLookupKey.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
lookupUnknownKey(mSignatureKeyId);
|
||||
}
|
||||
});
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
mSignatureLayout.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
lookupUnknownKey(mSignatureKeyId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void lookupUnknownKey(long unknownKeyId) {
|
||||
Intent intent = new Intent(getActivity(), ImportKeysActivity.class);
|
||||
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, unknownKeyId);
|
||||
startActivityForResult(intent, RESULT_CODE_LOOKUP_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
|
||||
case RESULT_CODE_LOOKUP_KEY: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// TODO: generate new OpenPgpSignatureResult and display it
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onSignatureResult(OpenPgpSignatureResult signatureResult) {
|
||||
mSignatureKeyId = 0;
|
||||
mSignatureLayout.setVisibility(View.GONE);
|
||||
if (signatureResult != null) {
|
||||
|
||||
mSignatureKeyId = signatureResult.getKeyId();
|
||||
|
||||
String userId = signatureResult.getUserId();
|
||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||
if (userIdSplit[0] != null) {
|
||||
mUserId.setText(userId);
|
||||
} else {
|
||||
mUserId.setText(R.string.user_id_no_name);
|
||||
}
|
||||
if (userIdSplit[1] != null) {
|
||||
mUserIdRest.setText(userIdSplit[1]);
|
||||
} else {
|
||||
mUserIdRest.setText(getString(R.string.label_key_id) + ": "
|
||||
+ PgpKeyHelper.convertKeyIdToHex(mSignatureKeyId));
|
||||
}
|
||||
|
||||
switch (signatureResult.getStatus()) {
|
||||
case OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED: {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_ok);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO!
|
||||
// case OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED: {
|
||||
// break;
|
||||
// }
|
||||
|
||||
case OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY: {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.VISIBLE);
|
||||
AppMsg.makeText(getActivity(),
|
||||
R.string.unknown_signature,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
mSignatureStatusImage.setImageResource(R.drawable.overlay_error);
|
||||
mLookupKey.setVisibility(View.GONE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mSignatureLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showPassphraseDialog(long keyId) {
|
||||
PassphraseDialogFragment.show(getActivity(), keyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
String passphrase =
|
||||
message.getData().getString(PassphraseDialogFragment.MESSAGE_DATA_PASSPHRASE);
|
||||
decryptStart(passphrase);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be overridden by MessageFragment and FileFragment to start actual decryption
|
||||
*
|
||||
* @param passphrase
|
||||
*/
|
||||
protected void decryptStart(String passphrase) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpHelper;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
public class DecryptMessageFragment extends DecryptFragment {
|
||||
public static final String ARG_CIPHERTEXT = "ciphertext";
|
||||
|
||||
// view
|
||||
private EditText mMessage;
|
||||
private BootstrapButton mDecryptButton;
|
||||
private BootstrapButton mDecryptFromCLipboardButton;
|
||||
|
||||
// model
|
||||
private String mCiphertext;
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.decrypt_message_fragment, container, false);
|
||||
|
||||
mMessage = (EditText) view.findViewById(R.id.message);
|
||||
mDecryptButton = (BootstrapButton) view.findViewById(R.id.action_decrypt);
|
||||
mDecryptFromCLipboardButton = (BootstrapButton) view.findViewById(R.id.action_decrypt_from_clipboard);
|
||||
mDecryptButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
decryptClicked();
|
||||
}
|
||||
});
|
||||
mDecryptFromCLipboardButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
decryptFromClipboardClicked();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String ciphertext = getArguments().getString(ARG_CIPHERTEXT);
|
||||
if (ciphertext != null) {
|
||||
mMessage.setText(ciphertext);
|
||||
decryptStart(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void decryptClicked() {
|
||||
mCiphertext = mMessage.getText().toString();
|
||||
decryptStart(null);
|
||||
}
|
||||
|
||||
private void decryptFromClipboardClicked() {
|
||||
CharSequence clipboardText = ClipboardReflection.getClipboardText(getActivity());
|
||||
|
||||
// only decrypt if clipboard content is available and a pgp message or cleartext signature
|
||||
if (clipboardText != null) {
|
||||
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(clipboardText);
|
||||
if (!matcher.matches()) {
|
||||
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(clipboardText);
|
||||
}
|
||||
if (matcher.matches()) {
|
||||
mCiphertext = matcher.group(1);
|
||||
decryptStart(null);
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
}
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decryptStart(String passphrase) {
|
||||
Log.d(Constants.TAG, "decryptStart");
|
||||
|
||||
// Send all information needed to service to decrypt in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_DECRYPT_VERIFY);
|
||||
|
||||
// data
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
|
||||
data.putByteArray(KeychainIntentService.DECRYPT_CIPHERTEXT_BYTES, mCiphertext.getBytes());
|
||||
data.putString(KeychainIntentService.DECRYPT_PASSPHRASE, passphrase);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_decrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle returnData = message.getData();
|
||||
|
||||
PgpDecryptVerifyResult decryptVerifyResult =
|
||||
returnData.getParcelable(KeychainIntentService.RESULT_DECRYPT_VERIFY_RESULT);
|
||||
|
||||
if (PgpDecryptVerifyResult.KEY_PASSHRASE_NEEDED == decryptVerifyResult.getStatus()) {
|
||||
showPassphraseDialog(decryptVerifyResult.getKeyIdPassphraseNeeded());
|
||||
} else if (PgpDecryptVerifyResult.SYMMETRIC_PASSHRASE_NEEDED ==
|
||||
decryptVerifyResult.getStatus()) {
|
||||
showPassphraseDialog(Id.key.symmetric);
|
||||
} else {
|
||||
AppMsg.makeText(getActivity(), R.string.decryption_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
|
||||
byte[] decryptedMessage = returnData
|
||||
.getByteArray(KeychainIntentService.RESULT_DECRYPTED_BYTES);
|
||||
mMessage.setText(new String(decryptedMessage));
|
||||
mMessage.setHorizontallyScrolling(false);
|
||||
|
||||
OpenPgpSignatureResult signatureResult = decryptVerifyResult.getSignatureResult();
|
||||
|
||||
// display signature result in activity
|
||||
onSignatureResult(signatureResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
}
|
@ -21,19 +21,26 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActionBarDrawerToggle;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.*;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.FontAwesomeText;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
|
||||
|
||||
public class DrawerActivity extends ActionBarActivity {
|
||||
private DrawerLayout mDrawerLayout;
|
||||
@ -42,10 +49,8 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
|
||||
private CharSequence mDrawerTitle;
|
||||
private CharSequence mTitle;
|
||||
private boolean mIsDrawerLocked = false;
|
||||
|
||||
private static Class[] mItemsClass = new Class[]{KeyListActivity.class,
|
||||
EncryptActivity.class, DecryptActivity.class, ImportKeysActivity.class,
|
||||
RegisteredAppsListActivity.class};
|
||||
private Class mSelectedItem;
|
||||
|
||||
private static final int MENU_ID_PREFERENCE = 222;
|
||||
@ -55,10 +60,22 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
mDrawerTitle = getString(R.string.app_name);
|
||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mDrawerList = (ListView) findViewById(R.id.left_drawer);
|
||||
ViewGroup viewGroup = (ViewGroup) findViewById(R.id.content_frame);
|
||||
int leftMarginLoaded = ((ViewGroup.MarginLayoutParams) viewGroup.getLayoutParams()).leftMargin;
|
||||
int leftMarginInTablets = (int) getResources().getDimension(R.dimen.drawer_size);
|
||||
int errorInMarginAllowed = 5;
|
||||
|
||||
// set a custom shadow that overlays the main content when the drawer
|
||||
// opens
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
// if the left margin of the loaded layout is close to the
|
||||
// one used in tablets then set drawer as open and locked
|
||||
if (Math.abs(leftMarginLoaded - leftMarginInTablets) < errorInMarginAllowed) {
|
||||
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerList);
|
||||
mDrawerLayout.setScrimColor(Color.TRANSPARENT);
|
||||
mIsDrawerLocked = true;
|
||||
} else {
|
||||
// set a custom shadow that overlays the main content when the drawer opens
|
||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
||||
mIsDrawerLocked = false;
|
||||
}
|
||||
|
||||
NavItem mItemIconTexts[] = new NavItem[]{
|
||||
new NavItem("fa-user", getString(R.string.nav_contacts)),
|
||||
@ -73,8 +90,11 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||
|
||||
// enable ActionBar app icon to behave as action to toggle nav drawer
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
// if the drawer is not locked
|
||||
if (!mIsDrawerLocked) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
|
||||
// ActionBarDrawerToggle ties together the the proper interactions
|
||||
// between the sliding drawer and the action bar app icon
|
||||
@ -86,19 +106,8 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
) {
|
||||
public void onDrawerClosed(View view) {
|
||||
getSupportActionBar().setTitle(mTitle);
|
||||
// creates call to onPrepareOptionsMenu()
|
||||
supportInvalidateOptionsMenu();
|
||||
|
||||
// call intent activity if selected
|
||||
if (mSelectedItem != null) {
|
||||
finish();
|
||||
overridePendingTransition(0, 0);
|
||||
|
||||
Intent intent = new Intent(DrawerActivity.this, mSelectedItem);
|
||||
startActivity(intent);
|
||||
// disable animation of activity start
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
callIntentForDrawerItem(mSelectedItem);
|
||||
}
|
||||
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
@ -108,33 +117,56 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
};
|
||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
||||
|
||||
// if (savedInstanceState == null) {
|
||||
// selectItem(0);
|
||||
// }
|
||||
if (!mIsDrawerLocked) {
|
||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
||||
} else {
|
||||
// If the drawer is locked open make it un-focusable
|
||||
// so that it doesn't consume all the Back button presses
|
||||
mDrawerLayout.setFocusableInTouchMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses startActivity to call the Intent of the given class
|
||||
*
|
||||
* @param drawerItem the class of the drawer item you want to load. Based on Constants.DrawerItems.*
|
||||
*/
|
||||
public void callIntentForDrawerItem(Class drawerItem) {
|
||||
// creates call to onPrepareOptionsMenu()
|
||||
supportInvalidateOptionsMenu();
|
||||
|
||||
// call intent activity if selected
|
||||
if (drawerItem != null) {
|
||||
finish();
|
||||
overridePendingTransition(0, 0);
|
||||
|
||||
Intent intent = new Intent(this, drawerItem);
|
||||
startActivity(intent);
|
||||
|
||||
// disable animation of activity start
|
||||
overridePendingTransition(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
if (mDrawerToggle == null) {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
menu.add(42, MENU_ID_PREFERENCE, 100, R.string.menu_preferences);
|
||||
menu.add(42, MENU_ID_HELP, 101, R.string.menu_help);
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
/* Called whenever we call invalidateOptionsMenu() */
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// If the nav drawer is open, hide action items related to the content
|
||||
// view
|
||||
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
|
||||
// menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (mDrawerToggle == null) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// The action bar home/up action should open or close the drawer.
|
||||
// ActionBarDrawerToggle will take care of this.
|
||||
if (mDrawerToggle.onOptionsItemSelected(item)) {
|
||||
@ -155,26 +187,11 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
// Handle action buttons
|
||||
// switch (item.getItemId()) {
|
||||
// case R.id.action_websearch:
|
||||
// // create intent to perform web search for this planet
|
||||
// Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||
// intent.putExtra(SearchManager.QUERY, getSupportActionBar().getTitle());
|
||||
// // catch event that there's no activity to handle intent
|
||||
// if (intent.resolveActivity(getPackageManager()) != null) {
|
||||
// startActivity(intent);
|
||||
// } else {
|
||||
// Toast.makeText(this, R.string.app_not_available, Toast.LENGTH_LONG).show();
|
||||
// }
|
||||
// return true;
|
||||
// default:
|
||||
// return super.onOptionsItemSelected(item);
|
||||
// }
|
||||
}
|
||||
|
||||
/* The click listener for ListView in the navigation drawer */
|
||||
/**
|
||||
* The click listener for ListView in the navigation drawer
|
||||
*/
|
||||
private class DrawerItemClickListener implements ListView.OnItemClickListener {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
@ -185,10 +202,18 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
private void selectItem(int position) {
|
||||
// update selected item and title, then close the drawer
|
||||
mDrawerList.setItemChecked(position, true);
|
||||
// setTitle(mDrawerTitles[position]);
|
||||
mDrawerLayout.closeDrawer(mDrawerList);
|
||||
// set selected class
|
||||
mSelectedItem = mItemsClass[position];
|
||||
mSelectedItem = Constants.DrawerItems.ARRAY[position];
|
||||
|
||||
// setTitle(mDrawerTitles[position]);
|
||||
// If drawer isn't locked just close the drawer and
|
||||
// it will move to the selected item by itself (via drawer toggle listener)
|
||||
if (!mIsDrawerLocked) {
|
||||
mDrawerLayout.closeDrawer(mDrawerList);
|
||||
// else move to the selected item yourself
|
||||
} else {
|
||||
callIntentForDrawerItem(mSelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -199,14 +224,18 @@ public class DrawerActivity extends ActionBarActivity {
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||
mDrawerToggle.syncState();
|
||||
if (mDrawerToggle != null) {
|
||||
mDrawerToggle.syncState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Pass any configuration change to the drawer toggles
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
if (mDrawerToggle != null) {
|
||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private class NavItem {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@ -27,32 +28,45 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.*;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
|
||||
import org.sufficientlysecure.keychain.ui.widget.KeyEditor;
|
||||
import org.sufficientlysecure.keychain.ui.widget.SectionView;
|
||||
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
|
||||
@ -61,9 +75,10 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EditKeyActivity extends ActionBarActivity {
|
||||
public class EditKeyActivity extends ActionBarActivity implements EditorListener {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
|
||||
@ -74,10 +89,6 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
public static final String EXTRA_NO_PASSPHRASE = "no_passphrase";
|
||||
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
|
||||
|
||||
// results when saving key
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String RESULT_EXTRA_USER_ID = "user_id";
|
||||
|
||||
// EDIT
|
||||
private Uri mDataUri;
|
||||
|
||||
@ -87,9 +98,11 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
private SectionView mKeysView;
|
||||
|
||||
private String mCurrentPassphrase = null;
|
||||
private String mNewPassPhrase = null;
|
||||
private String mSavedNewPassPhrase = null;
|
||||
private boolean mIsPassPhraseSet;
|
||||
private String mNewPassphrase = null;
|
||||
private String mSavedNewPassphrase = null;
|
||||
private boolean mIsPassphraseSet;
|
||||
private boolean mNeedsSaving;
|
||||
private boolean mIsBrandNewKeyring = false;
|
||||
|
||||
private BootstrapButton mChangePassphrase;
|
||||
|
||||
@ -102,12 +115,37 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
|
||||
ExportHelper mExportHelper;
|
||||
|
||||
public boolean needsSaving() {
|
||||
mNeedsSaving = (mUserIdsView == null) ? false : mUserIdsView.needsSaving();
|
||||
mNeedsSaving |= (mKeysView == null) ? false : mKeysView.needsSaving();
|
||||
mNeedsSaving |= hasPassphraseChanged();
|
||||
mNeedsSaving |= mIsBrandNewKeyring;
|
||||
return mNeedsSaving;
|
||||
}
|
||||
|
||||
|
||||
public void somethingChanged() {
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
}
|
||||
|
||||
public void onDeleted(Editor e, boolean wasNewItem) {
|
||||
somethingChanged();
|
||||
}
|
||||
|
||||
public void onEdited() {
|
||||
somethingChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setIcon(android.R.color.transparent);
|
||||
getSupportActionBar().setHomeButtonEnabled(true);
|
||||
|
||||
mUserIds = new Vector<String>();
|
||||
mKeys = new Vector<PGPSecretKey>();
|
||||
mKeysUsages = new Vector<Integer>();
|
||||
@ -128,24 +166,10 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionCreateKey(Intent intent) {
|
||||
// Inflate a "Save"/"Cancel" custom action bar
|
||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
saveClicked();
|
||||
}
|
||||
}, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cancelClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
Bundle extras = intent.getExtras();
|
||||
|
||||
mCurrentPassphrase = "";
|
||||
mIsBrandNewKeyring = true;
|
||||
|
||||
if (extras != null) {
|
||||
// if userId is given, prefill the fields
|
||||
@ -180,7 +204,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
|
||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after generating is done in ApgService
|
||||
// Message is received after generating is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
|
||||
ProgressDialog.STYLE_HORIZONTAL, true,
|
||||
@ -197,28 +221,28 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get new key from data bundle returned from service
|
||||
Bundle data = message.getData();
|
||||
PGPSecretKey masterKey = (PGPSecretKey) PgpConversionHelper
|
||||
.BytesToPGPSecretKey(data
|
||||
|
||||
ArrayList<PGPSecretKey> newKeys =
|
||||
PgpConversionHelper.BytesToPGPSecretKeyList(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
||||
PGPSecretKey subKey = (PGPSecretKey) PgpConversionHelper
|
||||
.BytesToPGPSecretKey(data
|
||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY2));
|
||||
|
||||
// add master key
|
||||
mKeys.add(masterKey);
|
||||
mKeysUsages.add(Id.choice.usage.sign_only); //TODO: get from key flags
|
||||
ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
|
||||
KeychainIntentService.RESULT_KEY_USAGES);
|
||||
|
||||
// add sub key
|
||||
mKeys.add(subKey);
|
||||
mKeysUsages.add(Id.choice.usage.encrypt_only); //TODO: get from key flags
|
||||
if (newKeys.size() == keyUsageFlags.size()) {
|
||||
for (int i = 0; i < newKeys.size(); ++i) {
|
||||
mKeys.add(newKeys.get(i));
|
||||
mKeysUsages.add(keyUsageFlags.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
buildLayout();
|
||||
buildLayout(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -234,7 +258,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildLayout();
|
||||
buildLayout(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,67 +268,16 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
* @param intent
|
||||
*/
|
||||
private void handleActionEditKey(Intent intent) {
|
||||
// Inflate a "Save"/"Cancel" custom action bar
|
||||
ActionBarHelper.setOneButtonView(getSupportActionBar(), R.string.btn_save, R.drawable.ic_action_save,
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
saveClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mDataUri = intent.getData();
|
||||
if (mDataUri == null) {
|
||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
||||
finish();
|
||||
return;
|
||||
} else {
|
||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||
|
||||
// get master key id using row id
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||
|
||||
mMasterCanSign = ProviderHelper.getMasterKeyCanSign(this, mDataUri);
|
||||
finallyEdit(masterKeyId, mMasterCanSign);
|
||||
}
|
||||
}
|
||||
|
||||
private void showPassphraseDialog(final long masterKeyId, final boolean masterCanSign) {
|
||||
// Message is received after passphrase is cached
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
String passphrase = PassphraseCacheService.getCachedPassphrase(
|
||||
EditKeyActivity.this, masterKeyId);
|
||||
mCurrentPassphrase = passphrase;
|
||||
finallySaveClicked();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(
|
||||
EditKeyActivity.this, messenger, masterKeyId);
|
||||
|
||||
passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// show menu only on edit
|
||||
if (mDataUri != null) {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
} else {
|
||||
return false;
|
||||
finallyEdit(masterKeyId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,45 +285,66 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
getMenuInflater().inflate(R.menu.key_edit, menu);
|
||||
//totally get rid of some actions for new keys
|
||||
if (mDataUri == null) {
|
||||
MenuItem mButton = menu.findItem(R.id.menu_key_edit_export_file);
|
||||
mButton.setVisible(false);
|
||||
mButton = menu.findItem(R.id.menu_key_edit_delete);
|
||||
mButton.setVisible(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
cancelClicked();
|
||||
// TODO: why isn't this triggered on my tablet - one of many ui problems
|
||||
// I've had with this device. A code compatibility issue or a Samsung fail?
|
||||
return true;
|
||||
case R.id.menu_key_edit_cancel:
|
||||
cancelClicked();
|
||||
return true;
|
||||
case R.id.menu_key_edit_export_file:
|
||||
long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
|
||||
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC);
|
||||
if (needsSaving()) {
|
||||
Toast.makeText(this, R.string.error_save_first, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||
mExportHelper.showExportKeysDialog(
|
||||
new long[] { masterKeyId }, Constants.Path.APP_DIR_FILE_SEC, true);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
case R.id.menu_key_edit_delete:
|
||||
Uri convertUri = KeyRingData.buildSecretKeyRingUri(mDataUri);
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}};
|
||||
mExportHelper.deleteKey(convertUri, returnHandler);
|
||||
return true;
|
||||
case R.id.menu_key_edit_delete: {
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mExportHelper.deleteKey(mDataUri, Id.type.secret_key, returnHandler);
|
||||
case R.id.menu_key_edit_save:
|
||||
saveClicked();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void finallyEdit(final long masterKeyId, final boolean masterCanSign) {
|
||||
private void finallyEdit(final long masterKeyId) {
|
||||
if (masterKeyId != 0) {
|
||||
PGPSecretKey masterKey = null;
|
||||
mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId);
|
||||
mKeyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
||||
if (mKeyRing != null) {
|
||||
masterKey = PgpKeyHelper.getMasterKey(mKeyRing);
|
||||
masterKey = mKeyRing.getSecretKey();
|
||||
mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey());
|
||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
||||
mKeys.add(key);
|
||||
mKeysUsages.add(-1); // get usage when view is created
|
||||
@ -358,20 +352,29 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
|
||||
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
|
||||
// TODO
|
||||
}
|
||||
if (masterKey != null) {
|
||||
boolean isSet = false;
|
||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||
Log.d(Constants.TAG, "Added userId " + userId);
|
||||
if (!isSet) {
|
||||
isSet = true;
|
||||
String[] parts = PgpKeyHelper.splitUserId(userId);
|
||||
if (parts[0] != null) {
|
||||
setTitle(parts[0]);
|
||||
}
|
||||
}
|
||||
mUserIds.add(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mCurrentPassphrase = "";
|
||||
buildLayout(false);
|
||||
|
||||
buildLayout();
|
||||
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
|
||||
if (!mIsPassPhraseSet) {
|
||||
mIsPassphraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
|
||||
if (!mIsPassphraseSet) {
|
||||
// check "no passphrase" checkbox and remove button
|
||||
mNoPassphrase.setChecked(true);
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
@ -390,10 +393,11 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
Bundle data = message.getData();
|
||||
|
||||
// set new returned passphrase!
|
||||
mNewPassPhrase = data
|
||||
mNewPassphrase = data
|
||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
updatePassphraseButtonText();
|
||||
somethingChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -402,7 +406,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
// set title based on isPassphraseSet()
|
||||
int title = -1;
|
||||
int title;
|
||||
if (isPassphraseSet()) {
|
||||
title = R.string.title_change_passphrase;
|
||||
} else {
|
||||
@ -418,30 +422,37 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
/**
|
||||
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
|
||||
* id and key.
|
||||
*
|
||||
* @param newKeys
|
||||
*/
|
||||
private void buildLayout() {
|
||||
private void buildLayout(boolean newKeys) {
|
||||
setContentView(R.layout.edit_key_activity);
|
||||
|
||||
// find views
|
||||
mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
|
||||
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
||||
|
||||
// Build layout based on given userIds and keys
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
|
||||
if (mIsPassphraseSet) {
|
||||
mChangePassphrase.setText(getString(R.string.btn_change_passphrase));
|
||||
}
|
||||
mUserIdsView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mUserIdsView.setType(Id.type.user_id);
|
||||
mUserIdsView.setCanEdit(mMasterCanSign);
|
||||
mUserIdsView.setCanBeEdited(mMasterCanSign);
|
||||
mUserIdsView.setUserIds(mUserIds);
|
||||
mUserIdsView.setEditorListener(this);
|
||||
container.addView(mUserIdsView);
|
||||
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||
mKeysView.setType(Id.type.key);
|
||||
mKeysView.setCanEdit(mMasterCanSign);
|
||||
mKeysView.setKeys(mKeys, mKeysUsages);
|
||||
mKeysView.setCanBeEdited(mMasterCanSign);
|
||||
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
|
||||
mKeysView.setEditorListener(this);
|
||||
container.addView(mKeysView);
|
||||
|
||||
updatePassPhraseButtonText();
|
||||
updatePassphraseButtonText();
|
||||
|
||||
mChangePassphrase.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
@ -449,20 +460,21 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// disable passphrase when no passphrase checkobox is checked!
|
||||
// disable passphrase when no passphrase checkbox is checked!
|
||||
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
// remove passphrase
|
||||
mSavedNewPassPhrase = mNewPassPhrase;
|
||||
mNewPassPhrase = "";
|
||||
mSavedNewPassphrase = mNewPassphrase;
|
||||
mNewPassphrase = "";
|
||||
mChangePassphrase.setVisibility(View.GONE);
|
||||
} else {
|
||||
mNewPassPhrase = mSavedNewPassPhrase;
|
||||
mNewPassphrase = mSavedNewPassphrase;
|
||||
mChangePassphrase.setVisibility(View.VISIBLE);
|
||||
}
|
||||
somethingChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -477,37 +489,116 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
public boolean isPassphraseSet() {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return true;
|
||||
} else if ((mIsPassPhraseSet)
|
||||
|| (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) {
|
||||
} else if ((mIsPassphraseSet)
|
||||
|| (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveClicked() {
|
||||
long masterKeyId = getMasterKeyId();
|
||||
try {
|
||||
if (!isPassphraseSet()) {
|
||||
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
|
||||
public boolean hasPassphraseChanged() {
|
||||
if (mNoPassphrase != null) {
|
||||
if (mNoPassphrase.isChecked()) {
|
||||
return mIsPassphraseSet;
|
||||
} else {
|
||||
return (mNewPassphrase != null && !mNewPassphrase.equals(""));
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String passphrase = null;
|
||||
if (mIsPassPhraseSet) {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
|
||||
} else {
|
||||
passphrase = "";
|
||||
private void saveClicked() {
|
||||
final long masterKeyId = getMasterKeyId();
|
||||
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
|
||||
try {
|
||||
if (!isPassphraseSet()) {
|
||||
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
|
||||
}
|
||||
|
||||
String passphrase;
|
||||
if (mIsPassphraseSet) {
|
||||
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
|
||||
} else {
|
||||
passphrase = "";
|
||||
}
|
||||
if (passphrase == null) {
|
||||
PassphraseDialogFragment.show(this, masterKeyId,
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(
|
||||
EditKeyActivity.this, masterKeyId);
|
||||
checkEmptyIDsWanted();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mCurrentPassphrase = passphrase;
|
||||
checkEmptyIDsWanted();
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (passphrase == null) {
|
||||
showPassphraseDialog(masterKeyId, mMasterCanSign);
|
||||
} else {
|
||||
mCurrentPassphrase = passphrase;
|
||||
finallySaveClicked();
|
||||
} else {
|
||||
AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEmptyIDsWanted() {
|
||||
try {
|
||||
ArrayList<String> userIDs = getUserIds(mUserIdsView);
|
||||
List<Boolean> newIDs = mUserIdsView.getNewIDFlags();
|
||||
ArrayList<String> originalIDs = mUserIdsView.getOriginalIDs();
|
||||
int curID = 0;
|
||||
for (String userID : userIDs) {
|
||||
if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(
|
||||
EditKeyActivity.this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(EditKeyActivity.this.getString(R.string.ask_empty_id_ok));
|
||||
|
||||
alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
finallySaveClicked();
|
||||
}
|
||||
}
|
||||
);
|
||||
alert.setNegativeButton(this.getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
);
|
||||
alert.setCancelable(false);
|
||||
alert.create().show();
|
||||
return;
|
||||
}
|
||||
curID++;
|
||||
}
|
||||
} catch (PgpGeneralException e) {
|
||||
//Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
// Toast.LENGTH_SHORT).show();
|
||||
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
finallySaveClicked();
|
||||
}
|
||||
|
||||
private boolean[] toPrimitiveArray(final List<Boolean> booleanList) {
|
||||
final boolean[] primitives = new boolean[booleanList.size()];
|
||||
int index = 0;
|
||||
for (Boolean object : booleanList) {
|
||||
primitives[index++] = object;
|
||||
}
|
||||
return primitives;
|
||||
}
|
||||
|
||||
private void finallySaveClicked() {
|
||||
@ -517,42 +608,45 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
||||
|
||||
SaveKeyringParcel saveParams = new SaveKeyringParcel();
|
||||
saveParams.userIDs = getUserIds(mUserIdsView);
|
||||
saveParams.originalIDs = mUserIdsView.getOriginalIDs();
|
||||
saveParams.deletedIDs = mUserIdsView.getDeletedIDs();
|
||||
saveParams.newIDs = toPrimitiveArray(mUserIdsView.getNewIDFlags());
|
||||
saveParams.primaryIDChanged = mUserIdsView.primaryChanged();
|
||||
saveParams.moddedKeys = toPrimitiveArray(mKeysView.getNeedsSavingArray());
|
||||
saveParams.deletedKeys = mKeysView.getDeletedKeys();
|
||||
saveParams.keysExpiryDates = getKeysExpiryDates(mKeysView);
|
||||
saveParams.keysUsages = getKeysUsages(mKeysView);
|
||||
saveParams.newPassphrase = mNewPassphrase;
|
||||
saveParams.oldPassphrase = mCurrentPassphrase;
|
||||
saveParams.newKeys = toPrimitiveArray(mKeysView.getNewKeysArray());
|
||||
saveParams.keys = getKeys(mKeysView);
|
||||
saveParams.originalPrimaryID = mUserIdsView.getOriginalPrimaryID();
|
||||
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
data.putString(KeychainIntentService.SAVE_KEYRING_CURRENT_PASSPHRASE,
|
||||
mCurrentPassphrase);
|
||||
data.putString(KeychainIntentService.SAVE_KEYRING_NEW_PASSPHRASE, mNewPassPhrase);
|
||||
data.putStringArrayList(KeychainIntentService.SAVE_KEYRING_USER_IDS,
|
||||
getUserIds(mUserIdsView));
|
||||
ArrayList<PGPSecretKey> keys = getKeys(mKeysView);
|
||||
data.putByteArray(KeychainIntentService.SAVE_KEYRING_KEYS,
|
||||
PgpConversionHelper.PGPSecretKeyArrayListToBytes(keys));
|
||||
data.putIntegerArrayList(KeychainIntentService.SAVE_KEYRING_KEYS_USAGES,
|
||||
getKeysUsages(mKeysView));
|
||||
data.putSerializable(KeychainIntentService.SAVE_KEYRING_KEYS_EXPIRY_DATES,
|
||||
getKeysExpiryDates(mKeysView));
|
||||
data.putLong(KeychainIntentService.SAVE_KEYRING_MASTER_KEY_ID, getMasterKeyId());
|
||||
data.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
|
||||
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after saving is done in ApgService
|
||||
// Message is received after saving is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, getMasterKeyId());
|
||||
ArrayList<String> userIds = null;
|
||||
try {
|
||||
userIds = getUserIds(mUserIdsView);
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.e(Constants.TAG, "exception while getting user ids", e);
|
||||
}
|
||||
data.putExtra(RESULT_EXTRA_USER_ID, userIds.get(0));
|
||||
|
||||
// return uri pointing to new created key
|
||||
Uri uri = KeychainContract.KeyRings.buildGenericKeyRingUri(
|
||||
String.valueOf(getMasterKeyId()));
|
||||
data.setData(uri);
|
||||
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
@ -568,14 +662,42 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
// start service with intent
|
||||
startService(intent);
|
||||
} catch (PgpGeneralException e) {
|
||||
//Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
// Toast.LENGTH_SHORT).show();
|
||||
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelClicked() {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
if (needsSaving()) { //ask if we want to save
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(
|
||||
EditKeyActivity.this);
|
||||
|
||||
alert.setIcon(android.R.drawable.ic_dialog_alert);
|
||||
alert.setTitle(R.string.warning);
|
||||
alert.setMessage(EditKeyActivity.this.getString(R.string.ask_save_changed_key));
|
||||
|
||||
alert.setPositiveButton(EditKeyActivity.this.getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
saveClicked();
|
||||
}
|
||||
});
|
||||
alert.setNegativeButton(this.getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
alert.setCancelable(false);
|
||||
alert.create().show();
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -592,19 +714,8 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
boolean gotMainUserId = false;
|
||||
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
||||
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
||||
String userId = null;
|
||||
try {
|
||||
userId = editor.getValue();
|
||||
} catch (UserIdEditor.NoNameException e) {
|
||||
throw new PgpGeneralException(this.getString(R.string.error_user_id_needs_a_name));
|
||||
} catch (UserIdEditor.NoEmailException e) {
|
||||
throw new PgpGeneralException(
|
||||
this.getString(R.string.error_user_id_needs_an_email_address));
|
||||
}
|
||||
|
||||
if (userId.equals("")) {
|
||||
continue;
|
||||
}
|
||||
String userId;
|
||||
userId = editor.getValue();
|
||||
|
||||
if (editor.isMainUserId()) {
|
||||
userIds.add(0, userId);
|
||||
@ -688,7 +799,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
||||
return keysExpiryDates;
|
||||
}
|
||||
|
||||
private void updatePassPhraseButtonText() {
|
||||
private void updatePassphraseButtonText() {
|
||||
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
|
||||
: getString(R.string.btn_set_passphrase));
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public interface EncryptActivityInterface {
|
||||
|
||||
public boolean isModeSymmetric();
|
||||
|
||||
public long getSignatureKey();
|
||||
public long[] getEncryptionKeys();
|
||||
|
||||
public String getPassphrase();
|
||||
public String getPassphraseAgain();
|
||||
|
||||
}
|
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* 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.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Vector;
|
||||
|
||||
public class EncryptAsymmetricFragment extends Fragment {
|
||||
public static final String ARG_SIGNATURE_KEY_ID = "signature_key_id";
|
||||
public static final String ARG_ENCRYPTION_KEY_IDS = "encryption_key_ids";
|
||||
|
||||
public static final int RESULT_CODE_PUBLIC_KEYS = 0x00007001;
|
||||
public static final int RESULT_CODE_SECRET_KEYS = 0x00007002;
|
||||
|
||||
OnAsymmetricKeySelection mKeySelectionListener;
|
||||
|
||||
// view
|
||||
private BootstrapButton mSelectKeysButton;
|
||||
private CheckBox mSign;
|
||||
private TextView mMainUserId;
|
||||
private TextView mMainUserIdRest;
|
||||
|
||||
// model
|
||||
private long mSecretKeyId = Id.key.none;
|
||||
private long mEncryptionKeyIds[] = null;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnAsymmetricKeySelection {
|
||||
public void onSigningKeySelected(long signingKeyId);
|
||||
|
||||
public void onEncryptionKeysSelected(long[] encryptionKeyIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mKeySelectionListener = (OnAsymmetricKeySelection) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement OnAsymmetricKeySelection");
|
||||
}
|
||||
}
|
||||
|
||||
private void setSignatureKeyId(long signatureKeyId) {
|
||||
mSecretKeyId = signatureKeyId;
|
||||
// update key selection in EncryptActivity
|
||||
mKeySelectionListener.onSigningKeySelected(signatureKeyId);
|
||||
updateView();
|
||||
}
|
||||
|
||||
private void setEncryptionKeyIds(long[] encryptionKeyIds) {
|
||||
mEncryptionKeyIds = encryptionKeyIds;
|
||||
// update key selection in EncryptActivity
|
||||
mKeySelectionListener.onEncryptionKeysSelected(encryptionKeyIds);
|
||||
updateView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_asymmetric_fragment, container, false);
|
||||
|
||||
mSelectKeysButton = (BootstrapButton) view.findViewById(R.id.btn_selectEncryptKeys);
|
||||
mSign = (CheckBox) view.findViewById(R.id.sign);
|
||||
mMainUserId = (TextView) view.findViewById(R.id.mainUserId);
|
||||
mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
|
||||
mSelectKeysButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
selectPublicKeys();
|
||||
}
|
||||
});
|
||||
mSign.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
CheckBox checkBox = (CheckBox) v;
|
||||
if (checkBox.isChecked()) {
|
||||
selectSecretKey();
|
||||
} else {
|
||||
setSignatureKeyId(Id.key.none);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
long signatureKeyId = getArguments().getLong(ARG_SIGNATURE_KEY_ID);
|
||||
long[] encryptionKeyIds = getArguments().getLongArray(ARG_ENCRYPTION_KEY_IDS);
|
||||
|
||||
// preselect keys given by arguments (given by Intent to EncryptActivity)
|
||||
preselectKeys(signatureKeyId, encryptionKeyIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* If an Intent gives a signatureKeyId and/or encryptionKeyIds, preselect those!
|
||||
*
|
||||
* @param preselectedSignatureKeyId
|
||||
* @param preselectedEncryptionKeyIds
|
||||
*/
|
||||
private void preselectKeys(long preselectedSignatureKeyId, long[] preselectedEncryptionKeyIds) {
|
||||
if (preselectedSignatureKeyId != 0) {
|
||||
// TODO: don't use bouncy castle objects!
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(getActivity(),
|
||||
preselectedSignatureKeyId);
|
||||
PGPSecretKey masterKey;
|
||||
if (keyRing != null) {
|
||||
masterKey = keyRing.getSecretKey();
|
||||
if (masterKey != null) {
|
||||
Vector<PGPSecretKey> signKeys = PgpKeyHelper.getUsableSigningKeys(keyRing);
|
||||
if (signKeys.size() > 0) {
|
||||
setSignatureKeyId(masterKey.getKeyID());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preselectedEncryptionKeyIds != null) {
|
||||
Vector<Long> goodIds = new Vector<Long>();
|
||||
for (int i = 0; i < preselectedEncryptionKeyIds.length; ++i) {
|
||||
long id = ProviderHelper.getMasterKeyId(getActivity(),
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(preselectedEncryptionKeyIds[i]))
|
||||
);
|
||||
// TODO check for available encrypt keys... is this even relevant?
|
||||
goodIds.add(id);
|
||||
}
|
||||
if (goodIds.size() > 0) {
|
||||
long[] keyIds = new long[goodIds.size()];
|
||||
for (int i = 0; i < goodIds.size(); ++i) {
|
||||
keyIds[i] = goodIds.get(i);
|
||||
}
|
||||
setEncryptionKeyIds(keyIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateView() {
|
||||
if (mEncryptionKeyIds == null || mEncryptionKeyIds.length == 0) {
|
||||
mSelectKeysButton.setText(getString(R.string.select_keys_button_default));
|
||||
} else {
|
||||
mSelectKeysButton.setText(getResources().getQuantityString(
|
||||
R.plurals.select_keys_button, mEncryptionKeyIds.length,
|
||||
mEncryptionKeyIds.length));
|
||||
}
|
||||
|
||||
if (mSecretKeyId == Id.key.none) {
|
||||
mSign.setChecked(false);
|
||||
mMainUserId.setText("");
|
||||
mMainUserIdRest.setText("");
|
||||
} else {
|
||||
String uid = getResources().getString(R.string.user_id_no_name);
|
||||
String uidExtra = "";
|
||||
// See if we can get a user_id from a unified query
|
||||
String user_id = (String) ProviderHelper.getUnifiedData(
|
||||
getActivity(), mSecretKeyId, KeyRings.USER_ID, ProviderHelper.FIELD_TYPE_STRING);
|
||||
if(user_id != null) {
|
||||
String chunks[] = user_id.split(" <", 2);
|
||||
uid = chunks[0];
|
||||
if (chunks.length > 1) {
|
||||
uidExtra = "<" + chunks[1];
|
||||
}
|
||||
}
|
||||
|
||||
mMainUserId.setText(uid);
|
||||
mMainUserIdRest.setText(uidExtra);
|
||||
mSign.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectPublicKeys() {
|
||||
Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class);
|
||||
Vector<Long> keyIds = new Vector<Long>();
|
||||
if (mSecretKeyId != 0) {
|
||||
keyIds.add(mSecretKeyId);
|
||||
}
|
||||
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
|
||||
for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
|
||||
keyIds.add(mEncryptionKeyIds[i]);
|
||||
}
|
||||
}
|
||||
long[] initialKeyIds = null;
|
||||
if (keyIds.size() > 0) {
|
||||
initialKeyIds = new long[keyIds.size()];
|
||||
for (int i = 0; i < keyIds.size(); ++i) {
|
||||
initialKeyIds[i] = keyIds.get(i);
|
||||
}
|
||||
}
|
||||
intent.putExtra(SelectPublicKeyActivity.EXTRA_SELECTED_MASTER_KEY_IDS, initialKeyIds);
|
||||
startActivityForResult(intent, Id.request.public_keys);
|
||||
}
|
||||
|
||||
private void selectSecretKey() {
|
||||
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
||||
startActivityForResult(intent, Id.request.secret_keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_CODE_PUBLIC_KEYS: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Bundle bundle = data.getExtras();
|
||||
setEncryptionKeyIds(bundle
|
||||
.getLongArray(SelectPublicKeyActivity.RESULT_EXTRA_MASTER_KEY_IDS));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RESULT_CODE_SECRET_KEYS: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uriMasterKey = data.getData();
|
||||
setSignatureKeyId(Long.valueOf(uriMasterKey.getLastPathSegment()));
|
||||
} else {
|
||||
setSignatureKeyId(Id.key.none);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,380 @@
|
||||
/*
|
||||
* 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.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteFileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.FileDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Choice;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class EncryptFileFragment extends Fragment {
|
||||
public static final String ARG_FILENAME = "filename";
|
||||
public static final String ARG_ASCII_ARMOR = "ascii_armor";
|
||||
|
||||
private static final int RESULT_CODE_FILE = 0x00007003;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
|
||||
// view
|
||||
private CheckBox mAsciiArmor = null;
|
||||
private Spinner mFileCompression = null;
|
||||
private EditText mFilename = null;
|
||||
private CheckBox mDeleteAfter = null;
|
||||
private CheckBox mShareAfter = null;
|
||||
private BootstrapButton mBrowse = null;
|
||||
private BootstrapButton mEncryptFile;
|
||||
|
||||
private FileDialogFragment mFileDialog;
|
||||
|
||||
// model
|
||||
private String mInputFilename = null;
|
||||
private String mOutputFilename = null;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_file_fragment, container, false);
|
||||
|
||||
mEncryptFile = (BootstrapButton) view.findViewById(R.id.action_encrypt_file);
|
||||
mEncryptFile.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptClicked();
|
||||
}
|
||||
});
|
||||
|
||||
mFilename = (EditText) view.findViewById(R.id.filename);
|
||||
mBrowse = (BootstrapButton) view.findViewById(R.id.btn_browse);
|
||||
mBrowse.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
FileHelper.openFile(EncryptFileFragment.this, mFilename.getText().toString(), "*/*",
|
||||
Id.request.filename);
|
||||
}
|
||||
});
|
||||
|
||||
mFileCompression = (Spinner) view.findViewById(R.id.fileCompression);
|
||||
Choice[] choices = new Choice[] {
|
||||
new Choice(Id.choice.compression.none, getString(R.string.choice_none) + " ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Id.choice.compression.zip, "ZIP ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Id.choice.compression.zlib, "ZLIB ("
|
||||
+ getString(R.string.compression_fast) + ")"),
|
||||
new Choice(Id.choice.compression.bzip2, "BZIP2 ("
|
||||
+ getString(R.string.compression_very_slow) + ")"),
|
||||
};
|
||||
ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getActivity(),
|
||||
android.R.layout.simple_spinner_item, choices);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
mFileCompression.setAdapter(adapter);
|
||||
|
||||
int defaultFileCompression = Preferences.getPreferences(getActivity()).getDefaultFileCompression();
|
||||
for (int i = 0; i < choices.length; ++i) {
|
||||
if (choices[i].getId() == defaultFileCompression) {
|
||||
mFileCompression.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mDeleteAfter = (CheckBox) view.findViewById(R.id.deleteAfterEncryption);
|
||||
mShareAfter = (CheckBox) view.findViewById(R.id.shareAfterEncryption);
|
||||
|
||||
mAsciiArmor = (CheckBox) view.findViewById(R.id.asciiArmor);
|
||||
mAsciiArmor.setChecked(Preferences.getPreferences(getActivity()).getDefaultAsciiArmor());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String filename = getArguments().getString(ARG_FILENAME);
|
||||
if (filename != null) {
|
||||
mFilename.setText(filename);
|
||||
}
|
||||
boolean asciiArmor = getArguments().getBoolean(ARG_ASCII_ARMOR);
|
||||
if (asciiArmor) {
|
||||
mAsciiArmor.setChecked(asciiArmor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess output filename based on input path
|
||||
*
|
||||
* @param path
|
||||
* @return Suggestion for output filename
|
||||
*/
|
||||
private String guessOutputFilename(String path) {
|
||||
// output in the same directory but with additional ending
|
||||
File file = new File(path);
|
||||
String ending = (mAsciiArmor.isChecked() ? ".asc" : ".gpg");
|
||||
String outputFilename = file.getParent() + File.separator + file.getName() + ending;
|
||||
|
||||
return outputFilename;
|
||||
}
|
||||
|
||||
private void showOutputFileDialog() {
|
||||
// Message is received after file is selected
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == FileDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle data = message.getData();
|
||||
mOutputFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
||||
encryptStart();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
mFileDialog = FileDialogFragment.newInstance(messenger,
|
||||
getString(R.string.title_encrypt_to_file),
|
||||
getString(R.string.specify_file_to_encrypt_to), mOutputFilename, null);
|
||||
|
||||
mFileDialog.show(getActivity().getSupportFragmentManager(), "fileDialog");
|
||||
}
|
||||
|
||||
private void encryptClicked() {
|
||||
String currentFilename = mFilename.getText().toString();
|
||||
if (mInputFilename == null || !mInputFilename.equals(currentFilename)) {
|
||||
mInputFilename = mFilename.getText().toString();
|
||||
}
|
||||
|
||||
mOutputFilename = guessOutputFilename(mInputFilename);
|
||||
|
||||
if (mInputFilename.equals("")) {
|
||||
AppMsg.makeText(getActivity(), R.string.no_file_selected, AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mInputFilename.startsWith("content")) {
|
||||
File file = new File(mInputFilename);
|
||||
if (!file.exists() || !file.isFile()) {
|
||||
AppMsg.makeText(
|
||||
getActivity(),
|
||||
getString(R.string.error_message,
|
||||
getString(R.string.error_file_not_found)), AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
// symmetric encryption
|
||||
|
||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
||||
if (!gotPassphrase) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// asymmetric encryption
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null
|
||||
&& mEncryptInterface.getEncryptionKeys().length > 0);
|
||||
|
||||
if (!gotEncryptionKeys) {
|
||||
AppMsg.makeText(getActivity(), R.string.select_encryption_key, AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
|
||||
AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncryptInterface.getSignatureKey() != 0 &&
|
||||
PassphraseCacheService.getCachedPassphrase(getActivity(),
|
||||
mEncryptInterface.getSignatureKey()) == null) {
|
||||
PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(),
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
showOutputFileDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
showOutputFileDialog();
|
||||
}
|
||||
|
||||
private void encryptStart() {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_URI);
|
||||
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
String passphrase = mEncryptInterface.getPassphrase();
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID,
|
||||
mEncryptInterface.getSignatureKey());
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS,
|
||||
mEncryptInterface.getEncryptionKeys());
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "mInputFilename=" + mInputFilename + ", mOutputFilename="
|
||||
+ mOutputFilename);
|
||||
|
||||
data.putString(KeychainIntentService.ENCRYPT_INPUT_FILE, mInputFilename);
|
||||
data.putString(KeychainIntentService.ENCRYPT_OUTPUT_FILE, mOutputFilename);
|
||||
|
||||
boolean useAsciiArmor = mAsciiArmor.isChecked();
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, useAsciiArmor);
|
||||
|
||||
int compressionId = ((Choice) mFileCompression.getSelectedItem()).getId();
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
|
||||
// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
AppMsg.makeText(getActivity(), R.string.encryption_successful,
|
||||
AppMsg.STYLE_INFO).show();
|
||||
|
||||
if (mDeleteAfter.isChecked()) {
|
||||
// Create and show dialog to delete original file
|
||||
DeleteFileDialogFragment deleteFileDialog = DeleteFileDialogFragment
|
||||
.newInstance(mInputFilename);
|
||||
deleteFileDialog.show(getActivity().getSupportFragmentManager(), "deleteDialog");
|
||||
}
|
||||
|
||||
if (mShareAfter.isChecked()) {
|
||||
// Share encrypted file
|
||||
Intent sendFileIntent = new Intent(Intent.ACTION_SEND);
|
||||
sendFileIntent.setType("*/*");
|
||||
sendFileIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(mOutputFilename));
|
||||
startActivity(Intent.createChooser(sendFileIntent,
|
||||
getString(R.string.title_send_file)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_CODE_FILE: {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
try {
|
||||
String path = FileHelper.getPath(getActivity(), data.getData());
|
||||
Log.d(Constants.TAG, "path=" + path);
|
||||
|
||||
mFilename.setText(path);
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(Constants.TAG, "Nullpointer while retrieving path!");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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.app.ProgressDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
public class EncryptMessageFragment extends Fragment {
|
||||
public static final String ARG_TEXT = "text";
|
||||
|
||||
private EditText mMessage = null;
|
||||
private BootstrapButton mEncryptShare;
|
||||
private BootstrapButton mEncryptClipboard;
|
||||
|
||||
private EncryptActivityInterface mEncryptInterface;
|
||||
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mEncryptInterface = (EncryptActivityInterface) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement EncryptActivityInterface");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false);
|
||||
|
||||
mMessage = (EditText) view.findViewById(R.id.message);
|
||||
mEncryptClipboard = (BootstrapButton) view.findViewById(R.id.action_encrypt_clipboard);
|
||||
mEncryptShare = (BootstrapButton) view.findViewById(R.id.action_encrypt_share);
|
||||
mEncryptClipboard.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptClicked(true);
|
||||
}
|
||||
});
|
||||
mEncryptShare.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptClicked(false);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
String text = getArguments().getString(ARG_TEXT);
|
||||
if (text != null) {
|
||||
mMessage.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes bad message characters for gmail
|
||||
*
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
private String fixBadCharactersForGmail(String message) {
|
||||
// fix the message a bit, trailing spaces and newlines break stuff,
|
||||
// because GMail sends as HTML and such things fuck up the
|
||||
// signature,
|
||||
// TODO: things like "<" and ">" also fuck up the signature
|
||||
message = message.replaceAll(" +\n", "\n");
|
||||
message = message.replaceAll("\n\n+", "\n\n");
|
||||
message = message.replaceFirst("^\n+", "");
|
||||
// make sure there'll be exactly one newline at the end
|
||||
message = message.replaceFirst("\n*$", "\n");
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private void encryptClicked(final boolean toClipboard) {
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
// symmetric encryption
|
||||
|
||||
boolean gotPassphrase = (mEncryptInterface.getPassphrase() != null
|
||||
&& mEncryptInterface.getPassphrase().length() != 0);
|
||||
if (!gotPassphrase) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrase_must_not_be_empty, AppMsg.STYLE_ALERT)
|
||||
.show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mEncryptInterface.getPassphrase().equals(mEncryptInterface.getPassphraseAgain())) {
|
||||
AppMsg.makeText(getActivity(), R.string.passphrases_do_not_match, AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
// asymmetric encryption
|
||||
|
||||
boolean gotEncryptionKeys = (mEncryptInterface.getEncryptionKeys() != null
|
||||
&& mEncryptInterface.getEncryptionKeys().length > 0);
|
||||
|
||||
if (!gotEncryptionKeys && mEncryptInterface.getSignatureKey() == 0) {
|
||||
AppMsg.makeText(getActivity(), R.string.select_encryption_or_signature_key,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mEncryptInterface.getSignatureKey() != 0 &&
|
||||
PassphraseCacheService.getCachedPassphrase(getActivity(),
|
||||
mEncryptInterface.getSignatureKey()) == null) {
|
||||
PassphraseDialogFragment.show(getActivity(), mEncryptInterface.getSignatureKey(),
|
||||
new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
|
||||
encryptStart(toClipboard);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
encryptStart(toClipboard);
|
||||
}
|
||||
|
||||
private void encryptStart(final boolean toClipboard) {
|
||||
// Send all information needed to service to edit key in other thread
|
||||
Intent intent = new Intent(getActivity(), KeychainIntentService.class);
|
||||
|
||||
intent.setAction(KeychainIntentService.ACTION_ENCRYPT_SIGN);
|
||||
|
||||
// fill values for this action
|
||||
Bundle data = new Bundle();
|
||||
|
||||
data.putInt(KeychainIntentService.TARGET, KeychainIntentService.TARGET_BYTES);
|
||||
|
||||
String message = mMessage.getText().toString();
|
||||
|
||||
if (mEncryptInterface.isModeSymmetric()) {
|
||||
Log.d(Constants.TAG, "Symmetric encryption enabled!");
|
||||
String passphrase = mEncryptInterface.getPassphrase();
|
||||
if (passphrase.length() == 0) {
|
||||
passphrase = null;
|
||||
}
|
||||
data.putString(KeychainIntentService.ENCRYPT_SYMMETRIC_PASSPHRASE, passphrase);
|
||||
} else {
|
||||
data.putLong(KeychainIntentService.ENCRYPT_SIGNATURE_KEY_ID,
|
||||
mEncryptInterface.getSignatureKey());
|
||||
data.putLongArray(KeychainIntentService.ENCRYPT_ENCRYPTION_KEYS_IDS,
|
||||
mEncryptInterface.getEncryptionKeys());
|
||||
|
||||
boolean signOnly = (mEncryptInterface.getEncryptionKeys() == null
|
||||
|| mEncryptInterface.getEncryptionKeys().length == 0);
|
||||
if (signOnly) {
|
||||
message = fixBadCharactersForGmail(message);
|
||||
}
|
||||
}
|
||||
|
||||
data.putByteArray(KeychainIntentService.ENCRYPT_MESSAGE_BYTES, message.getBytes());
|
||||
|
||||
data.putBoolean(KeychainIntentService.ENCRYPT_USE_ASCII_ARMOR, true);
|
||||
|
||||
int compressionId = Preferences.getPreferences(getActivity()).getDefaultMessageCompression();
|
||||
data.putInt(KeychainIntentService.ENCRYPT_COMPRESSION_ID, compressionId);
|
||||
// data.putBoolean(KeychainIntentService.ENCRYPT_GENERATE_SIGNATURE, mGenerateSignature);
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after encrypting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(getActivity(),
|
||||
getString(R.string.progress_encrypting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
// get returned data bundle
|
||||
Bundle data = message.getData();
|
||||
|
||||
String output = new String(data.getByteArray(KeychainIntentService.RESULT_BYTES));
|
||||
Log.d(Constants.TAG, "output: " + output);
|
||||
|
||||
if (toClipboard) {
|
||||
ClipboardReflection.copyToClipboard(getActivity(), output);
|
||||
AppMsg.makeText(getActivity(),
|
||||
R.string.encryption_to_clipboard_successful, AppMsg.STYLE_INFO)
|
||||
.show();
|
||||
} else {
|
||||
Intent sendIntent = new Intent(Intent.ACTION_SEND);
|
||||
|
||||
// Type is set to text/plain so that encrypted messages can
|
||||
// be sent with Whatsapp, Hangouts, SMS etc...
|
||||
sendIntent.setType("text/plain");
|
||||
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, output);
|
||||
startActivity(Intent.createChooser(sendIntent,
|
||||
getString(R.string.title_send_email)));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(saveHandler);
|
||||
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
|
||||
|
||||
// show progress dialog
|
||||
saveHandler.showProgressDialog(getActivity());
|
||||
|
||||
// start service with intent
|
||||
getActivity().startService(intent);
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class EncryptSymmetricFragment extends Fragment {
|
||||
|
||||
OnSymmetricKeySelection mPassphraseUpdateListener;
|
||||
|
||||
private EditText mPassphrase;
|
||||
private EditText mPassphraseAgain;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
public interface OnSymmetricKeySelection {
|
||||
public void onPassphraseUpdate(String passphrase);
|
||||
|
||||
public void onPassphraseAgainUpdate(String passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
try {
|
||||
mPassphraseUpdateListener = (OnSymmetricKeySelection) activity;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(activity.toString() + " must implement OnSymmetricKeySelection");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.encrypt_symmetric_fragment, container, false);
|
||||
|
||||
mPassphrase = (EditText) view.findViewById(R.id.passphrase);
|
||||
mPassphraseAgain = (EditText) view.findViewById(R.id.passphraseAgain);
|
||||
mPassphrase.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// update passphrase in EncryptActivity
|
||||
mPassphraseUpdateListener.onPassphraseUpdate(s.toString());
|
||||
}
|
||||
});
|
||||
mPassphraseAgain.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
// update passphrase in EncryptActivity
|
||||
mPassphraseUpdateListener.onPassphraseAgainUpdate(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2011 Senecaso
|
||||
*
|
||||
*
|
||||
* 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
|
||||
@ -18,6 +18,7 @@
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -34,8 +35,10 @@ import android.support.v7.app.ActionBar;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.devspark.appmsg.AppMsg;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
@ -54,7 +57,6 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
+ "IMPORT_KEY_FROM_QR_CODE";
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_KEYSERVER";
|
||||
// TODO: implement:
|
||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
|
||||
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
|
||||
|
||||
@ -72,6 +74,10 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
public static final String EXTRA_KEY_ID = "key_id";
|
||||
public static final String EXTRA_FINGERPRINT = "fingerprint";
|
||||
|
||||
// only used by ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN when used from OpenPgpService
|
||||
public static final String EXTRA_PENDING_INTENT_DATA = "data";
|
||||
private Intent mPendingIntentData;
|
||||
|
||||
// view
|
||||
private ImportKeysListFragment mListFragment;
|
||||
private String[] mNavigationStrings;
|
||||
@ -86,7 +92,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
ImportKeysNFCFragment.class
|
||||
};
|
||||
|
||||
private int mCurrentNavPostition = -1;
|
||||
private int mCurrentNavPosition = -1;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -102,17 +108,22 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
}
|
||||
});
|
||||
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
|
||||
// set drop down navigation
|
||||
mNavigationStrings = getResources().getStringArray(R.array.import_action_list);
|
||||
Context context = getSupportActionBar().getThemedContext();
|
||||
ArrayAdapter<CharSequence> navigationAdapter = ArrayAdapter.createFromResource(context,
|
||||
R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item);
|
||||
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||
getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this);
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
|
||||
setTitle(R.string.nav_import);
|
||||
} else {
|
||||
getSupportActionBar().setDisplayShowTitleEnabled(false);
|
||||
|
||||
setupDrawerNavigation(savedInstanceState);
|
||||
|
||||
// set drop down navigation
|
||||
Context context = getSupportActionBar().getThemedContext();
|
||||
ArrayAdapter<CharSequence> navigationAdapter = ArrayAdapter.createFromResource(context,
|
||||
R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item);
|
||||
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
||||
getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this);
|
||||
}
|
||||
|
||||
handleActions(savedInstanceState, getIntent());
|
||||
}
|
||||
@ -152,33 +163,52 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
// action: directly load data
|
||||
startListFragment(savedInstanceState, importData, null, null);
|
||||
}
|
||||
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)) {
|
||||
String query = null;
|
||||
if (extras.containsKey(EXTRA_QUERY)) {
|
||||
query = extras.getString(EXTRA_QUERY);
|
||||
} else if (extras.containsKey(EXTRA_KEY_ID)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
query = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)
|
||||
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action)) {
|
||||
|
||||
// only used for OpenPgpService
|
||||
if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) {
|
||||
mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA);
|
||||
}
|
||||
if (extras.containsKey(EXTRA_QUERY) || extras.containsKey(EXTRA_KEY_ID)) {
|
||||
/* simple search based on query or key id */
|
||||
|
||||
String query = null;
|
||||
if (extras.containsKey(EXTRA_QUERY)) {
|
||||
query = extras.getString(EXTRA_QUERY);
|
||||
} else if (extras.containsKey(EXTRA_KEY_ID)) {
|
||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
|
||||
if (keyId != 0) {
|
||||
query = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
if (query != null && query.length() > 0) {
|
||||
// display keyserver fragment with query
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
|
||||
loadNavFragment(0, args);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(savedInstanceState, null, null, query);
|
||||
} else {
|
||||
Log.e(Constants.TAG, "Query is empty!");
|
||||
return;
|
||||
}
|
||||
} else if (extras.containsKey(EXTRA_FINGERPRINT)) {
|
||||
/*
|
||||
* search based on fingerprint, here we can enforce a check in the end
|
||||
* if the right key has been downloaded
|
||||
*/
|
||||
|
||||
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
|
||||
if (fingerprint != null) {
|
||||
query = "0x" + fingerprint;
|
||||
}
|
||||
loadFromFingerprint(savedInstanceState, fingerprint);
|
||||
} else {
|
||||
Log.e(Constants.TAG,
|
||||
"IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or 'fingerprint' extra!");
|
||||
"IMPORT_KEY_FROM_KEYSERVER action needs to contain the 'query', 'key_id', or " +
|
||||
"'fingerprint' extra!");
|
||||
return;
|
||||
}
|
||||
|
||||
// display keyserver fragment with query
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
|
||||
loadNavFragment(0, args);
|
||||
|
||||
// action: search immediately
|
||||
startListFragment(savedInstanceState, null, null, query);
|
||||
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
|
||||
|
||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
||||
@ -233,14 +263,14 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
* onNavigationItemSelected() should check whether the Fragment is already in existence
|
||||
* inside your Activity."
|
||||
* <p/>
|
||||
* from http://stackoverflow.com/questions/10983396/fragment-oncreateview-and-onactivitycreated-called-twice/14295474#14295474
|
||||
* from http://stackoverflow.com/a/14295474
|
||||
* <p/>
|
||||
* In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint,
|
||||
* the fragment would be loaded twice resulting in the query being empty after the second load.
|
||||
* <p/>
|
||||
* Our solution:
|
||||
* To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment
|
||||
* checks against mCurrentNavPostition.
|
||||
* checks against mCurrentNavPosition.
|
||||
*
|
||||
* @param itemPosition
|
||||
* @param itemId
|
||||
@ -256,10 +286,12 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
}
|
||||
|
||||
private void loadNavFragment(int itemPosition, Bundle args) {
|
||||
if (mCurrentNavPostition != itemPosition) {
|
||||
getSupportActionBar().setSelectedNavigationItem(itemPosition);
|
||||
if (mCurrentNavPosition != itemPosition) {
|
||||
if (ActionBar.NAVIGATION_MODE_LIST == getSupportActionBar().getNavigationMode()) {
|
||||
getSupportActionBar().setSelectedNavigationItem(itemPosition);
|
||||
}
|
||||
loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[itemPosition]);
|
||||
mCurrentNavPostition = itemPosition;
|
||||
mCurrentNavPosition = itemPosition;
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,7 +311,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
|
||||
Log.d(Constants.TAG, "fingerprint: " + fingerprint);
|
||||
|
||||
if (fingerprint.length() < 16) {
|
||||
loadFromFingerprint(savedInstanceState, fingerprint);
|
||||
}
|
||||
|
||||
public void loadFromFingerprint(Bundle savedInstanceState, String fingerprint) {
|
||||
if (fingerprint == null || fingerprint.length() < 40) {
|
||||
AppMsg.makeText(this, R.string.import_qr_code_too_short_fingerprint,
|
||||
AppMsg.STYLE_ALERT).show();
|
||||
return;
|
||||
@ -290,6 +326,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
// display keyserver fragment with query
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
|
||||
args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
|
||||
loadNavFragment(0, args);
|
||||
|
||||
// action: search directly
|
||||
@ -300,70 +337,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
mListFragment.loadNew(importData, dataUri, serverQuery, keyServer);
|
||||
}
|
||||
|
||||
// private void importAndSignOld(final long keyId, final String expectedFingerprint) {
|
||||
// if (expectedFingerprint != null && expectedFingerprint.length() > 0) {
|
||||
//
|
||||
// Thread t = new Thread() {
|
||||
// @Override
|
||||
// public void run() {
|
||||
// try {
|
||||
// // TODO: display some sort of spinner here while the user waits
|
||||
//
|
||||
// // TODO: there should be only 1
|
||||
// HkpKeyServer server = new HkpKeyServer(mPreferences.getKeyServers()[0]);
|
||||
// String encodedKey = server.get(keyId);
|
||||
//
|
||||
// PGPKeyRing keyring = PGPHelper.decodeKeyRing(new ByteArrayInputStream(
|
||||
// encodedKey.getBytes()));
|
||||
// if (keyring != null && keyring instanceof PGPPublicKeyRing) {
|
||||
// PGPPublicKeyRing publicKeyRing = (PGPPublicKeyRing) keyring;
|
||||
//
|
||||
// // make sure the fingerprints match before we cache this thing
|
||||
// String actualFingerprint = PGPHelper.convertFingerprintToHex(publicKeyRing
|
||||
// .getPublicKey().getFingerprint());
|
||||
// if (expectedFingerprint.equals(actualFingerprint)) {
|
||||
// // store the signed key in our local cache
|
||||
// int retval = PGPMain.storeKeyRingInCache(publicKeyRing);
|
||||
// if (retval != Id.return_value.ok
|
||||
// && retval != Id.return_value.updated) {
|
||||
// status.putString(EXTRA_ERROR,
|
||||
// "Failed to store signed key in local cache");
|
||||
// } else {
|
||||
// Intent intent = new Intent(ImportFromQRCodeActivity.this,
|
||||
// SignKeyActivity.class);
|
||||
// intent.putExtra(EXTRA_KEY_ID, keyId);
|
||||
// startActivityForResult(intent, Id.request.sign_key);
|
||||
// }
|
||||
// } else {
|
||||
// status.putString(
|
||||
// EXTRA_ERROR,
|
||||
// "Scanned fingerprint does NOT match the fingerprint of the received key. You shouldnt trust this key.");
|
||||
// }
|
||||
// }
|
||||
// } catch (QueryException e) {
|
||||
// Log.e(TAG, "Failed to query KeyServer", e);
|
||||
// status.putString(EXTRA_ERROR, "Failed to query KeyServer");
|
||||
// status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
// } catch (IOException e) {
|
||||
// Log.e(TAG, "Failed to query KeyServer", e);
|
||||
// status.putString(EXTRA_ERROR, "Failed to query KeyServer");
|
||||
// status.putInt(Constants.extras.STATUS, Id.message.done);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// t.setName("KeyExchange Download Thread");
|
||||
// t.setDaemon(true);
|
||||
// t.start();
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Import keys with mImportData
|
||||
*/
|
||||
public void importKeys() {
|
||||
// Message is received after importing is done in ApgService
|
||||
// Message is received after importing is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||
this,
|
||||
getString(R.string.progress_importing),
|
||||
@ -403,6 +381,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
||||
BadImportKeyDialogFragment.newInstance(bad);
|
||||
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
|
||||
}
|
||||
|
||||
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
|
||||
ImportKeysActivity.this.setResult(Activity.RESULT_OK, mPendingIntentData);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
@ -24,9 +25,13 @@ import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ImportKeysClipboardFragment extends Fragment {
|
||||
|
||||
private ImportKeysActivity mImportActivity;
|
||||
@ -60,6 +65,10 @@ public class ImportKeysClipboardFragment extends Fragment {
|
||||
String sendText = "";
|
||||
if (clipboardText != null) {
|
||||
sendText = clipboardText.toString();
|
||||
if (sendText.toLowerCase(Locale.ENGLISH).startsWith(Constants.FINGERPRINT_SCHEME)) {
|
||||
mImportActivity.loadFromFingerprintUri(null, Uri.parse(sendText));
|
||||
return;
|
||||
}
|
||||
}
|
||||
mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
|
||||
}
|
||||
|
@ -29,7 +29,11 @@ import com.devspark.appmsg.AppMsg;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.*;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
|
||||
import org.sufficientlysecure.keychain.util.InputData;
|
||||
import org.sufficientlysecure.keychain.util.KeyServer;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
@ -57,7 +57,7 @@ public class ImportKeysNFCFragment extends Fragment {
|
||||
public void onClick(View v) {
|
||||
// show nfc help
|
||||
Intent intent = new Intent(getActivity(), HelpActivity.class);
|
||||
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 1);
|
||||
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 2);
|
||||
startActivityForResult(intent, 0);
|
||||
}
|
||||
});
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -28,8 +30,9 @@ import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import com.google.zxing.integration.android.IntentResult;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
|
||||
|
@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
public class ImportKeysServerFragment extends Fragment {
|
||||
public static final String ARG_QUERY = "query";
|
||||
public static final String ARG_KEY_SERVER = "key_server";
|
||||
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
|
||||
|
||||
private ImportKeysActivity mImportActivity;
|
||||
|
||||
@ -140,6 +141,10 @@ public class ImportKeysServerFragment extends Fragment {
|
||||
|
||||
Log.d(Constants.TAG, "keyServer: " + keyServer);
|
||||
}
|
||||
|
||||
if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
|
||||
mQueryEditText.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,8 +21,8 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
|
||||
@ -53,27 +53,21 @@ public class KeyListActivity extends DrawerActivity {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_key_list_import:
|
||||
Intent intentImport = new Intent(this, ImportKeysActivity.class);
|
||||
startActivityForResult(intentImport, 0);
|
||||
|
||||
callIntentForDrawerItem(Constants.DrawerItems.IMPORT_KEYS);
|
||||
return true;
|
||||
case R.id.menu_key_list_export:
|
||||
// TODO fix this for unified keylist
|
||||
mExportHelper.showExportKeysDialog(null, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB);
|
||||
|
||||
return true;
|
||||
case R.id.menu_key_list_create:
|
||||
createKey();
|
||||
|
||||
return true;
|
||||
|
||||
case R.id.menu_key_list_create_expert:
|
||||
createKeyExpert();
|
||||
|
||||
return true;
|
||||
case R.id.menu_key_list_secret_export:
|
||||
mExportHelper.showExportKeysDialog(null, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC);
|
||||
|
||||
case R.id.menu_key_list_export:
|
||||
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE_PUB, true);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.*;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
@ -33,22 +37,29 @@ import android.support.v4.view.MenuItemCompat;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.*;
|
||||
import android.view.ActionMode;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.*;
|
||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
@ -56,7 +67,6 @@ import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
@ -142,9 +152,6 @@ public class KeyListFragment extends Fragment
|
||||
} catch (ApiLevelTooLowException e) {
|
||||
}
|
||||
|
||||
// this view is made visible if no data is available
|
||||
mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
|
||||
|
||||
/*
|
||||
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
|
||||
* available for Android >= 3.0
|
||||
@ -178,18 +185,15 @@ public class KeyListFragment extends Fragment
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_delete: {
|
||||
ids = mStickyList.getWrappedList().getCheckedItemIds();
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
showDeleteKeyDialog(mode, ids);
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_export: {
|
||||
// todo: public/secret needs to be handled differently here
|
||||
ids = mStickyList.getWrappedList().getCheckedItemIds();
|
||||
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
|
||||
mExportHelper
|
||||
.showExportKeysDialog(ids,
|
||||
Id.type.public_key,
|
||||
Constants.Path.APP_DIR_FILE_PUB);
|
||||
mExportHelper.showExportKeysDialog(
|
||||
ids, Constants.Path.APP_DIR_FILE_PUB, mAdapter.isAnySecretSelected());
|
||||
break;
|
||||
}
|
||||
case R.id.menu_key_list_multi_select_all: {
|
||||
@ -243,23 +247,17 @@ public class KeyListFragment extends Fragment
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[]{
|
||||
KeychainContract.KeyRings._ID,
|
||||
KeychainContract.KeyRings.TYPE,
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.UserIds.USER_ID,
|
||||
KeychainContract.Keys.IS_REVOKED
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
KeyRings.USER_ID,
|
||||
KeyRings.IS_REVOKED,
|
||||
KeyRings.HAS_SECRET
|
||||
};
|
||||
|
||||
static final int INDEX_TYPE = 1;
|
||||
static final int INDEX_MASTER_KEY_ID = 2;
|
||||
static final int INDEX_USER_ID = 3;
|
||||
static final int INDEX_IS_REVOKED = 4;
|
||||
|
||||
static final String SORT_ORDER =
|
||||
// show secret before public key
|
||||
KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings.TYPE + " DESC, "
|
||||
// sort by user id otherwise
|
||||
+ UserIds.USER_ID + " ASC";
|
||||
static final int INDEX_MASTER_KEY_ID = 1;
|
||||
static final int INDEX_USER_ID = 2;
|
||||
static final int INDEX_IS_REVOKED = 3;
|
||||
static final int INDEX_HAS_SECRET = 4;
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
@ -269,12 +267,12 @@ public class KeyListFragment extends Fragment
|
||||
String where = null;
|
||||
String whereArgs[] = null;
|
||||
if (mCurQuery != null) {
|
||||
where = KeychainContract.UserIds.USER_ID + " LIKE ?";
|
||||
where = KeyRings.USER_ID + " LIKE ?";
|
||||
whereArgs = new String[]{"%" + mCurQuery + "%"};
|
||||
}
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, SORT_ORDER);
|
||||
return new CursorLoader(getActivity(), baseUri, PROJECTION, where, whereArgs, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -286,6 +284,9 @@ public class KeyListFragment extends Fragment
|
||||
|
||||
mStickyList.setAdapter(mAdapter);
|
||||
|
||||
// this view is made visible if no data is available
|
||||
mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
|
||||
|
||||
// NOTE: Not supported by StickyListHeader, but reimplemented here
|
||||
// The list should now be shown.
|
||||
if (isResumed()) {
|
||||
@ -315,17 +316,15 @@ public class KeyListFragment extends Fragment
|
||||
viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);
|
||||
}
|
||||
viewIntent.setData(
|
||||
KeychainContract
|
||||
.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
|
||||
Long.toString(mAdapter.getMasterKeyId(position))));
|
||||
KeyRings.buildGenericKeyRingUri(Long.toString(mAdapter.getMasterKeyId(position))));
|
||||
startActivity(viewIntent);
|
||||
}
|
||||
|
||||
@TargetApi(11)
|
||||
protected void encrypt(ActionMode mode, long[] keyRingMasterKeyIds) {
|
||||
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
|
||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, keyRingMasterKeyIds);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, masterKeyIds);
|
||||
// used instead of startActivity set actionbar based on callingPackage
|
||||
startActivityForResult(intent, 0);
|
||||
|
||||
@ -335,35 +334,17 @@ public class KeyListFragment extends Fragment
|
||||
/**
|
||||
* Show dialog to delete key
|
||||
*
|
||||
* @param keyRingRowIds
|
||||
* @param masterKeyIds
|
||||
*/
|
||||
@TargetApi(11)
|
||||
// TODO: this method needs an overhaul to handle both public and secret keys gracefully!
|
||||
public void showDeleteKeyDialog(final ActionMode mode, long[] keyRingRowIds) {
|
||||
public void showDeleteKeyDialog(final ActionMode mode, long[] masterKeyIds) {
|
||||
// Message is received after key is deleted
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData != null
|
||||
&& returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) {
|
||||
ArrayList<String> notDeleted =
|
||||
returnData.getStringArrayList(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED);
|
||||
String notDeletedMsg = "";
|
||||
for (String userId : notDeleted) {
|
||||
notDeletedMsg += userId + "\n";
|
||||
}
|
||||
Toast.makeText(getActivity(),
|
||||
getString(R.string.error_can_not_delete_contacts, notDeletedMsg)
|
||||
+ getResources()
|
||||
.getQuantityString(
|
||||
R.plurals.error_can_not_delete_info,
|
||||
notDeleted.size()),
|
||||
Toast.LENGTH_LONG).show();
|
||||
|
||||
mode.finish();
|
||||
}
|
||||
mode.finish();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -372,7 +353,7 @@ public class KeyListFragment extends Fragment
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
||||
keyRingRowIds, Id.type.public_key);
|
||||
masterKeyIds);
|
||||
|
||||
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
|
||||
}
|
||||
@ -506,11 +487,15 @@ public class KeyListFragment extends Fragment
|
||||
}
|
||||
|
||||
{ // set edit button and revoked info, specific by key type
|
||||
View statusDivider = (View) view.findViewById(R.id.status_divider);
|
||||
FrameLayout statusLayout = (FrameLayout) view.findViewById(R.id.status_layout);
|
||||
Button button = (Button) view.findViewById(R.id.edit);
|
||||
TextView revoked = (TextView) view.findViewById(R.id.revoked);
|
||||
|
||||
if (cursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) {
|
||||
if (cursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
|
||||
// this is a secret key - show the edit button
|
||||
statusDivider.setVisibility(View.VISIBLE);
|
||||
statusLayout.setVisibility(View.VISIBLE);
|
||||
revoked.setVisibility(View.GONE);
|
||||
button.setVisibility(View.VISIBLE);
|
||||
|
||||
@ -518,26 +503,31 @@ public class KeyListFragment extends Fragment
|
||||
button.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
editIntent.setData(
|
||||
KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(
|
||||
Long.toString(id)
|
||||
)
|
||||
);
|
||||
editIntent.setData(KeyRingData.buildSecretKeyRingUri(Long.toString(id)));
|
||||
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
startActivityForResult(editIntent, 0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// this is a public key - hide the edit button, show if it's revoked
|
||||
statusDivider.setVisibility(View.GONE);
|
||||
button.setVisibility(View.GONE);
|
||||
|
||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||
statusLayout.setVisibility(isRevoked ? View.VISIBLE : View.GONE);
|
||||
revoked.setVisibility(isRevoked ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public boolean isSecretAvailable(int id) {
|
||||
if (!mCursor.moveToPosition(id)) {
|
||||
throw new IllegalStateException("couldn't move cursor to position " + id);
|
||||
}
|
||||
|
||||
return mCursor.getInt(INDEX_HAS_SECRET) != 0;
|
||||
}
|
||||
public long getMasterKeyId(int id) {
|
||||
if (!mCursor.moveToPosition(id)) {
|
||||
throw new IllegalStateException("couldn't move cursor to position " + id);
|
||||
@ -581,7 +571,7 @@ public class KeyListFragment extends Fragment
|
||||
throw new IllegalStateException("couldn't move cursor to position " + position);
|
||||
}
|
||||
|
||||
if (mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) {
|
||||
if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
|
||||
{ // set contact count
|
||||
int num = mCursor.getCount();
|
||||
String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num);
|
||||
@ -620,7 +610,7 @@ public class KeyListFragment extends Fragment
|
||||
}
|
||||
|
||||
// early breakout: all secret keys are assigned id 0
|
||||
if (mCursor.getInt(KeyListFragment.INDEX_TYPE) == KeyTypes.SECRET) {
|
||||
if (mCursor.getInt(KeyListFragment.INDEX_HAS_SECRET) != 0) {
|
||||
return 1L;
|
||||
}
|
||||
// otherwise, return the first character of the name as ID
|
||||
@ -645,6 +635,14 @@ public class KeyListFragment extends Fragment
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public boolean isAnySecretSelected() {
|
||||
for (int pos : mSelection.keySet()) {
|
||||
if(mAdapter.isSecretAvailable(pos))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public long[] getCurrentSelectedMasterKeyIds() {
|
||||
long[] ids = new long[mSelection.size()];
|
||||
int i = 0;
|
||||
|
@ -20,9 +20,15 @@ import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.*;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@ -38,11 +44,11 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
|
||||
|
||||
private PreferenceScreen mKeyServerPreference = null;
|
||||
private static Preferences mPreferences;
|
||||
private static Preferences sPreferences;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
mPreferences = Preferences.getPreferences(this);
|
||||
sPreferences = Preferences.getPreferences(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// final ActionBar actionBar = getSupportActionBar();
|
||||
@ -55,11 +61,11 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
if (action != null && action.equals(ACTION_PREFS_GEN)) {
|
||||
addPreferencesFromResource(R.xml.gen_preferences);
|
||||
|
||||
initializePassPassPhraceCacheTtl(
|
||||
(IntegerListPreference) findPreference(Constants.Pref.PASS_PHRASE_CACHE_TTL));
|
||||
initializePassPassphraceCacheTtl(
|
||||
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
|
||||
|
||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
||||
String servers[] = mPreferences.getKeyServers();
|
||||
String servers[] = sPreferences.getKeyServers();
|
||||
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
||||
servers.length, servers.length));
|
||||
mKeyServerPreference
|
||||
@ -68,7 +74,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
Intent intent = new Intent(PreferencesActivity.this,
|
||||
PreferencesKeyServerActivity.class);
|
||||
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
||||
mPreferences.getKeyServers());
|
||||
sPreferences.getKeyServers());
|
||||
startActivityForResult(intent, Id.request.key_server_preference);
|
||||
return false;
|
||||
}
|
||||
@ -104,8 +110,8 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
|
||||
entries, values);
|
||||
|
||||
initializeAsciiArmour(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOUR));
|
||||
initializeAsciiArmor(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
|
||||
|
||||
initializeForceV3Signatures(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||
@ -125,7 +131,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
}
|
||||
String servers[] = data
|
||||
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
||||
mPreferences.setKeyServers(servers);
|
||||
sPreferences.setKeyServers(servers);
|
||||
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
||||
R.plurals.n_key_servers, servers.length, servers.length));
|
||||
break;
|
||||
@ -159,11 +165,11 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
// Load the preferences from an XML resource
|
||||
addPreferencesFromResource(R.xml.gen_preferences);
|
||||
|
||||
initializePassPassPhraceCacheTtl(
|
||||
(IntegerListPreference) findPreference(Constants.Pref.PASS_PHRASE_CACHE_TTL));
|
||||
initializePassPassphraceCacheTtl(
|
||||
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
|
||||
|
||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
||||
String servers[] = mPreferences.getKeyServers();
|
||||
String servers[] = sPreferences.getKeyServers();
|
||||
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
||||
servers.length, servers.length));
|
||||
mKeyServerPreference
|
||||
@ -172,7 +178,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
Intent intent = new Intent(getActivity(),
|
||||
PreferencesKeyServerActivity.class);
|
||||
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
||||
mPreferences.getKeyServers());
|
||||
sPreferences.getKeyServers());
|
||||
startActivityForResult(intent, Id.request.key_server_preference);
|
||||
return false;
|
||||
}
|
||||
@ -188,7 +194,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
}
|
||||
String servers[] = data
|
||||
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
||||
mPreferences.setKeyServers(servers);
|
||||
sPreferences.setKeyServers(servers);
|
||||
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
||||
R.plurals.n_key_servers, servers.length, servers.length));
|
||||
break;
|
||||
@ -241,8 +247,8 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
|
||||
entries, values);
|
||||
|
||||
initializeAsciiArmour(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOUR));
|
||||
initializeAsciiArmor(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
|
||||
|
||||
initializeForceV3Signatures(
|
||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||
@ -255,15 +261,15 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
|| super.isValidFragment(fragmentName);
|
||||
}
|
||||
|
||||
private static void initializePassPassPhraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) {
|
||||
mPassphraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl());
|
||||
private static void initializePassPassphraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) {
|
||||
mPassphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl());
|
||||
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
|
||||
mPassphraseCacheTtl
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mPassphraseCacheTtl.setValue(newValue.toString());
|
||||
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
|
||||
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
|
||||
sPreferences.setPassphraseCacheTtl(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@ -282,14 +288,14 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
}
|
||||
mEncryptionAlgorithm.setEntries(entries);
|
||||
mEncryptionAlgorithm.setEntryValues(values);
|
||||
mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm());
|
||||
mEncryptionAlgorithm.setValue("" + sPreferences.getDefaultEncryptionAlgorithm());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mEncryptionAlgorithm
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mEncryptionAlgorithm.setValue(newValue.toString());
|
||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||
mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
|
||||
sPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
|
||||
.toString()));
|
||||
return false;
|
||||
}
|
||||
@ -309,13 +315,13 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
}
|
||||
mHashAlgorithm.setEntries(entries);
|
||||
mHashAlgorithm.setEntryValues(values);
|
||||
mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm());
|
||||
mHashAlgorithm.setValue("" + sPreferences.getDefaultHashAlgorithm());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mHashAlgorithm.setValue(newValue.toString());
|
||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||
mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
||||
sPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@ -326,14 +332,14 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
int[] valueIds, String[] entries, String[] values) {
|
||||
mMessageCompression.setEntries(entries);
|
||||
mMessageCompression.setEntryValues(values);
|
||||
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
|
||||
mMessageCompression.setValue("" + sPreferences.getDefaultMessageCompression());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mMessageCompression
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mMessageCompression.setValue(newValue.toString());
|
||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||
mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
|
||||
sPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
|
||||
.toString()));
|
||||
return false;
|
||||
}
|
||||
@ -344,36 +350,36 @@ public class PreferencesActivity extends PreferenceActivity {
|
||||
(final IntegerListPreference mFileCompression, String[] entries, String[] values) {
|
||||
mFileCompression.setEntries(entries);
|
||||
mFileCompression.setEntryValues(values);
|
||||
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
|
||||
mFileCompression.setValue("" + sPreferences.getDefaultFileCompression());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mFileCompression.setValue(newValue.toString());
|
||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||
mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
||||
sPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeAsciiArmour(final CheckBoxPreference mAsciiArmour) {
|
||||
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
|
||||
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
private static void initializeAsciiArmor(final CheckBoxPreference mAsciiArmor) {
|
||||
mAsciiArmor.setChecked(sPreferences.getDefaultAsciiArmor());
|
||||
mAsciiArmor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mAsciiArmour.setChecked((Boolean) newValue);
|
||||
mPreferences.setDefaultAsciiArmour((Boolean) newValue);
|
||||
mAsciiArmor.setChecked((Boolean) newValue);
|
||||
sPreferences.setDefaultAsciiArmor((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
|
||||
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
|
||||
mForceV3Signatures.setChecked(sPreferences.getForceV3Signatures());
|
||||
mForceV3Signatures
|
||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
mForceV3Signatures.setChecked((Boolean) newValue);
|
||||
mPreferences.setForceV3Signatures((Boolean) newValue);
|
||||
sPreferences.setForceV3Signatures((Boolean) newValue);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -91,10 +91,15 @@ public class PreferencesKeyServerActivity extends ActionBarActivity implements O
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeleted(Editor editor) {
|
||||
public void onDeleted(Editor editor, boolean wasNewItem) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEdited() {
|
||||
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
|
||||
mEditors, false);
|
||||
|
@ -32,7 +32,13 @@ import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.*;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||
@ -248,9 +254,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
long now = new Date().getTime() / 1000;
|
||||
@ -258,24 +262,24 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID,
|
||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
||||
+ Keys.CAN_ENCRYPT + " = '1') AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
|
||||
+ Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
|
||||
+ now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
|
||||
+ Keys.EXPIRY + " >= '" + now + "')) AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||
+" WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||
+ " AND k." + Keys.CAN_ENCRYPT + " = '1'"
|
||||
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||
+ " WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||
+ " AND k." + Keys.CAN_ENCRYPT + " = '1'"
|
||||
+ " AND k." + Keys.CREATION + " <= '" + now + "'"
|
||||
+ " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )"
|
||||
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
String inMasterKeyList = null;
|
||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
||||
inMasterKeyList = KeyRings.MASTER_KEY_ID + " IN (";
|
||||
inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN (";
|
||||
for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) {
|
||||
if (i != 0) {
|
||||
inMasterKeyList += ", ";
|
||||
|
@ -1,42 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
|
||||
public class SelectSecretKeyActivity extends ActionBarActivity {
|
||||
|
||||
// Actions for internal use only:
|
||||
public static final String ACTION_SELECT_SECRET_KEY = Constants.INTENT_PREFIX
|
||||
+ "SELECT_SECRET_KEYRING";
|
||||
|
||||
public static final String EXTRA_FILTER_CERTIFY = "filter_certify";
|
||||
|
||||
public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
|
||||
public static final String RESULT_EXTRA_USER_ID = "user_id";
|
||||
|
||||
private boolean mFilterCertify = false;
|
||||
private boolean mFilterCertify;
|
||||
private SelectSecretKeyFragment mSelectFragment;
|
||||
|
||||
@Override
|
||||
@ -50,23 +45,8 @@ public class SelectSecretKeyActivity extends ActionBarActivity {
|
||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||
actionBar.setHomeButtonEnabled(false);
|
||||
|
||||
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
|
||||
|
||||
// TODO: reimplement!
|
||||
// mFilterLayout = findViewById(R.id.layout_filter);
|
||||
// mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
|
||||
// mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
|
||||
//
|
||||
// mClearFilterButton.setOnClickListener(new OnClickListener() {
|
||||
// public void onClick(View v) {
|
||||
// handleIntent(new Intent());
|
||||
// }
|
||||
// });
|
||||
|
||||
mFilterCertify = getIntent().getBooleanExtra(EXTRA_FILTER_CERTIFY, false);
|
||||
|
||||
handleIntent(getIntent());
|
||||
|
||||
// Check that the activity is using the layout version with
|
||||
// the fragment_container FrameLayout
|
||||
if (findViewById(R.id.select_secret_key_fragment_container) != null) {
|
||||
@ -90,48 +70,14 @@ public class SelectSecretKeyActivity extends ActionBarActivity {
|
||||
/**
|
||||
* This is executed by SelectSecretKeyFragment after clicking on an item
|
||||
*
|
||||
* @param masterKeyId
|
||||
* @param userId
|
||||
* @param selectedUri
|
||||
*/
|
||||
public void afterListSelection(long masterKeyId, String userId) {
|
||||
public void afterListSelection(Uri selectedUri) {
|
||||
Intent data = new Intent();
|
||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, masterKeyId);
|
||||
data.putExtra(RESULT_EXTRA_USER_ID, (String) userId);
|
||||
data.setData(selectedUri);
|
||||
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
handleIntent(intent);
|
||||
}
|
||||
|
||||
private void handleIntent(Intent intent) {
|
||||
// TODO: reimplement!
|
||||
|
||||
// String searchString = null;
|
||||
// if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
// searchString = intent.getStringExtra(SearchManager.QUERY);
|
||||
// if (searchString != null && searchString.trim().length() == 0) {
|
||||
// searchString = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (searchString == null) {
|
||||
// mFilterLayout.setVisibility(View.GONE);
|
||||
// } else {
|
||||
// mFilterLayout.setVisibility(View.VISIBLE);
|
||||
// mFilterInfo.setText(getString(R.string.filterInfo, searchString));
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// TODO: reimplement!
|
||||
// menu.add(0, Id.menu.option.search, 0, R.string.menu_search).setIcon(
|
||||
// android.R.drawable.ic_menu_search);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -28,6 +28,7 @@ import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
@ -55,10 +56,9 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
||||
*/
|
||||
public static SelectSecretKeyFragment newInstance(boolean filterCertify) {
|
||||
SelectSecretKeyFragment frag = new SelectSecretKeyFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putBoolean(ARG_FILTER_CERTIFY, filterCertify);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
@ -85,10 +85,10 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||
long masterKeyId = mAdapter.getMasterKeyId(position);
|
||||
String userId = mAdapter.getUserId(position);
|
||||
Uri result = KeyRings.buildGenericKeyRingUri(String.valueOf(masterKeyId));
|
||||
|
||||
// return data to activity, which results in finishing it
|
||||
mActivity.afterListSelection(masterKeyId, userId);
|
||||
mActivity.afterListSelection(result);
|
||||
}
|
||||
});
|
||||
|
||||
@ -112,12 +112,7 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
Uri baseUri = KeyRings.buildSecretKeyRingsUri();
|
||||
|
||||
String capFilter = null;
|
||||
if (mFilterCertify) {
|
||||
capFilter = "(cert > 0)";
|
||||
}
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
long now = new Date().getTime() / 1000;
|
||||
@ -125,29 +120,36 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
||||
KeyRings._ID,
|
||||
KeyRings.MASTER_KEY_ID,
|
||||
UserIds.USER_ID,
|
||||
"(SELECT COUNT(cert_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS cert_keys WHERE cert_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND cert_keys."
|
||||
+ Keys.CAN_CERTIFY + " = '1') AS cert",
|
||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
||||
+ Keys.CAN_SIGN + " = '1') AS "
|
||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_SIGN
|
||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||
+ " WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
|
||||
+ " AND k." + Keys.CAN_CERTIFY + " = '1'"
|
||||
+ ") AS cert",
|
||||
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||
+" WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||
+ " AND k." + Keys.CAN_SIGN + " = '1'"
|
||||
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||
+ " WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||
+ " AND k." + Keys.CAN_SIGN + " = '1'"
|
||||
+ " AND k." + Keys.CREATION + " <= '" + now + "'"
|
||||
+ " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )"
|
||||
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||
|
||||
String orderBy = UserIds.USER_ID + " ASC";
|
||||
|
||||
String where = Tables.KEY_RINGS_SECRET + "." + KeyRings.MASTER_KEY_ID + " IS NOT NULL";
|
||||
if (mFilterCertify) {
|
||||
where += " AND (cert > 0)";
|
||||
}
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, projection, capFilter, null, orderBy);
|
||||
return new CursorLoader(getActivity(), baseUri, projection, where, null, orderBy);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,22 +19,26 @@ package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
import org.spongycastle.openpgp.PGPSecretKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
|
||||
public class SelectSecretKeyLayoutFragment extends Fragment {
|
||||
public class SelectSecretKeyLayoutFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private TextView mKeyUserId;
|
||||
private TextView mKeyUserIdRest;
|
||||
@ -43,10 +47,23 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
||||
private BootstrapButton mSelectKeyButton;
|
||||
private Boolean mFilterCertify;
|
||||
|
||||
private Uri mReceivedUri = null;
|
||||
|
||||
private SelectSecretKeyCallback mCallback;
|
||||
|
||||
private static final int REQUEST_CODE_SELECT_KEY = 8882;
|
||||
|
||||
private static final int LOADER_ID = 0;
|
||||
|
||||
//The Projection we will retrieve, Master Key ID is for convenience sake,
|
||||
//to avoid having to pass the Key Around
|
||||
final String[] PROJECTION = new String[] {
|
||||
KeychainContract.Keys.MASTER_KEY_ID,
|
||||
KeychainContract.UserIds.USER_ID
|
||||
};
|
||||
final int INDEX_MASTER_KEY_ID = 0;
|
||||
final int INDEX_USER_ID = 1;
|
||||
|
||||
public interface SelectSecretKeyCallback {
|
||||
void onKeySelected(long secretKeyId);
|
||||
}
|
||||
@ -59,68 +76,30 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
||||
mFilterCertify = filterCertify;
|
||||
}
|
||||
|
||||
public void selectKey(long secretKeyId) {
|
||||
if (secretKeyId == Id.key.none) {
|
||||
mNoKeySelected.setVisibility(View.VISIBLE);
|
||||
mKeyUserId.setVisibility(View.GONE);
|
||||
mKeyUserIdRest.setVisibility(View.GONE);
|
||||
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
||||
public void setNoKeySelected() {
|
||||
mNoKeySelected.setVisibility(View.VISIBLE);
|
||||
mKeyUserId.setVisibility(View.GONE);
|
||||
mKeyUserIdRest.setVisibility(View.GONE);
|
||||
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
} else {
|
||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
||||
getActivity(), secretKeyId);
|
||||
if (keyRing != null) {
|
||||
PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
|
||||
String masterkeyIdHex = PgpKeyHelper.convertKeyIdToHex(secretKeyId);
|
||||
public void setSelectedKeyData(String userName, String email, String masterKeyHex) {
|
||||
|
||||
if (key != null) {
|
||||
String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key);
|
||||
mNoKeySelected.setVisibility(View.GONE);
|
||||
|
||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
||||
String userName, userEmail;
|
||||
mKeyUserId.setText(userName);
|
||||
mKeyUserIdRest.setText(email);
|
||||
mKeyMasterKeyIdHex.setText(masterKeyHex);
|
||||
|
||||
if (userIdSplit[0] != null) {
|
||||
userName = userIdSplit[0];
|
||||
} else {
|
||||
userName = getActivity().getResources().getString(R.string.user_id_no_name);
|
||||
}
|
||||
mKeyUserId.setVisibility(View.VISIBLE);
|
||||
mKeyUserIdRest.setVisibility(View.VISIBLE);
|
||||
mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
|
||||
|
||||
if (userIdSplit[1] != null) {
|
||||
userEmail = userIdSplit[1];
|
||||
} else {
|
||||
userEmail = getActivity().getResources().getString(R.string.error_user_id_no_email);
|
||||
}
|
||||
|
||||
mKeyMasterKeyIdHex.setText(masterkeyIdHex);
|
||||
mKeyUserId.setText(userName);
|
||||
mKeyUserIdRest.setText(userEmail);
|
||||
mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
|
||||
mKeyUserId.setVisibility(View.VISIBLE);
|
||||
mKeyUserIdRest.setVisibility(View.VISIBLE);
|
||||
mNoKeySelected.setVisibility(View.GONE);
|
||||
} else {
|
||||
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
||||
mKeyUserId.setVisibility(View.GONE);
|
||||
mKeyUserIdRest.setVisibility(View.GONE);
|
||||
mNoKeySelected.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
mKeyMasterKeyIdHex.setText(
|
||||
getActivity().getResources()
|
||||
.getString(R.string.no_keys_added_or_updated)
|
||||
+ " for master id: " + secretKeyId);
|
||||
mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
|
||||
mKeyUserId.setVisibility(View.GONE);
|
||||
mKeyUserIdRest.setVisibility(View.GONE);
|
||||
mNoKeySelected.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
mKeyUserId.requestFocus();
|
||||
mKeyUserId.setError(error);
|
||||
mNoKeySelected.requestFocus();
|
||||
mNoKeySelected.setError(error);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,29 +126,78 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
//For AppSettingsFragment
|
||||
public void selectKey(long masterKeyId) {
|
||||
Uri buildUri = KeychainContract.KeyRings.buildGenericKeyRingUri(String.valueOf(masterKeyId));
|
||||
mReceivedUri = buildUri;
|
||||
getActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
|
||||
}
|
||||
|
||||
private void startSelectKeyActivity() {
|
||||
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
||||
intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_CERTIFY, mFilterCertify);
|
||||
startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
|
||||
}
|
||||
|
||||
// Select Secret Key Activity delivers the intent which was sent by it using interface to Select
|
||||
// Secret Key Fragment.Intent contains Master Key Id, User Email, User Name, Master Key Id Hex.
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mReceivedUri);
|
||||
//We don't care about the Loader id
|
||||
return new CursorLoader(getActivity(), uri, PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if (data.moveToFirst()) {
|
||||
String userName, email, masterKeyHex;
|
||||
String userID = data.getString(INDEX_USER_ID);
|
||||
long masterKeyID = data.getLong(INDEX_MASTER_KEY_ID);
|
||||
|
||||
String splitUserID[] = PgpKeyHelper.splitUserId(userID);
|
||||
|
||||
if (splitUserID[0] != null) {
|
||||
userName = splitUserID[0];
|
||||
} else {
|
||||
userName = getActivity().getResources().getString(R.string.user_id_no_name);
|
||||
}
|
||||
|
||||
if (splitUserID[1] != null) {
|
||||
email = splitUserID[1];
|
||||
} else {
|
||||
email = getActivity().getResources().getString(R.string.error_user_id_no_email);
|
||||
}
|
||||
|
||||
//TODO Can the cursor return invalid values for the Master Key ?
|
||||
masterKeyHex = PgpKeyHelper.convertKeyIdToHexShort(masterKeyID);
|
||||
|
||||
//Set the data
|
||||
setSelectedKeyData(userName, email, masterKeyHex);
|
||||
|
||||
//Give value to the callback
|
||||
mCallback.onKeySelected(masterKeyID);
|
||||
} else {
|
||||
//Set The empty View
|
||||
setNoKeySelected();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode & 0xFFFF) {
|
||||
switch (requestCode) {
|
||||
case REQUEST_CODE_SELECT_KEY: {
|
||||
long secretKeyId;
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Bundle bundle = data.getExtras();
|
||||
secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
|
||||
selectKey(secretKeyId);
|
||||
mReceivedUri = data.getData();
|
||||
|
||||
//Must be restartLoader() or the data will not be updated on selecting a new key
|
||||
getActivity().getSupportLoaderManager().restartLoader(0, null, this);
|
||||
|
||||
// remove displayed errors
|
||||
mKeyUserId.setError(null);
|
||||
|
||||
// give value back to callback
|
||||
mCallback.onKeySelected(secretKeyId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -98,11 +98,11 @@ public class UploadKeyActivity extends ActionBarActivity {
|
||||
|
||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
||||
|
||||
// Message is received after uploading is done in ApgService
|
||||
// Message is received after uploading is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentServiceHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
@ -37,7 +37,9 @@ import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.Date;
|
||||
@ -50,15 +52,14 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] {
|
||||
KeychainContract.Certs._ID,
|
||||
KeychainContract.Certs.KEY_ID,
|
||||
KeychainContract.UserIds.USER_ID,
|
||||
KeychainContract.Certs.CREATION,
|
||||
KeychainContract.Certs.KEY_ID_CERTIFIER,
|
||||
"signer_uid",
|
||||
KeychainContract.Certs.KEY_DATA
|
||||
Certs.MASTER_KEY_ID,
|
||||
Certs.USER_ID,
|
||||
Certs.CREATION,
|
||||
Certs.KEY_ID_CERTIFIER,
|
||||
Certs.SIGNER_UID,
|
||||
Certs.KEY_DATA
|
||||
};
|
||||
private static final int INDEX_KEY_ID = 1;
|
||||
private static final int INDEX_MASTER_KEY_ID = 1;
|
||||
private static final int INDEX_USER_ID = 2;
|
||||
private static final int INDEX_CREATION = 3;
|
||||
private static final int INDEX_KEY_ID_CERTIFIER = 4;
|
||||
@ -112,7 +113,7 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
if(data.moveToFirst()) {
|
||||
String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_KEY_ID));
|
||||
String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID));
|
||||
mSigneeKey.setText(signeeKey);
|
||||
|
||||
String signeeUid = data.getString(INDEX_USER_ID);
|
||||
@ -179,17 +180,21 @@ public class ViewCertActivity extends ActionBarActivity
|
||||
return true;
|
||||
case R.id.menu_view_cert_view_signer:
|
||||
// can't do this before the data is initialized
|
||||
// TODO notify user of this, maybe offer download?
|
||||
if(mSignerKeyId == 0)
|
||||
return true;
|
||||
Intent viewIntent = null;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
viewIntent = new Intent(this, ViewKeyActivity.class);
|
||||
} else {
|
||||
viewIntent = new Intent(this, ViewKeyActivityJB.class);
|
||||
}
|
||||
viewIntent.setData(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
|
||||
Long.toString(mSignerKeyId))
|
||||
//
|
||||
long signerMasterKeyId = ProviderHelper.getMasterKeyId(this,
|
||||
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mSignerKeyId))
|
||||
);
|
||||
// TODO notify user of this, maybe offer download?
|
||||
if(mSignerKeyId == 0L)
|
||||
return true;
|
||||
viewIntent.setData(KeyRings.buildGenericKeyRingUri(
|
||||
Long.toString(signerMasterKeyId))
|
||||
);
|
||||
startActivity(viewIntent);
|
||||
return true;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@ -28,6 +29,7 @@ import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.Window;
|
||||
import android.widget.Toast;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
@ -38,10 +40,8 @@ import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -60,6 +60,7 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mExportHelper = new ExportHelper(this);
|
||||
@ -83,11 +84,7 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
|
||||
}
|
||||
|
||||
// normalize mDataUri to a "by row id" query, to ensure it works with any
|
||||
// given valid /public/ query
|
||||
long rowId = ProviderHelper.getRowId(this, getIntent().getData());
|
||||
// TODO: handle (rowId == 0) with something else than a crash
|
||||
mDataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(rowId));
|
||||
mDataUri = getIntent().getData();
|
||||
|
||||
Bundle mainBundle = new Bundle();
|
||||
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri);
|
||||
@ -95,7 +92,7 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
ViewKeyMainFragment.class, mainBundle, (selectedTab == 0));
|
||||
|
||||
Bundle certBundle = new Bundle();
|
||||
certBundle.putLong(ViewKeyCertsFragment.ARG_KEYRING_ROW_ID, rowId);
|
||||
certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri);
|
||||
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)),
|
||||
ViewKeyCertsFragment.class, certBundle, (selectedTab == 1));
|
||||
}
|
||||
@ -122,8 +119,12 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
uploadToKeyserver(mDataUri);
|
||||
return true;
|
||||
case R.id.menu_key_view_export_file:
|
||||
long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
|
||||
mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB);
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||
mExportHelper.showExportKeysDialog(
|
||||
new long[] { masterKeyId } , Constants.Path.APP_DIR_FILE_PUB,
|
||||
// TODO this doesn't work?
|
||||
((ViewKeyMainFragment) mTabsAdapter.getItem(0)).isSecretAvailable()
|
||||
);
|
||||
return true;
|
||||
case R.id.menu_key_view_share_default_fingerprint:
|
||||
shareKey(mDataUri, true);
|
||||
@ -158,33 +159,37 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
}
|
||||
|
||||
private void updateFromKeyserver(Uri dataUri) {
|
||||
long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, dataUri);
|
||||
|
||||
if (updateKeyId == 0) {
|
||||
Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
|
||||
return;
|
||||
}
|
||||
byte[] blob = (byte[]) ProviderHelper.getGenericData(
|
||||
this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
|
||||
Intent queryIntent = new Intent(this, ImportKeysActivity.class);
|
||||
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
||||
queryIntent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, updateKeyId);
|
||||
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||
queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
|
||||
|
||||
// TODO: lookup with onactivityresult!
|
||||
startActivityForResult(queryIntent, RESULT_CODE_LOOKUP_KEY);
|
||||
}
|
||||
|
||||
private void shareKey(Uri dataUri, boolean fingerprintOnly) {
|
||||
String content;
|
||||
if (fingerprintOnly) {
|
||||
byte[] fingerprintBlob = ProviderHelper.getFingerprint(this, dataUri);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false);
|
||||
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
byte[] data = (byte[]) ProviderHelper.getGenericData(
|
||||
this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if(data != null) {
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
} else {
|
||||
Toast.makeText(getApplicationContext(), "Bad key selected!",
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// get public keyring as ascii armored string
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this,
|
||||
dataUri, new long[]{masterKeyId});
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||
this, new long[]{ masterKeyId });
|
||||
|
||||
content = keyringArmored.get(0);
|
||||
|
||||
@ -214,8 +219,8 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
private void copyToClipboard(Uri dataUri) {
|
||||
// get public keyring as ascii armored string
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri,
|
||||
new long[]{masterKeyId});
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||
this, new long[]{ masterKeyId });
|
||||
|
||||
ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
|
||||
Toast.makeText(getApplicationContext(), R.string.key_copied_to_clipboard, Toast.LENGTH_LONG)
|
||||
@ -232,25 +237,29 @@ public class ViewKeyActivity extends ActionBarActivity {
|
||||
Handler returnHandler = new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||
Bundle returnData = message.getData();
|
||||
if (returnData != null
|
||||
&& returnData.containsKey(DeleteKeyDialogFragment.MESSAGE_NOT_DELETED)) {
|
||||
// we delete only this key, so MESSAGE_NOT_DELETED will solely contain this key
|
||||
Toast.makeText(ViewKeyActivity.this,
|
||||
getString(R.string.error_can_not_delete_contact)
|
||||
+ getResources()
|
||||
.getQuantityString(R.plurals.error_can_not_delete_info, 1),
|
||||
Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
};
|
||||
|
||||
mExportHelper.deleteKey(dataUri, Id.type.public_key, returnHandler);
|
||||
mExportHelper.deleteKey(dataUri, returnHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_CODE_LOOKUP_KEY: {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
// TODO: reload key??? move this into fragment?
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ import android.widget.Toast;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
|
||||
@ -47,26 +50,18 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
initNfc(mDataUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* NFC: Initialize NFC sharing if OS and device supports it
|
||||
*/
|
||||
private void initNfc(Uri dataUri) {
|
||||
private void initNfc() {
|
||||
// check if NFC Beam is supported (>= Android 4.1)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
// Check for available NFC Adapter
|
||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||
if (mNfcAdapter != null) {
|
||||
// init nfc
|
||||
|
||||
// get public keyring as byte array
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||
mSharedKeyringBytes = ProviderHelper.getKeyRingsAsByteArray(this, dataUri,
|
||||
new long[]{masterKeyId});
|
||||
|
||||
// Register callback to set NDEF message
|
||||
mNfcAdapter.setNdefPushMessageCallback(this, this);
|
||||
// Register callback to listen for message-sent success
|
||||
@ -86,9 +81,19 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess
|
||||
* guarantee that this activity starts when receiving a beamed message. For now, this code
|
||||
* uses the tag dispatch system.
|
||||
*/
|
||||
NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
|
||||
mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
|
||||
return msg;
|
||||
// get public keyring as byte array
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||
try {
|
||||
mSharedKeyringBytes = ProviderHelper.getPGPPublicKeyRing(this, masterKeyId).getEncoded();
|
||||
|
||||
NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
|
||||
mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
|
||||
return msg;
|
||||
} catch(IOException e) {
|
||||
// not much trouble, but leave a note
|
||||
Log.e(Constants.TAG, "Error parsing keyring: ", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,20 +53,21 @@ public class ViewKeyCertsFragment extends Fragment
|
||||
// These are the rows that we will retrieve.
|
||||
static final String[] PROJECTION = new String[] {
|
||||
KeychainContract.Certs._ID,
|
||||
KeychainContract.Certs.MASTER_KEY_ID,
|
||||
KeychainContract.Certs.VERIFIED,
|
||||
KeychainContract.Certs.RANK,
|
||||
KeychainContract.Certs.KEY_ID_CERTIFIER,
|
||||
KeychainContract.UserIds.USER_ID,
|
||||
"signer_uid"
|
||||
KeychainContract.Certs.USER_ID,
|
||||
KeychainContract.Certs.SIGNER_UID
|
||||
};
|
||||
|
||||
// sort by our user id,
|
||||
static final String SORT_ORDER =
|
||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " ASC, "
|
||||
+ KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.VERIFIED + " DESC, "
|
||||
+ "signer_uid ASC";
|
||||
+ KeychainContract.Certs.SIGNER_UID + " ASC";
|
||||
|
||||
public static final String ARG_KEYRING_ROW_ID = "row_id";
|
||||
public static final String ARG_DATA_URI = "data_uri";
|
||||
|
||||
private StickyListHeadersListView mStickyList;
|
||||
private Spinner mSpinner;
|
||||
@ -125,14 +126,14 @@ public class ViewKeyCertsFragment extends Fragment
|
||||
|
||||
mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
|
||||
|
||||
if (!getArguments().containsKey(ARG_KEYRING_ROW_ID)) {
|
||||
if (!getArguments().containsKey(ARG_DATA_URI)) {
|
||||
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||
getActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
long rowId = getArguments().getLong(ARG_KEYRING_ROW_ID);
|
||||
mBaseUri = KeychainContract.Certs.buildCertsByKeyRowIdUri(Long.toString(rowId));
|
||||
Uri uri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
mBaseUri = KeychainContract.Certs.buildCertsUri(uri);
|
||||
|
||||
mStickyList.setAreHeadersSticky(true);
|
||||
mStickyList.setDrawingListUnderStickyHeader(false);
|
||||
@ -229,12 +230,12 @@ public class ViewKeyCertsFragment extends Fragment
|
||||
private void initIndex(Cursor cursor) {
|
||||
if (cursor != null) {
|
||||
|
||||
mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs._ID);
|
||||
mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID);
|
||||
mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID);
|
||||
mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.RANK);
|
||||
mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED);
|
||||
mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER);
|
||||
mIndexSignerUserId = cursor.getColumnIndexOrThrow("signer_uid");
|
||||
mIndexSignerUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,15 +30,19 @@ import android.text.format.DateFormat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
|
||||
@ -52,6 +56,7 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
|
||||
public static final String ARG_DATA_URI = "uri";
|
||||
|
||||
private LinearLayout mContainer;
|
||||
private TextView mName;
|
||||
private TextView mEmail;
|
||||
private TextView mComment;
|
||||
@ -68,7 +73,7 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
private ListView mUserIds;
|
||||
private ListView mKeys;
|
||||
|
||||
private static final int LOADER_ID_KEYRING = 0;
|
||||
private static final int LOADER_ID_UNIFIED = 0;
|
||||
private static final int LOADER_ID_USER_IDS = 1;
|
||||
private static final int LOADER_ID_KEYS = 2;
|
||||
|
||||
@ -77,10 +82,14 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
|
||||
private Uri mDataUri;
|
||||
|
||||
// for activity
|
||||
private boolean mSecretAvailable = false;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.view_key_main_fragment, container, false);
|
||||
|
||||
mContainer = (LinearLayout) view.findViewById(R.id.container);
|
||||
mName = (TextView) view.findViewById(R.id.name);
|
||||
mEmail = (TextView) view.findViewById(R.id.email);
|
||||
mComment = (TextView) view.findViewById(R.id.comment);
|
||||
@ -119,64 +128,24 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
return;
|
||||
}
|
||||
|
||||
getActivity().setProgressBarIndeterminateVisibility(Boolean.TRUE);
|
||||
mContainer.setVisibility(View.GONE);
|
||||
|
||||
mDataUri = dataUri;
|
||||
|
||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
||||
|
||||
{ // label whether secret key is available, and edit button if it is
|
||||
final long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), mDataUri);
|
||||
if (ProviderHelper.hasSecretKeyByMasterKeyId(getActivity(), masterKeyId)) {
|
||||
// set this attribute. this is a LITTLE unclean, but we have the info available
|
||||
// right here, so why not.
|
||||
mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
|
||||
mSecretKey.setText(R.string.secret_key_yes);
|
||||
|
||||
// certify button
|
||||
// TODO this button MIGHT be useful if the user wants to
|
||||
// certify a private key with another...
|
||||
// mActionCertify.setVisibility(View.GONE);
|
||||
|
||||
// edit button
|
||||
mActionEdit.setVisibility(View.VISIBLE);
|
||||
mActionEdit.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
editIntent.setData(
|
||||
KeychainContract
|
||||
.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(
|
||||
Long.toString(masterKeyId)));
|
||||
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
startActivityForResult(editIntent, 0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mSecretKey.setTextColor(Color.BLACK);
|
||||
mSecretKey.setText(getResources().getString(R.string.secret_key_no));
|
||||
|
||||
// certify button
|
||||
mActionCertify.setVisibility(View.VISIBLE);
|
||||
// edit button
|
||||
mActionEdit.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// TODO see todo note above, doing this here for now
|
||||
mActionCertify.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
certifyKey(KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
|
||||
Long.toString(masterKeyId)
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
mActionEncrypt.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
encryptToContact(mDataUri);
|
||||
}
|
||||
});
|
||||
mActionCertify.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
certifyKey(mDataUri);
|
||||
}
|
||||
});
|
||||
|
||||
mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
|
||||
mUserIds.setAdapter(mUserIdsAdapter);
|
||||
@ -186,74 +155,51 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
|
||||
// Prepare the loaders. Either re-connect with an existing ones,
|
||||
// or start new ones.
|
||||
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
|
||||
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
|
||||
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
|
||||
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
|
||||
}
|
||||
|
||||
static final String[] KEYRING_PROJECTION =
|
||||
new String[]{KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID,
|
||||
KeychainContract.UserIds.USER_ID};
|
||||
static final int KEYRING_INDEX_ID = 0;
|
||||
static final int KEYRING_INDEX_MASTER_KEY_ID = 1;
|
||||
static final int KEYRING_INDEX_USER_ID = 2;
|
||||
static final String[] UNIFIED_PROJECTION = new String[] {
|
||||
KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET,
|
||||
KeyRings.USER_ID, KeyRings.FINGERPRINT,
|
||||
KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY,
|
||||
|
||||
static final String[] USER_IDS_PROJECTION = new String[]{
|
||||
KeychainContract.UserIds._ID,
|
||||
KeychainContract.UserIds.USER_ID,
|
||||
KeychainContract.UserIds.RANK,
|
||||
"verified",
|
||||
};
|
||||
// not the main user id
|
||||
static final String USER_IDS_SELECTION =
|
||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " > 0 ";
|
||||
static final String USER_IDS_SORT_ORDER =
|
||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC";
|
||||
static final int INDEX_UNIFIED_MKI = 1;
|
||||
static final int INDEX_UNIFIED_HAS_SECRET = 2;
|
||||
static final int INDEX_UNIFIED_UID = 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_EXPIRY = 8;
|
||||
|
||||
static final String[] KEYS_PROJECTION = new String[]{
|
||||
KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID,
|
||||
KeychainContract.Keys.IS_MASTER_KEY, KeychainContract.Keys.ALGORITHM,
|
||||
KeychainContract.Keys.KEY_SIZE, KeychainContract.Keys.CAN_CERTIFY,
|
||||
KeychainContract.Keys.CAN_SIGN, KeychainContract.Keys.CAN_ENCRYPT,
|
||||
KeychainContract.Keys.CREATION, KeychainContract.Keys.EXPIRY,
|
||||
KeychainContract.Keys.FINGERPRINT
|
||||
};
|
||||
static final String KEYS_SORT_ORDER = KeychainContract.Keys.RANK + " ASC";
|
||||
static final int KEYS_INDEX_ID = 0;
|
||||
static final int KEYS_INDEX_KEY_ID = 1;
|
||||
static final int KEYS_INDEX_IS_MASTER_KEY = 2;
|
||||
static final int KEYS_INDEX_ALGORITHM = 3;
|
||||
static final int KEYS_INDEX_KEY_SIZE = 4;
|
||||
static final int KEYS_INDEX_CAN_CERTIFY = 5;
|
||||
static final int KEYS_INDEX_CAN_SIGN = 6;
|
||||
static final int KEYS_INDEX_CAN_ENCRYPT = 7;
|
||||
static final int KEYS_INDEX_CREATION = 8;
|
||||
static final int KEYS_INDEX_EXPIRY = 9;
|
||||
static final int KEYS_INDEX_FINGERPRINT = 10;
|
||||
static final String[] USER_IDS_PROJECTION = new String[] {
|
||||
UserIds._ID, UserIds.USER_ID, UserIds.RANK,
|
||||
};
|
||||
|
||||
static final String[] KEYS_PROJECTION = new String[] {
|
||||
Keys._ID,
|
||||
Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE,
|
||||
Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
|
||||
Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
|
||||
};
|
||||
static final int KEYS_INDEX_CAN_ENCRYPT = 6;
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
switch (id) {
|
||||
case LOADER_ID_KEYRING: {
|
||||
Uri baseUri = mDataUri;
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, KEYRING_PROJECTION, null, null, null);
|
||||
case LOADER_ID_UNIFIED: {
|
||||
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||
}
|
||||
case LOADER_ID_USER_IDS: {
|
||||
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null,
|
||||
USER_IDS_SORT_ORDER);
|
||||
Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null, null);
|
||||
}
|
||||
case LOADER_ID_KEYS: {
|
||||
Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri);
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, KEYS_SORT_ORDER);
|
||||
Uri baseUri = Keys.buildKeysUri(mDataUri);
|
||||
return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
|
||||
}
|
||||
|
||||
default:
|
||||
@ -262,14 +208,19 @@ public class ViewKeyMainFragment extends Fragment 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)
|
||||
return;
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_KEYRING:
|
||||
case LOADER_ID_UNIFIED: {
|
||||
if (data.moveToFirst()) {
|
||||
// get name, email, and comment from USER_ID
|
||||
String[] mainUserId = PgpKeyHelper.splitUserId(data
|
||||
.getString(KEYRING_INDEX_USER_ID));
|
||||
String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID));
|
||||
if (mainUserId[0] != null) {
|
||||
getActivity().setTitle(mainUserId[0]);
|
||||
mName.setText(mainUserId[0]);
|
||||
@ -279,63 +230,97 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
}
|
||||
mEmail.setText(mainUserId[1]);
|
||||
mComment.setText(mainUserId[2]);
|
||||
}
|
||||
|
||||
break;
|
||||
case LOADER_ID_USER_IDS:
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
case LOADER_ID_KEYS:
|
||||
// the first key here is our master key
|
||||
if (data.moveToFirst()) {
|
||||
if (data.getInt(INDEX_UNIFIED_HAS_SECRET) != 0) {
|
||||
mSecretAvailable = true;
|
||||
|
||||
mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
|
||||
mSecretKey.setText(R.string.secret_key_yes);
|
||||
|
||||
// edit button
|
||||
mActionEdit.setVisibility(View.VISIBLE);
|
||||
mActionEdit.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View view) {
|
||||
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
||||
editIntent.setData(mDataUri);
|
||||
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||
startActivityForResult(editIntent, 0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
mSecretAvailable = false;
|
||||
|
||||
mSecretKey.setTextColor(Color.BLACK);
|
||||
mSecretKey.setText(getResources().getString(R.string.secret_key_no));
|
||||
|
||||
// certify button
|
||||
mActionCertify.setVisibility(View.VISIBLE);
|
||||
// edit button
|
||||
mActionEdit.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// get key id from MASTER_KEY_ID
|
||||
long keyId = data.getLong(KEYS_INDEX_KEY_ID);
|
||||
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
long masterKeyId = data.getLong(INDEX_UNIFIED_MKI);
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
|
||||
mKeyId.setText(keyIdStr);
|
||||
|
||||
// get creation date from CREATION
|
||||
if (data.isNull(KEYS_INDEX_CREATION)) {
|
||||
if (data.isNull(INDEX_UNIFIED_CREATION)) {
|
||||
mCreation.setText(R.string.none);
|
||||
} else {
|
||||
Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000);
|
||||
Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000);
|
||||
|
||||
mCreation.setText(
|
||||
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
||||
creationDate));
|
||||
creationDate));
|
||||
}
|
||||
|
||||
// get expiry date from EXPIRY
|
||||
if (data.isNull(KEYS_INDEX_EXPIRY)) {
|
||||
if (data.isNull(INDEX_UNIFIED_EXPIRY)) {
|
||||
mExpiry.setText(R.string.none);
|
||||
} else {
|
||||
Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000);
|
||||
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||
|
||||
mExpiry.setText(
|
||||
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
||||
expiryDate));
|
||||
expiryDate));
|
||||
}
|
||||
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
||||
data.getInt(KEYS_INDEX_ALGORITHM), data.getInt(KEYS_INDEX_KEY_SIZE));
|
||||
data.getInt(INDEX_UNIFIED_ALGORITHM), data.getInt(INDEX_UNIFIED_KEY_SIZE));
|
||||
mAlgorithm.setText(algorithmStr);
|
||||
|
||||
byte[] fingerprintBlob = data.getBlob(KEYS_INDEX_FINGERPRINT);
|
||||
if (fingerprintBlob == null) {
|
||||
// FALLBACK for old database entries
|
||||
fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri);
|
||||
}
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
||||
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
|
||||
mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
|
||||
|
||||
mFingerprint.setText(OtherHelper.colorizeFingerprint(fingerprint));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case LOADER_ID_USER_IDS:
|
||||
mUserIdsAdapter.swapCursor(data);
|
||||
break;
|
||||
|
||||
case LOADER_ID_KEYS:
|
||||
// hide encrypt button if no encryption key is available
|
||||
boolean canEncrypt = false;
|
||||
data.moveToFirst();
|
||||
do {
|
||||
if (data.getInt(KEYS_INDEX_CAN_ENCRYPT) == 1) {
|
||||
canEncrypt = true;
|
||||
break;
|
||||
}
|
||||
} while (data.moveToNext());
|
||||
if (!canEncrypt) {
|
||||
mActionEncrypt.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mKeysAdapter.swapCursor(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
getActivity().setProgressBarIndeterminateVisibility(Boolean.FALSE);
|
||||
mContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -344,24 +329,25 @@ public class ViewKeyMainFragment extends Fragment implements
|
||||
*/
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
switch (loader.getId()) {
|
||||
case LOADER_ID_KEYRING:
|
||||
// No resources need to be freed for this ID
|
||||
break;
|
||||
case LOADER_ID_USER_IDS:
|
||||
mUserIdsAdapter.swapCursor(null);
|
||||
break;
|
||||
case LOADER_ID_KEYS:
|
||||
mKeysAdapter.swapCursor(null);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the key current displayed is known to have a secret key. */
|
||||
public boolean isSecretAvailable() {
|
||||
return mSecretAvailable;
|
||||
}
|
||||
|
||||
private void encryptToContact(Uri dataUri) {
|
||||
// TODO preselect from uri? should be feasible without trivial query
|
||||
long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
||||
|
||||
long[] encryptionKeyIds = new long[]{keyId};
|
||||
long[] encryptionKeyIds = new long[]{ keyId };
|
||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
|
||||
|
@ -30,6 +30,7 @@ import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.LinearLayout.LayoutParams;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
|
||||
@ -43,13 +44,12 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
protected List<ImportKeysListEntry> mData;
|
||||
|
||||
static class ViewHolder {
|
||||
private TextView mMainUserId;
|
||||
private TextView mMainUserIdRest;
|
||||
private TextView mKeyId;
|
||||
private TextView mFingerprint;
|
||||
private TextView mAlgorithm;
|
||||
private TextView mStatus;
|
||||
|
||||
public TextView mainUserId;
|
||||
public TextView mainUserIdRest;
|
||||
public TextView keyId;
|
||||
public TextView fingerprint;
|
||||
public TextView algorithm;
|
||||
public TextView status;
|
||||
}
|
||||
|
||||
public ImportKeysAdapter(Activity activity) {
|
||||
@ -100,12 +100,12 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
if (convertView == null) {
|
||||
holder = new ViewHolder();
|
||||
convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
|
||||
holder.mMainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
|
||||
holder.mMainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
|
||||
holder.mKeyId = (TextView) convertView.findViewById(R.id.keyId);
|
||||
holder.mFingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
|
||||
holder.mAlgorithm = (TextView) convertView.findViewById(R.id.algorithm);
|
||||
holder.mStatus = (TextView) convertView.findViewById(R.id.status);
|
||||
holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
|
||||
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
|
||||
holder.keyId = (TextView) convertView.findViewById(R.id.keyId);
|
||||
holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
|
||||
holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
|
||||
holder.status = (TextView) convertView.findViewById(R.id.status);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
@ -119,36 +119,36 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
||||
// show red user id if it is a secret key
|
||||
if (entry.secretKey) {
|
||||
userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0];
|
||||
holder.mMainUserId.setTextColor(Color.RED);
|
||||
holder.mainUserId.setTextColor(Color.RED);
|
||||
}
|
||||
holder.mMainUserId.setText(userIdSplit[0]);
|
||||
holder.mainUserId.setText(userIdSplit[0]);
|
||||
} else {
|
||||
holder.mMainUserId.setText(R.string.user_id_no_name);
|
||||
holder.mainUserId.setText(R.string.user_id_no_name);
|
||||
}
|
||||
|
||||
// email
|
||||
if (userIdSplit[1] != null) {
|
||||
holder.mMainUserIdRest.setText(userIdSplit[1]);
|
||||
holder.mMainUserIdRest.setVisibility(View.VISIBLE);
|
||||
holder.mainUserIdRest.setText(userIdSplit[1]);
|
||||
holder.mainUserIdRest.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.mMainUserIdRest.setVisibility(View.GONE);
|
||||
holder.mainUserIdRest.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.mKeyId.setText(entry.hexKeyId);
|
||||
holder.keyId.setText(entry.keyIdHex);
|
||||
|
||||
if (entry.fingerPrint != null) {
|
||||
holder.mFingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint);
|
||||
holder.mFingerprint.setVisibility(View.VISIBLE);
|
||||
if (entry.fingerPrintHex != null) {
|
||||
holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex));
|
||||
holder.fingerprint.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.mFingerprint.setVisibility(View.GONE);
|
||||
holder.fingerprint.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
holder.mAlgorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
|
||||
holder.algorithm.setText("" + entry.bitStrength + "/" + entry.algorithm);
|
||||
|
||||
if (entry.revoked) {
|
||||
holder.mStatus.setText(R.string.revoked);
|
||||
holder.status.setText(R.string.revoked);
|
||||
} else {
|
||||
holder.mStatus.setVisibility(View.GONE);
|
||||
holder.status.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
LinearLayout ll = (LinearLayout) convertView.findViewById(R.id.list);
|
||||
|
@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.spongycastle.openpgp.PGPKeyRing;
|
||||
import org.spongycastle.openpgp.PGPPublicKey;
|
||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||
@ -34,13 +36,13 @@ import java.util.Date;
|
||||
|
||||
public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
private static final long serialVersionUID = -7797972103284992662L;
|
||||
public ArrayList<String> userIds;
|
||||
|
||||
public ArrayList<String> userIds;
|
||||
public long keyId;
|
||||
public String keyIdHex;
|
||||
public boolean revoked;
|
||||
public Date date; // TODO: not displayed
|
||||
public String fingerPrint;
|
||||
public String hexKeyId;
|
||||
public String fingerPrintHex;
|
||||
public int bitStrength;
|
||||
public String algorithm;
|
||||
public boolean secretKey;
|
||||
@ -54,8 +56,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
this.keyId = b.keyId;
|
||||
this.revoked = b.revoked;
|
||||
this.date = b.date;
|
||||
this.fingerPrint = b.fingerPrint;
|
||||
this.hexKeyId = b.hexKeyId;
|
||||
this.fingerPrintHex = b.fingerPrintHex;
|
||||
this.keyIdHex = b.keyIdHex;
|
||||
this.bitStrength = b.bitStrength;
|
||||
this.algorithm = b.algorithm;
|
||||
this.secretKey = b.secretKey;
|
||||
@ -73,8 +75,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
dest.writeLong(keyId);
|
||||
dest.writeByte((byte) (revoked ? 1 : 0));
|
||||
dest.writeSerializable(date);
|
||||
dest.writeString(fingerPrint);
|
||||
dest.writeString(hexKeyId);
|
||||
dest.writeString(fingerPrintHex);
|
||||
dest.writeString(keyIdHex);
|
||||
dest.writeInt(bitStrength);
|
||||
dest.writeString(algorithm);
|
||||
dest.writeByte((byte) (secretKey ? 1 : 0));
|
||||
@ -91,8 +93,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
vr.keyId = source.readLong();
|
||||
vr.revoked = source.readByte() == 1;
|
||||
vr.date = (Date) source.readSerializable();
|
||||
vr.fingerPrint = source.readString();
|
||||
vr.hexKeyId = source.readString();
|
||||
vr.fingerPrintHex = source.readString();
|
||||
vr.keyIdHex = source.readString();
|
||||
vr.bitStrength = source.readInt();
|
||||
vr.algorithm = source.readString();
|
||||
vr.secretKey = source.readByte() == 1;
|
||||
@ -108,8 +110,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
}
|
||||
};
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
public String getKeyIdHex() {
|
||||
return keyIdHex;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
@ -120,6 +122,82 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
this.mBytes = bytes;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return mSelected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.mSelected = selected;
|
||||
}
|
||||
|
||||
public long getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(long keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public void setKeyIdHex(String keyIdHex) {
|
||||
this.keyIdHex = keyIdHex;
|
||||
}
|
||||
|
||||
public boolean isRevoked() {
|
||||
return revoked;
|
||||
}
|
||||
|
||||
public void setRevoked(boolean revoked) {
|
||||
this.revoked = revoked;
|
||||
}
|
||||
|
||||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(Date date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
public String getFingerPrintHex() {
|
||||
return fingerPrintHex;
|
||||
}
|
||||
|
||||
public void setFingerPrintHex(String fingerPrintHex) {
|
||||
this.fingerPrintHex = fingerPrintHex;
|
||||
}
|
||||
|
||||
public int getBitStrength() {
|
||||
return bitStrength;
|
||||
}
|
||||
|
||||
public void setBitStrength(int bitStrength) {
|
||||
this.bitStrength = bitStrength;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.algorithm = algorithm;
|
||||
}
|
||||
|
||||
public boolean isSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public void setSecretKey(boolean secretKey) {
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
public ArrayList<String> getUserIds() {
|
||||
return userIds;
|
||||
}
|
||||
|
||||
public void setUserIds(ArrayList<String> userIds) {
|
||||
this.userIds = userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for later querying from keyserver
|
||||
*/
|
||||
@ -131,14 +209,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
userIds = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return mSelected;
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
this.mSelected = selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor based on key object, used for import from NFC, QR Codes, files
|
||||
*/
|
||||
@ -164,27 +234,41 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||
for (String userId : new IterableIterator<String>(pgpKeyRing.getPublicKey().getUserIDs())) {
|
||||
userIds.add(userId);
|
||||
}
|
||||
|
||||
this.keyId = pgpKeyRing.getPublicKey().getKeyID();
|
||||
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
|
||||
this.revoked = pgpKeyRing.getPublicKey().isRevoked();
|
||||
this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
|
||||
.getFingerprint(), true);
|
||||
this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
|
||||
.getFingerprint());
|
||||
this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
|
||||
int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
|
||||
if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL
|
||||
|| algorithm == PGPPublicKey.RSA_SIGN) {
|
||||
this.algorithm = "RSA";
|
||||
} else if (algorithm == PGPPublicKey.DSA) {
|
||||
this.algorithm = "DSA";
|
||||
} else if (algorithm == PGPPublicKey.ELGAMAL_ENCRYPT
|
||||
|| algorithm == PGPPublicKey.ELGAMAL_GENERAL) {
|
||||
this.algorithm = "ElGamal";
|
||||
} else if (algorithm == PGPPublicKey.EC || algorithm == PGPPublicKey.ECDSA) {
|
||||
this.algorithm = "ECC";
|
||||
} else {
|
||||
// TODO: with resources
|
||||
this.algorithm = "unknown";
|
||||
}
|
||||
final int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
|
||||
this.algorithm = getAlgorithmFromId(algorithm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
||||
*/
|
||||
private static final SparseArray<String> ALGORITHM_IDS = new SparseArray<String>() {{
|
||||
put(-1, "unknown"); // TODO: with resources
|
||||
put(0, "unencrypted");
|
||||
put(PGPPublicKey.RSA_GENERAL, "RSA");
|
||||
put(PGPPublicKey.RSA_ENCRYPT, "RSA");
|
||||
put(PGPPublicKey.RSA_SIGN, "RSA");
|
||||
put(PGPPublicKey.ELGAMAL_ENCRYPT, "ElGamal");
|
||||
put(PGPPublicKey.ELGAMAL_GENERAL, "ElGamal");
|
||||
put(PGPPublicKey.DSA, "DSA");
|
||||
put(PGPPublicKey.EC, "ECC");
|
||||
put(PGPPublicKey.ECDSA, "ECC");
|
||||
put(PGPPublicKey.ECDH, "ECC");
|
||||
}};
|
||||
|
||||
/**
|
||||
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
||||
*/
|
||||
public static String getAlgorithmFromId(int algorithmId) {
|
||||
return (ALGORITHM_IDS.get(algorithmId) != null ?
|
||||
ALGORITHM_IDS.get(algorithmId) :
|
||||
ALGORITHM_IDS.get(-1));
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||
import org.sufficientlysecure.keychain.util.KeyServer;
|
||||
@ -53,7 +54,12 @@ public class ImportKeysListServerLoader
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
|
||||
queryServer(mServerQuery, mKeyServer);
|
||||
if (mServerQuery.startsWith("0x") && mServerQuery.length() == 42) {
|
||||
Log.d(Constants.TAG, "This search is based on a unique fingerprint. Enforce a fingerprint check!");
|
||||
queryServer(mServerQuery, mKeyServer, true);
|
||||
} else {
|
||||
queryServer(mServerQuery, mKeyServer, false);
|
||||
}
|
||||
|
||||
return mEntryListWrapper;
|
||||
}
|
||||
@ -84,14 +90,30 @@ public class ImportKeysListServerLoader
|
||||
/**
|
||||
* Query keyserver
|
||||
*/
|
||||
private void queryServer(String query, String keyServer) {
|
||||
private void queryServer(String query, String keyServer, boolean enforceFingerprint) {
|
||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||
try {
|
||||
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
||||
|
||||
mEntryList.clear();
|
||||
// add result to data
|
||||
mEntryList.addAll(searchResult);
|
||||
if (enforceFingerprint) {
|
||||
String fingerprint = query.substring(2);
|
||||
Log.d(Constants.TAG, "fingerprint: " + fingerprint);
|
||||
// query must return only one result!
|
||||
if (searchResult.size() > 0) {
|
||||
ImportKeysListEntry uniqueEntry = searchResult.get(0);
|
||||
/*
|
||||
* set fingerprint explicitly after query
|
||||
* to enforce a check when the key is imported by KeychainIntentService
|
||||
*/
|
||||
uniqueEntry.setFingerPrintHex(fingerprint);
|
||||
uniqueEntry.setSelected(true);
|
||||
mEntryList.add(uniqueEntry);
|
||||
}
|
||||
} else {
|
||||
mEntryList.addAll(searchResult);
|
||||
}
|
||||
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
||||
} catch (KeyServer.InsufficientQuery e) {
|
||||
Log.e(Constants.TAG, "InsufficientQuery", e);
|
||||
|
@ -20,7 +20,11 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
||||
import android.content.Context;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class KeyValueSpinnerAdapter extends ArrayAdapter<String> {
|
||||
private final HashMap<Integer, String> mData;
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v7.app.ActionBarActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PagerTabStripAdapter extends FragmentPagerAdapter {
|
||||
private final Context mContext;
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
|
||||
|
||||
static final class TabInfo {
|
||||
public final Class<?> clss;
|
||||
public final Bundle args;
|
||||
public final String title;
|
||||
|
||||
TabInfo(Class<?> clss, Bundle args, String title) {
|
||||
this.clss = clss;
|
||||
this.args = args;
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
|
||||
public PagerTabStripAdapter(ActionBarActivity activity) {
|
||||
super(activity.getSupportFragmentManager());
|
||||
mContext = activity;
|
||||
}
|
||||
|
||||
public void addTab(Class<?> clss, Bundle args, String title) {
|
||||
TabInfo info = new TabInfo(clss, args, title);
|
||||
mTabs.add(info);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mTabs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
TabInfo info = mTabs.get(position);
|
||||
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return mTabs.get(position).title;
|
||||
}
|
||||
}
|
@ -115,7 +115,7 @@ public class SelectKeyCursorAdapter extends HighlightQueryCursorAdapter {
|
||||
// TODO: needed to key id to no?
|
||||
keyId.setText(R.string.no_key);
|
||||
long masterKeyId = cursor.getLong(mIndexMasterKeyId);
|
||||
keyId.setText(PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
||||
keyId.setText(PgpKeyHelper.convertKeyIdToHexShort(masterKeyId));
|
||||
|
||||
// TODO: needed to set unknown_status?
|
||||
status.setText(R.string.unknown_status);
|
||||
|
@ -36,12 +36,12 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.
|
||||
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
|
||||
|
||||
static final class TabInfo {
|
||||
private final Class<?> mClss;
|
||||
private final Bundle mArgs;
|
||||
public final Class<?> clss;
|
||||
public final Bundle args;
|
||||
|
||||
TabInfo(Class<?> mClss, Bundle mArgs) {
|
||||
this.mClss = mClss;
|
||||
this.mArgs = mArgs;
|
||||
TabInfo(Class<?> clss, Bundle args) {
|
||||
this.clss = clss;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
TabInfo info = mTabs.get(position);
|
||||
return Fragment.instantiate(mContext, info.mClss.getName(), info.mArgs);
|
||||
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
|
||||
}
|
||||
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
|
@ -18,27 +18,36 @@
|
||||
package org.sufficientlysecure.keychain.ui.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class ViewKeyKeysAdapter extends CursorAdapter {
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private int mIndexKeyId;
|
||||
private int mIndexAlgorithm;
|
||||
private int mIndexKeySize;
|
||||
private int mIndexIsMasterKey;
|
||||
private int mIndexRank;
|
||||
private int mIndexCanCertify;
|
||||
private int mIndexCanEncrypt;
|
||||
private int mIndexCanSign;
|
||||
private int mIndexRevokedKey;
|
||||
private int mIndexExpiry;
|
||||
|
||||
private ColorStateList mDefaultTextColor;
|
||||
|
||||
public ViewKeyKeysAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
@ -66,10 +75,12 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
|
||||
mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID);
|
||||
mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM);
|
||||
mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE);
|
||||
mIndexIsMasterKey = cursor.getColumnIndexOrThrow(Keys.IS_MASTER_KEY);
|
||||
mIndexRank = cursor.getColumnIndexOrThrow(Keys.RANK);
|
||||
mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
|
||||
mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
|
||||
mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN);
|
||||
mIndexRevokedKey = cursor.getColumnIndexOrThrow(Keys.IS_REVOKED);
|
||||
mIndexExpiry = cursor.getColumnIndexOrThrow(Keys.EXPIRY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,20 +88,21 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
||||
TextView keyExpiry = (TextView) view.findViewById(R.id.keyExpiry);
|
||||
ImageView masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||
ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
||||
ImageView revokedKeyIcon = (ImageView) view.findViewById(R.id.ic_revokedKey);
|
||||
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexKeyId));
|
||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(cursor.getLong(mIndexKeyId));
|
||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm),
|
||||
cursor.getInt(mIndexKeySize));
|
||||
|
||||
keyId.setText(keyIdStr);
|
||||
|
||||
keyDetails.setText("(" + algorithmStr + ")");
|
||||
|
||||
if (cursor.getInt(mIndexIsMasterKey) != 1) {
|
||||
if (cursor.getInt(mIndexRank) == 0) {
|
||||
masterKeyIcon.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
masterKeyIcon.setVisibility(View.VISIBLE);
|
||||
@ -113,11 +125,51 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
|
||||
} else {
|
||||
signIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
boolean valid = true;
|
||||
if (cursor.getInt(mIndexRevokedKey) > 0) {
|
||||
revokedKeyIcon.setVisibility(View.VISIBLE);
|
||||
|
||||
valid = false;
|
||||
} else {
|
||||
keyId.setTextColor(mDefaultTextColor);
|
||||
keyDetails.setTextColor(mDefaultTextColor);
|
||||
keyExpiry.setTextColor(mDefaultTextColor);
|
||||
|
||||
revokedKeyIcon.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (!cursor.isNull(mIndexExpiry)) {
|
||||
Date expiryDate = new Date(cursor.getLong(mIndexExpiry) * 1000);
|
||||
|
||||
valid = valid && expiryDate.after(new Date());
|
||||
keyExpiry.setText("(" +
|
||||
context.getString(R.string.label_expiry) + ": " +
|
||||
DateFormat.getDateFormat(context).format(expiryDate) + ")");
|
||||
|
||||
keyExpiry.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
keyExpiry.setVisibility(View.GONE);
|
||||
}
|
||||
// if key is expired or revoked, strike through text
|
||||
if (!valid) {
|
||||
keyId.setText(OtherHelper.strikeOutText(keyId.getText()));
|
||||
keyDetails.setText(OtherHelper.strikeOutText(keyDetails.getText()));
|
||||
keyExpiry.setText(OtherHelper.strikeOutText(keyExpiry.getText()));
|
||||
}
|
||||
keyId.setEnabled(valid);
|
||||
keyDetails.setEnabled(valid);
|
||||
keyExpiry.setEnabled(valid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.view_key_keys_item, null);
|
||||
View view = mInflater.inflate(R.layout.view_key_keys_item, null);
|
||||
if (mDefaultTextColor == null) {
|
||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||
mDefaultTextColor = keyId.getTextColors();
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
private int mIndexUserId, mIndexRank;
|
||||
private int mVerifiedId;
|
||||
|
||||
final private ArrayList<Boolean> mCheckStates;
|
||||
private final ArrayList<Boolean> mCheckStates;
|
||||
|
||||
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
|
||||
super(context, c, flags);
|
||||
@ -57,9 +57,9 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
@Override
|
||||
public Cursor swapCursor(Cursor newCursor) {
|
||||
initIndex(newCursor);
|
||||
if(mCheckStates != null) {
|
||||
if (mCheckStates != null) {
|
||||
mCheckStates.clear();
|
||||
if(newCursor != null) {
|
||||
if (newCursor != null) {
|
||||
int count = newCursor.getCount();
|
||||
mCheckStates.ensureCapacity(count);
|
||||
// initialize to true (use case knowledge: we usually want to sign all uids)
|
||||
@ -84,7 +84,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
if (cursor != null) {
|
||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||
mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);
|
||||
mVerifiedId = cursor.getColumnIndexOrThrow("verified");
|
||||
// mVerifiedId = cursor.getColumnIndexOrThrow(UserIds.VERIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +106,7 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
}
|
||||
vAddress.setText(userId[1]);
|
||||
|
||||
int verified = cursor.getInt(mVerifiedId);
|
||||
int verified = 1; // cursor.getInt(mVerifiedId);
|
||||
// TODO introduce own resource for this :)
|
||||
if(verified > 0)
|
||||
vVerified.setImageResource(android.R.drawable.presence_online);
|
||||
@ -114,8 +114,9 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
vVerified.setImageResource(android.R.drawable.presence_invisible);
|
||||
|
||||
// don't care further if checkboxes aren't shown
|
||||
if(mCheckStates == null)
|
||||
if (mCheckStates == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox);
|
||||
final int position = cursor.getPosition();
|
||||
@ -138,8 +139,8 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
||||
|
||||
public ArrayList<String> getSelectedUserIds() {
|
||||
ArrayList<String> result = new ArrayList<String>();
|
||||
for(int i = 0; i < mCheckStates.size(); i++) {
|
||||
if(mCheckStates.get(i)) {
|
||||
for (int i = 0; i < mCheckStates.size(); i++) {
|
||||
if (mCheckStates.get(i)) {
|
||||
mCursor.moveToPosition(i);
|
||||
result.add(mCursor.getString(mIndexUserId));
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
@ -113,21 +114,8 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
||||
public void onClick(DialogInterface di, int id) {
|
||||
di.dismiss();
|
||||
try {
|
||||
int nKeyIndex = keySize.getSelectedItemPosition();
|
||||
switch (nKeyIndex) {
|
||||
case 0:
|
||||
mNewKeySize = 512;
|
||||
break;
|
||||
case 1:
|
||||
mNewKeySize = 1024;
|
||||
break;
|
||||
case 2:
|
||||
mNewKeySize = 2048;
|
||||
break;
|
||||
case 3:
|
||||
mNewKeySize = 4096;
|
||||
break;
|
||||
}
|
||||
final String selectedItem = (String) keySize.getSelectedItem();
|
||||
mNewKeySize = Integer.parseInt(selectedItem);
|
||||
} catch (NumberFormatException e) {
|
||||
mNewKeySize = 0;
|
||||
}
|
||||
@ -145,7 +133,27 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
||||
}
|
||||
});
|
||||
|
||||
return dialog.create();
|
||||
final AlertDialog alertDialog = dialog.create();
|
||||
|
||||
final AdapterView.OnItemSelectedListener weakRsaListener = new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
final Choice selectedAlgorithm = (Choice) algorithm.getSelectedItem();
|
||||
final int selectedKeySize = Integer.parseInt((String) keySize.getSelectedItem());
|
||||
final boolean isWeakRsa = (selectedAlgorithm.getId() == Id.choice.algorithm.rsa &&
|
||||
selectedKeySize <= 1024);
|
||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!isWeakRsa);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
};
|
||||
|
||||
keySize.setOnItemSelectedListener(weakRsaListener);
|
||||
algorithm.setOnItemSelectedListener(weakRsaListener);
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -87,11 +87,11 @@ public class DeleteFileDialogFragment extends DialogFragment {
|
||||
false,
|
||||
null);
|
||||
|
||||
// Message is received after deleting is done in ApgService
|
||||
// Message is received after deleting is done in KeychainIntentService
|
||||
KeychainIntentServiceHandler saveHandler =
|
||||
new KeychainIntentServiceHandler(activity, deletingDialog) {
|
||||
public void handleMessage(Message message) {
|
||||
// handle messages by standard ApgHandler first
|
||||
// handle messages by standard KeychainIntentHandler first
|
||||
super.handleMessage(message);
|
||||
|
||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2013 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -20,161 +20,125 @@ package org.sufficientlysecure.keychain.ui.dialog;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.Id;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
private static final String ARG_MESSENGER = "messenger";
|
||||
private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file";
|
||||
private static final String ARG_KEY_TYPE = "key_type";
|
||||
private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
|
||||
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_ERROR = 0;
|
||||
|
||||
public static final String MESSAGE_NOT_DELETED = "not_deleted";
|
||||
private boolean mIsSingleSelection = false;
|
||||
|
||||
private TextView mMainMessage;
|
||||
private CheckBox mCheckDeleteSecret;
|
||||
private LinearLayout mDeleteSecretKeyView;
|
||||
private View mInflateView;
|
||||
|
||||
private Messenger mMessenger;
|
||||
|
||||
/**
|
||||
* Creates new instance of this delete file dialog fragment
|
||||
*/
|
||||
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds,
|
||||
int keyType) {
|
||||
public static DeleteKeyDialogFragment newInstance(Messenger messenger,
|
||||
long[] masterKeyIds) {
|
||||
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(ARG_MESSENGER, messenger);
|
||||
args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds);
|
||||
args.putInt(ARG_KEY_TYPE, keyType);
|
||||
args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
|
||||
//We don't need the key type
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates dialog
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
|
||||
final FragmentActivity activity = getActivity();
|
||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||
|
||||
final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS);
|
||||
final int keyType = getArguments().getInt(ARG_KEY_TYPE);
|
||||
final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
|
||||
//Setup custom View to display in AlertDialog
|
||||
LayoutInflater inflater = activity.getLayoutInflater();
|
||||
mInflateView = inflater.inflate(R.layout.view_key_delete_fragment, null);
|
||||
builder.setView(mInflateView);
|
||||
|
||||
mDeleteSecretKeyView = (LinearLayout) mInflateView.findViewById(R.id.deleteSecretKeyView);
|
||||
mMainMessage = (TextView) mInflateView.findViewById(R.id.mainMessage);
|
||||
mCheckDeleteSecret = (CheckBox) mInflateView.findViewById(R.id.checkDeleteSecret);
|
||||
|
||||
builder.setTitle(R.string.warning);
|
||||
|
||||
if (keyRingRowIds.length == 1) {
|
||||
Uri dataUri;
|
||||
if (keyType == Id.type.public_key) {
|
||||
dataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowIds[0]));
|
||||
} else {
|
||||
dataUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowIds[0]));
|
||||
}
|
||||
String userId = ProviderHelper.getUserId(activity, dataUri);
|
||||
// If only a single key has been selected
|
||||
if (masterKeyIds.length == 1) {
|
||||
mIsSingleSelection = true;
|
||||
|
||||
builder.setMessage(getString(
|
||||
keyType == Id.type.public_key ? R.string.key_deletion_confirmation
|
||||
: R.string.secret_key_deletion_confirmation, userId));
|
||||
long masterKeyId = masterKeyIds[0];
|
||||
|
||||
HashMap<String, Object> data = ProviderHelper.getUnifiedData(activity, masterKeyId, new String[]{
|
||||
KeyRings.USER_ID,
|
||||
KeyRings.HAS_SECRET
|
||||
}, new int[] { ProviderHelper.FIELD_TYPE_STRING, ProviderHelper.FIELD_TYPE_INTEGER });
|
||||
String userId = (String) data.get(KeyRings.USER_ID);
|
||||
boolean hasSecret = ((Long) data.get(KeyRings.HAS_SECRET)) == 1;
|
||||
|
||||
// Hide the Checkbox and TextView since this is a single selection,user will be notified through message
|
||||
mDeleteSecretKeyView.setVisibility(View.GONE);
|
||||
// Set message depending on which key it is.
|
||||
mMainMessage.setText(getString(
|
||||
hasSecret ? R.string.secret_key_deletion_confirmation
|
||||
: R.string.public_key_deletetion_confirmation,
|
||||
userId));
|
||||
} else {
|
||||
builder.setMessage(R.string.key_deletion_confirmation_multi);
|
||||
mDeleteSecretKeyView.setVisibility(View.VISIBLE);
|
||||
mMainMessage.setText(R.string.key_deletion_confirmation_multi);
|
||||
}
|
||||
|
||||
builder.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
ArrayList<String> notDeleted = new ArrayList<String>();
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
if (keyType == Id.type.public_key) {
|
||||
Uri queryUri = KeychainContract.KeyRings.buildPublicKeyRingsUri();
|
||||
String[] projection = new String[]{
|
||||
KeychainContract.KeyRings._ID, // 0
|
||||
KeychainContract.KeyRings.MASTER_KEY_ID, // 1
|
||||
KeychainContract.UserIds.USER_ID // 2
|
||||
};
|
||||
|
||||
// make selection with all entries where _ID is one of the given row ids
|
||||
String selection = KeychainDatabase.Tables.KEY_RINGS + "." +
|
||||
KeychainContract.KeyRings._ID + " IN(";
|
||||
String selectionIDs = "";
|
||||
for (int i = 0; i < keyRingRowIds.length; i++) {
|
||||
selectionIDs += "'" + String.valueOf(keyRingRowIds[i]) + "'";
|
||||
if (i + 1 < keyRingRowIds.length) {
|
||||
selectionIDs += ",";
|
||||
}
|
||||
}
|
||||
selection += selectionIDs + ")";
|
||||
|
||||
Cursor cursor = activity.getContentResolver().query(queryUri, projection,
|
||||
selection, null, null);
|
||||
|
||||
long rowId;
|
||||
long masterKeyId;
|
||||
String userId;
|
||||
try {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
rowId = cursor.getLong(0);
|
||||
masterKeyId = cursor.getLong(1);
|
||||
userId = cursor.getString(2);
|
||||
|
||||
Log.d(Constants.TAG, "rowId: " + rowId + ", masterKeyId: " + masterKeyId
|
||||
+ ", userId: " + userId);
|
||||
|
||||
// check if a corresponding secret key exists...
|
||||
Cursor secretCursor = activity.getContentResolver().query(
|
||||
KeychainContract.KeyRings
|
||||
.buildSecretKeyRingsByMasterKeyIdUri(
|
||||
String.valueOf(masterKeyId)),
|
||||
null, null, null, null
|
||||
);
|
||||
if (secretCursor != null && secretCursor.getCount() > 0) {
|
||||
notDeleted.add(userId);
|
||||
} else {
|
||||
// it is okay to delete this key, no secret key found!
|
||||
ProviderHelper.deletePublicKeyRing(activity, rowId);
|
||||
}
|
||||
if (secretCursor != null) {
|
||||
secretCursor.close();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (long keyRowId : keyRingRowIds) {
|
||||
ProviderHelper.deleteSecretKeyRing(activity, keyRowId);
|
||||
}
|
||||
boolean success = false;
|
||||
for(long masterKeyId : masterKeyIds) {
|
||||
int count = activity.getContentResolver().delete(
|
||||
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null
|
||||
);
|
||||
success = count > 0;
|
||||
}
|
||||
|
||||
dismiss();
|
||||
|
||||
if (notDeleted.size() > 0) {
|
||||
Bundle data = new Bundle();
|
||||
data.putStringArrayList(MESSAGE_NOT_DELETED, notDeleted);
|
||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||
} else {
|
||||
if (success) {
|
||||
sendMessageToHandler(MESSAGE_OKAY, null);
|
||||
} else {
|
||||
sendMessageToHandler(MESSAGE_ERROR, null);
|
||||
}
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@ -184,6 +148,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@ -198,7 +163,6 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
|
@ -24,10 +24,12 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@ -59,10 +61,33 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
public static final int MESSAGE_OKAY = 1;
|
||||
public static final int MESSAGE_CANCEL = 2;
|
||||
|
||||
public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
|
||||
|
||||
private Messenger mMessenger;
|
||||
private EditText mPassphraseEditText;
|
||||
private boolean mCanKB;
|
||||
|
||||
/**
|
||||
* Shows passphrase dialog to cache a new passphrase the user enters for using it later for
|
||||
* encryption. Based on mSecretKeyId it asks for a passphrase to open a private key or it asks
|
||||
* for a symmetric passphrase
|
||||
*/
|
||||
public static void show(FragmentActivity context, long keyId, Handler returnHandler) {
|
||||
// Create a new Messenger for the communication back
|
||||
Messenger messenger = new Messenger(returnHandler);
|
||||
|
||||
try {
|
||||
PassphraseDialogFragment passphraseDialog = PassphraseDialogFragment.newInstance(context,
|
||||
messenger, keyId);
|
||||
|
||||
passphraseDialog.show(context.getSupportFragmentManager(), "passphraseDialog");
|
||||
} catch (PgpGeneralException e) {
|
||||
Log.d(Constants.TAG, "No passphrase for this secret key, encrypt directly!");
|
||||
// send message to handler to start encryption directly
|
||||
returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new instance of this dialog fragment
|
||||
*
|
||||
@ -114,10 +139,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
secretKey = null;
|
||||
alert.setMessage(R.string.passphrase_for_symmetric_encryption);
|
||||
} else {
|
||||
// TODO: by master key id???
|
||||
secretKey = PgpKeyHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity,
|
||||
secretKeyId));
|
||||
// secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
|
||||
secretKey = ProviderHelper.getPGPSecretKeyRing(activity, secretKeyId).getSecretKey();
|
||||
|
||||
if (secretKey == null) {
|
||||
alert.setTitle(R.string.title_key_not_found);
|
||||
@ -173,7 +195,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
return;
|
||||
} else {
|
||||
clickSecretKey = PgpKeyHelper.getKeyNum(ProviderHelper
|
||||
.getPGPSecretKeyRingByKeyId(activity, secretKeyId),
|
||||
.getPGPSecretKeyRingWithKeyId(activity, secretKeyId),
|
||||
curKeyIndex);
|
||||
curKeyIndex++; // does post-increment work like C?
|
||||
continue;
|
||||
@ -209,7 +231,11 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
passphrase);
|
||||
}
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY);
|
||||
// also return passphrase back to activity
|
||||
Bundle data = new Bundle();
|
||||
data.putString(MESSAGE_DATA_PASSPHRASE, passphrase);
|
||||
|
||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
||||
}
|
||||
});
|
||||
|
||||
@ -278,4 +304,25 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message back to handler which is initialized in a activity
|
||||
*
|
||||
* @param what Message integer you want to send
|
||||
*/
|
||||
private void sendMessageToHandler(Integer what, Bundle data) {
|
||||
Message msg = Message.obtain();
|
||||
msg.what = what;
|
||||
if (data != null) {
|
||||
msg.setData(data);
|
||||
}
|
||||
|
||||
try {
|
||||
mMessenger.send(msg);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
|
||||
} catch (NullPointerException e) {
|
||||
Log.w(Constants.TAG, "Messenger is null!", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import android.widget.TextView;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||
|
||||
@ -89,29 +90,33 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
|
||||
if (mFingerprintOnly) {
|
||||
alert.setPositiveButton(R.string.btn_okay, null);
|
||||
|
||||
byte[] fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), dataUri);
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false);
|
||||
byte[] blob = (byte[]) ProviderHelper.getGenericData(
|
||||
getActivity(), KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||
if(blob == null) {
|
||||
// TODO error handling?!
|
||||
return null;
|
||||
}
|
||||
|
||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
|
||||
|
||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||
setQrCode(content);
|
||||
} else {
|
||||
mText.setText(R.string.share_qr_code_dialog_start);
|
||||
|
||||
// TODO
|
||||
// TODO works, but
|
||||
long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
||||
|
||||
// get public keyring as ascii armored string
|
||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||
getActivity(), dataUri, new long[]{masterKeyId});
|
||||
getActivity(), new long[] { masterKeyId });
|
||||
|
||||
// TODO: binary?
|
||||
|
||||
content = keyringArmored.get(0);
|
||||
|
||||
// OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
|
||||
// http://stackoverflow.com/questions/2620444/how-to-prevent-a-dialog-from-closing-when-a-button-is-clicked
|
||||
// http://bit.ly/O5vfaR
|
||||
alert.setPositiveButton(R.string.btn_next, null);
|
||||
alert.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user