mirror of
https://github.com/moparisthebest/open-keychain
synced 2024-11-23 17:22:16 -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
|
2.3.1
|
||||||
* hotfix for crash when upgrading from old versions
|
* hotfix for crash when upgrading from old versions
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.sufficientlysecure.keychain.demo"
|
package="org.sufficientlysecure.keychain.demo"
|
||||||
android:versionCode="3"
|
android:versionCode="4"
|
||||||
android:versionName="2">
|
android:versionName="3">
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="9"
|
android:minSdkVersion="9"
|
||||||
|
@ -48,6 +48,7 @@ public class OpenPgpProviderActivity extends Activity {
|
|||||||
private Button mEncrypt;
|
private Button mEncrypt;
|
||||||
private Button mSignAndEncrypt;
|
private Button mSignAndEncrypt;
|
||||||
private Button mDecryptAndVerify;
|
private Button mDecryptAndVerify;
|
||||||
|
private EditText mAccount;
|
||||||
|
|
||||||
private OpenPgpServiceConnection mServiceConnection;
|
private OpenPgpServiceConnection mServiceConnection;
|
||||||
|
|
||||||
@ -57,8 +58,8 @@ public class OpenPgpProviderActivity extends Activity {
|
|||||||
public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
|
public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.openpgp_provider);
|
setContentView(R.layout.openpgp_provider);
|
||||||
|
|
||||||
mMessage = (EditText) findViewById(R.id.crypto_provider_demo_message);
|
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);
|
mEncrypt = (Button) findViewById(R.id.crypto_provider_demo_encrypt);
|
||||||
mSignAndEncrypt = (Button) findViewById(R.id.crypto_provider_demo_sign_and_encrypt);
|
mSignAndEncrypt = (Button) findViewById(R.id.crypto_provider_demo_sign_and_encrypt);
|
||||||
mDecryptAndVerify = (Button) findViewById(R.id.crypto_provider_demo_decrypt_and_verify);
|
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() {
|
mSign.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -142,7 +144,7 @@ public class OpenPgpProviderActivity extends Activity {
|
|||||||
private InputStream getInputstream(boolean ciphertext) {
|
private InputStream getInputstream(boolean ciphertext) {
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
String inputStr = null;
|
String inputStr;
|
||||||
if (ciphertext) {
|
if (ciphertext) {
|
||||||
inputStr = mCiphertext.getText().toString();
|
inputStr = mCiphertext.getText().toString();
|
||||||
} else {
|
} else {
|
||||||
@ -169,7 +171,7 @@ public class OpenPgpProviderActivity extends Activity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReturn(Intent result) {
|
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: {
|
case OpenPgpApi.RESULT_CODE_SUCCESS: {
|
||||||
try {
|
try {
|
||||||
Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length
|
Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length
|
||||||
@ -213,9 +215,10 @@ public class OpenPgpProviderActivity extends Activity {
|
|||||||
public void sign(Intent data) {
|
public void sign(Intent data) {
|
||||||
data.setAction(OpenPgpApi.ACTION_SIGN);
|
data.setAction(OpenPgpApi.ACTION_SIGN);
|
||||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||||
|
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||||
|
|
||||||
InputStream is = getInputstream(false);
|
InputStream is = getInputstream(false);
|
||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||||
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN));
|
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.setAction(OpenPgpApi.ACTION_ENCRYPT);
|
||||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
|
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
|
||||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||||
|
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||||
|
|
||||||
InputStream is = getInputstream(false);
|
InputStream is = getInputstream(false);
|
||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||||
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_ENCRYPT));
|
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.setAction(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
||||||
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
|
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, mEncryptUserIds.getText().toString().split(","));
|
||||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||||
|
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||||
|
|
||||||
InputStream is = getInputstream(false);
|
InputStream is = getInputstream(false);
|
||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||||
api.executeApiAsync(data, is, os, new MyCallback(true, os, REQUEST_CODE_SIGN_AND_ENCRYPT));
|
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) {
|
public void decryptAndVerify(Intent data) {
|
||||||
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||||
|
data.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, mAccount.getText().toString());
|
||||||
|
|
||||||
InputStream is = getInputstream(true);
|
InputStream is = getInputstream(true);
|
||||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
OpenPgpApi api = new OpenPgpApi(this, mServiceConnection.getService());
|
||||||
api.executeApiAsync(data, is, os, new MyCallback(false, os, REQUEST_CODE_DECRYPT_AND_VERIFY));
|
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
|
// try again after user interaction
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
/*
|
/*
|
||||||
* The data originally given to the pgp method are are again
|
* The data originally given to one of the methods above, is again
|
||||||
* returned here to be used when calling again after user interaction.
|
* returned here to be used when calling the method again after user
|
||||||
*
|
* interaction. The Intent now also contains results from the user
|
||||||
* They also contain results from the user interaction which happened,
|
* interaction, for example selected key ids.
|
||||||
* for example selected key ids.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_CODE_SIGN: {
|
case REQUEST_CODE_SIGN: {
|
||||||
sign(data);
|
sign(data);
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
android:scrollHorizontally="true"
|
android:scrollHorizontally="true"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
android:text="message"
|
android:text="message"
|
||||||
|
android:hint="cleartext message"
|
||||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
@ -66,6 +67,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="ciphertext"
|
android:text="ciphertext"
|
||||||
|
android:hint="ciphertext"
|
||||||
android:textAppearance="@android:style/TextAppearance.Small" />
|
android:textAppearance="@android:style/TextAppearance.Small" />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
@ -104,5 +106,18 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Decrypt and Verify" />
|
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>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
@ -19,12 +19,22 @@ package org.openintents.openpgp;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
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 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 GENERIC_ERROR = 0;
|
||||||
public static final int INCOMPATIBLE_API_VERSIONS = 1;
|
public static final int INCOMPATIBLE_API_VERSIONS = 1;
|
||||||
|
|
||||||
public static final int NO_OR_WRONG_PASSPHRASE = 2;
|
public static final int NO_OR_WRONG_PASSPHRASE = 2;
|
||||||
public static final int NO_USER_IDS = 3;
|
public static final int NO_USER_IDS = 3;
|
||||||
|
|
||||||
@ -65,15 +75,39 @@ public class OpenPgpError implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
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.writeInt(errorId);
|
||||||
dest.writeString(message);
|
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 static final Creator<OpenPgpError> CREATOR = new Creator<OpenPgpError>() {
|
||||||
public OpenPgpError createFromParcel(final Parcel source) {
|
public OpenPgpError createFromParcel(final Parcel source) {
|
||||||
|
int parcelableVersion = source.readInt();
|
||||||
|
int parcelableSize = source.readInt();
|
||||||
|
int startPosition = source.dataPosition();
|
||||||
|
|
||||||
OpenPgpError error = new OpenPgpError();
|
OpenPgpError error = new OpenPgpError();
|
||||||
error.errorId = source.readInt();
|
error.errorId = source.readInt();
|
||||||
error.message = source.readString();
|
error.message = source.readString();
|
||||||
|
|
||||||
|
// skip over all fields added in future versions of this parcel
|
||||||
|
source.setDataPosition(startPosition + parcelableSize);
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,18 @@ package org.openintents.openpgp;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
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 {
|
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
|
// generic error on signature verification
|
||||||
public static final int SIGNATURE_ERROR = 0;
|
public static final int SIGNATURE_ERROR = 0;
|
||||||
// successfully verified signature, with certified public key
|
// successfully verified signature, with certified public key
|
||||||
@ -90,19 +101,43 @@ public class OpenPgpSignatureResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
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.writeInt(status);
|
||||||
dest.writeByte((byte) (signatureOnly ? 1 : 0));
|
dest.writeByte((byte) (signatureOnly ? 1 : 0));
|
||||||
dest.writeString(userId);
|
dest.writeString(userId);
|
||||||
dest.writeLong(keyId);
|
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 static final Creator<OpenPgpSignatureResult> CREATOR = new Creator<OpenPgpSignatureResult>() {
|
||||||
public OpenPgpSignatureResult createFromParcel(final Parcel source) {
|
public OpenPgpSignatureResult createFromParcel(final Parcel source) {
|
||||||
|
int parcelableVersion = source.readInt();
|
||||||
|
int parcelableSize = source.readInt();
|
||||||
|
int startPosition = source.dataPosition();
|
||||||
|
|
||||||
OpenPgpSignatureResult vr = new OpenPgpSignatureResult();
|
OpenPgpSignatureResult vr = new OpenPgpSignatureResult();
|
||||||
vr.status = source.readInt();
|
vr.status = source.readInt();
|
||||||
vr.signatureOnly = source.readByte() == 1;
|
vr.signatureOnly = source.readByte() == 1;
|
||||||
vr.userId = source.readString();
|
vr.userId = source.readString();
|
||||||
vr.keyId = source.readLong();
|
vr.keyId = source.readLong();
|
||||||
|
|
||||||
|
// skip over all fields added in future versions of this parcel
|
||||||
|
source.setDataPosition(startPosition + parcelableSize);
|
||||||
|
|
||||||
return vr;
|
return vr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ public class OpenPgpApi {
|
|||||||
|
|
||||||
public static final String TAG = "OpenPgp API";
|
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";
|
public static final String SERVICE_INTENT = "org.openintents.openpgp.IOpenPgpService";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,6 +126,8 @@ public class OpenPgpApi {
|
|||||||
/* Intent extras */
|
/* Intent extras */
|
||||||
public static final String EXTRA_API_VERSION = "api_version";
|
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
|
// SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
|
||||||
// request ASCII Armor for output
|
// request ASCII Armor for output
|
||||||
// OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
|
// 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;
|
import android.os.IBinder;
|
||||||
|
|
||||||
public class OpenPgpServiceConnection {
|
public class OpenPgpServiceConnection {
|
||||||
|
|
||||||
|
// interface to create callbacks for onServiceConnected
|
||||||
|
public interface OnBound {
|
||||||
|
public void onBound(IOpenPgpService service);
|
||||||
|
}
|
||||||
|
|
||||||
private Context mApplicationContext;
|
private Context mApplicationContext;
|
||||||
|
|
||||||
private boolean mBound;
|
|
||||||
private IOpenPgpService mService;
|
private IOpenPgpService mService;
|
||||||
private String mProviderPackageName;
|
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) {
|
public OpenPgpServiceConnection(Context context, String providerPackageName) {
|
||||||
this.mApplicationContext = context.getApplicationContext();
|
this.mApplicationContext = context.getApplicationContext();
|
||||||
this.mProviderPackageName = providerPackageName;
|
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() {
|
public IOpenPgpService getService() {
|
||||||
return mService;
|
return mService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBound() {
|
public boolean isBound() {
|
||||||
return mBound;
|
return (mService != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
mService = IOpenPgpService.Stub.asInterface(service);
|
mService = IOpenPgpService.Stub.asInterface(service);
|
||||||
mBound = true;
|
if (mOnBoundListener != null) {
|
||||||
|
mOnBoundListener.onBound(mService);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
mService = null;
|
mService = null;
|
||||||
mBound = false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -63,7 +93,7 @@ public class OpenPgpServiceConnection {
|
|||||||
*/
|
*/
|
||||||
public boolean bindToService() {
|
public boolean bindToService() {
|
||||||
// if not already bound...
|
// if not already bound...
|
||||||
if (mService == null && !mBound) {
|
if (mService == null) {
|
||||||
try {
|
try {
|
||||||
Intent serviceIntent = new Intent();
|
Intent serviceIntent = new Intent();
|
||||||
serviceIntent.setAction(IOpenPgpService.class.getName());
|
serviceIntent.setAction(IOpenPgpService.class.getName());
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
apply plugin: 'android'
|
apply plugin: 'android'
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
testLocal {
|
||||||
|
java.srcDir file('src/test/java')
|
||||||
|
resources.srcDir file('src/test/resources')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-v4:19.0.1'
|
compile 'com.android.support:support-v4:19.0.1'
|
||||||
compile 'com.android.support:appcompat-v7: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:pkix')
|
||||||
compile project(':libraries:spongycastle:prov')
|
compile project(':libraries:spongycastle:prov')
|
||||||
compile project(':libraries:Android-AppMsg:library')
|
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 {
|
android {
|
||||||
@ -61,3 +87,19 @@ android {
|
|||||||
htmlOutput file("lint-report.html")
|
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"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.sufficientlysecure.keychain"
|
package="org.sufficientlysecure.keychain"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="23104"
|
android:versionCode="25000"
|
||||||
android:versionName="2.3.1 beta4">
|
android:versionName="2.5">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
General remarks
|
General remarks
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<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! -->
|
<!-- android:allowBackup="false": Don't allow backup over adb backup or other apps! -->
|
||||||
<application
|
<application
|
||||||
@ -107,14 +107,12 @@
|
|||||||
android:name=".ui.SelectPublicKeyActivity"
|
android:name=".ui.SelectPublicKeyActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:label="@string/title_select_recipients"
|
android:label="@string/title_select_recipients"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop"></activity>
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.SelectSecretKeyActivity"
|
android:name=".ui.SelectSecretKeyActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:label="@string/title_select_secret_key"
|
android:label="@string/title_select_secret_key"
|
||||||
android:launchMode="singleTop">
|
android:launchMode="singleTop"></activity>
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.EncryptActivity"
|
android:name=".ui.EncryptActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
@ -153,23 +151,23 @@
|
|||||||
|
|
||||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
<!--<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.BROWSABLE" />-->
|
||||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||||
|
|
||||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||||
<!--<data android:mimeType="application/pgp-signature" />-->
|
<!--<data android:mimeType="application/pgp-signature" />-->
|
||||||
<!--</intent-filter>-->
|
<!--</intent-filter>-->
|
||||||
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
<!--<!– VIEW with mimeType: TODO (from email app) –>-->
|
||||||
<!--<intent-filter android:label="@string/intent_import_key">-->
|
<!--<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.BROWSABLE" />-->
|
||||||
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
<!--<category android:name="android.intent.category.DEFAULT" />-->
|
||||||
|
|
||||||
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
<!--<!– mime type as defined in http://tools.ietf.org/html/rfc3156 –>-->
|
||||||
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
<!--<data android:mimeType="application/pgp-encrypted" />-->
|
||||||
<!--</intent-filter>-->
|
<!--</intent-filter>-->
|
||||||
<!-- Keychain's own Actions -->
|
<!-- Keychain's own Actions -->
|
||||||
<!-- DECRYPT with text as extra -->
|
<!-- DECRYPT with text as extra -->
|
||||||
@ -245,7 +243,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.PreferencesActivity"
|
android:name=".ui.PreferencesActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:label="@string/title_preferences" >
|
android:label="@string/title_preferences">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
|
<action android:name="org.sufficientlysecure.keychain.ui.PREFS_GEN" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
@ -383,44 +381,49 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":passphrase_cache" />
|
android:process=":passphrase_cache" />
|
||||||
<service
|
<service
|
||||||
android:name="org.sufficientlysecure.keychain.service.KeychainIntentService"
|
android:name=".service.KeychainIntentService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="org.sufficientlysecure.keychain.provider.KeychainProvider"
|
android:name=".provider.KeychainProvider"
|
||||||
android:authorities="org.sufficientlysecure.keychain.provider"
|
android:authorities="org.sufficientlysecure.keychain.provider"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Internal classes of the remote APIs (not exported) -->
|
<!-- Internal classes of the remote APIs (not exported) -->
|
||||||
<activity
|
<activity
|
||||||
android:name="org.sufficientlysecure.keychain.service.remote.RemoteServiceActivity"
|
android:name=".remote.ui.RemoteServiceActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/app_name" />
|
android:label="@string/app_name"
|
||||||
<!--android:launchMode="singleTop"-->
|
android:launchMode="singleTop"
|
||||||
<!--android:process=":remote_api"-->
|
android:process=":remote_api" />
|
||||||
<activity
|
<activity
|
||||||
android:name="org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity"
|
android:name=".remote.ui.AppsListActivity"
|
||||||
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:label="@string/title_api_registered_apps" />
|
android:label="@string/title_api_registered_apps" />
|
||||||
<activity
|
<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:configChanges="orientation|screenSize|keyboardHidden|keyboard"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- OpenPGP Remote API -->
|
<!-- OpenPGP Remote API -->
|
||||||
<service
|
<service
|
||||||
android:name="org.sufficientlysecure.keychain.service.remote.OpenPgpService"
|
android:name=".remote.OpenPgpService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:process=":remote_api">
|
android:process=":remote_api">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="org.openintents.openpgp.IOpenPgpService" />
|
<action android:name="org.openintents.openpgp.IOpenPgpService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="api_version"
|
|
||||||
android:value="1" />
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- Extended Remote API -->
|
<!-- Extended Remote API -->
|
||||||
|
@ -19,6 +19,11 @@ package org.sufficientlysecure.keychain;
|
|||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
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 {
|
public final class Constants {
|
||||||
|
|
||||||
@ -42,7 +47,7 @@ public final class Constants {
|
|||||||
|
|
||||||
public static final class Path {
|
public static final class Path {
|
||||||
public static final String APP_DIR = Environment.getExternalStorageDirectory()
|
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_SEC = APP_DIR + "/secexport.asc";
|
||||||
public static final String APP_DIR_FILE_PUB = APP_DIR + "/pubexport.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 class Pref {
|
||||||
public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm";
|
public static final String DEFAULT_ENCRYPTION_ALGORITHM = "defaultEncryptionAlgorithm";
|
||||||
public static final String DEFAULT_HASH_ALGORITHM = "defaultHashAlgorithm";
|
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_MESSAGE_COMPRESSION = "defaultMessageCompression";
|
||||||
public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression";
|
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 LANGUAGE = "language";
|
||||||
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
|
public static final String FORCE_V3_SIGNATURES = "forceV3Signatures";
|
||||||
public static final String KEY_SERVERS = "keyServers";
|
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 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 secret_key = 0x21070002;
|
||||||
public static final int user_id = 0x21070003;
|
public static final int user_id = 0x21070003;
|
||||||
public static final int key = 0x21070004;
|
public static final int key = 0x21070004;
|
||||||
|
public static final int public_secret_key = 0x21070005;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class choice {
|
public static final class choice {
|
||||||
|
@ -17,16 +17,17 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain;
|
package org.sufficientlysecure.keychain;
|
||||||
|
|
||||||
import java.io.File;
|
import android.app.Application;
|
||||||
import java.security.Provider;
|
import android.os.Environment;
|
||||||
import java.security.Security;
|
|
||||||
|
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
import org.sufficientlysecure.keychain.util.PRNGFixes;
|
||||||
|
|
||||||
import android.app.Application;
|
import java.io.File;
|
||||||
import android.os.Environment;
|
import java.security.Provider;
|
||||||
|
import java.security.Security;
|
||||||
|
|
||||||
public class KeychainApplication extends Application {
|
public class KeychainApplication extends Application {
|
||||||
|
|
||||||
@ -40,14 +41,14 @@ public class KeychainApplication extends Application {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Sets Bouncy (Spongy) Castle as preferred security provider
|
* Sets Bouncy (Spongy) Castle as preferred security provider
|
||||||
*
|
*
|
||||||
* insertProviderAt() position starts from 1
|
* insertProviderAt() position starts from 1
|
||||||
*/
|
*/
|
||||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* apply RNG fixes
|
* apply RNG fixes
|
||||||
*
|
*
|
||||||
* among other things, executes Security.insertProviderAt(new
|
* among other things, executes Security.insertProviderAt(new
|
||||||
* LinuxPRNGSecureRandomProvider(), 1) for Android <= SDK 17
|
* LinuxPRNGSecureRandomProvider(), 1) for Android <= SDK 17
|
||||||
*/
|
*/
|
||||||
|
@ -17,20 +17,20 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.compatibility;
|
package org.sufficientlysecure.keychain.compatibility;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class ClipboardReflection {
|
public class ClipboardReflection {
|
||||||
|
|
||||||
private static final String clipboardLabel = "Keychain";
|
private static final String clipboardLabel = "Keychain";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around ClipboardManager based on Android version using Reflection API
|
* Wrapper around ClipboardManager based on Android version using Reflection API
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
* @param text
|
* @param text
|
||||||
*/
|
*/
|
||||||
@ -57,7 +57,7 @@ public class ClipboardReflection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around ClipboardManager based on Android version using Reflection API
|
* Wrapper around ClipboardManager based on Android version using Reflection API
|
||||||
*
|
*
|
||||||
* @param context
|
* @param context
|
||||||
*/
|
*/
|
||||||
public static CharSequence getClipboardText(Context context) {
|
public static CharSequence getClipboardText(Context context) {
|
||||||
|
@ -37,7 +37,6 @@ public class ActionBarHelper {
|
|||||||
* @param activity
|
* @param activity
|
||||||
*/
|
*/
|
||||||
public static void setBackButton(ActionBarActivity activity) {
|
public static void setBackButton(ActionBarActivity activity) {
|
||||||
// set actionbar without home button if called from another app
|
|
||||||
final ActionBar actionBar = activity.getSupportActionBar();
|
final ActionBar actionBar = activity.getSupportActionBar();
|
||||||
Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
|
Log.d(Constants.TAG, "calling package (only set when using startActivityForResult)="
|
||||||
+ activity.getCallingPackage());
|
+ activity.getCallingPackage());
|
||||||
|
@ -30,6 +30,7 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
|
||||||
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||||
@ -47,23 +48,21 @@ public class ExportHelper {
|
|||||||
this.mActivity = activity;
|
this.mActivity = activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteKey(Uri dataUri, final int keyType, Handler deleteHandler) {
|
public void deleteKey(Uri dataUri, Handler deleteHandler) {
|
||||||
long keyRingRowId = Long.valueOf(dataUri.getLastPathSegment());
|
|
||||||
|
|
||||||
// Create a new Messenger for the communication back
|
// Create a new Messenger for the communication back
|
||||||
Messenger messenger = new Messenger(deleteHandler);
|
Messenger messenger = new Messenger(deleteHandler);
|
||||||
|
long masterKeyId = ProviderHelper.getMasterKeyId(mActivity, dataUri);
|
||||||
|
|
||||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
||||||
new long[]{keyRingRowId}, keyType);
|
new long[]{ masterKeyId });
|
||||||
|
|
||||||
deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog");
|
deleteKeyDialog.show(mActivity.getSupportFragmentManager(), "deleteKeyDialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show dialog where to export keys
|
* Show dialog where to export keys
|
||||||
*/
|
*/
|
||||||
public void showExportKeysDialog(final long[] rowIds, final int keyType,
|
public void showExportKeysDialog(final long[] masterKeyIds, final String exportFilename,
|
||||||
final String exportFilename) {
|
final boolean showSecretCheckbox) {
|
||||||
mExportFilename = exportFilename;
|
mExportFilename = exportFilename;
|
||||||
|
|
||||||
// Message is received after file is selected
|
// Message is received after file is selected
|
||||||
@ -74,7 +73,7 @@ public class ExportHelper {
|
|||||||
Bundle data = message.getData();
|
Bundle data = message.getData();
|
||||||
mExportFilename = data.getString(FileDialogFragment.MESSAGE_DATA_FILENAME);
|
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() {
|
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
String title = null;
|
String title = null;
|
||||||
if (rowIds == null) {
|
if (masterKeyIds == null) {
|
||||||
// export all keys
|
// export all keys
|
||||||
title = mActivity.getString(R.string.title_export_keys);
|
title = mActivity.getString(R.string.title_export_keys);
|
||||||
} else {
|
} else {
|
||||||
@ -93,15 +92,12 @@ public class ExportHelper {
|
|||||||
title = mActivity.getString(R.string.title_export_key);
|
title = mActivity.getString(R.string.title_export_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
String message = null;
|
String message = mActivity.getString(R.string.specify_file_to_export_to);
|
||||||
if (keyType == Id.type.public_key) {
|
String checkMsg = showSecretCheckbox ?
|
||||||
message = mActivity.getString(R.string.specify_file_to_export_to);
|
mActivity.getString(R.string.also_export_secret_keys) : null;
|
||||||
} else {
|
|
||||||
message = mActivity.getString(R.string.specify_file_to_export_secret_keys_to);
|
|
||||||
}
|
|
||||||
|
|
||||||
mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
|
mFileDialog = FileDialogFragment.newInstance(messenger, title, message,
|
||||||
exportFilename, null);
|
exportFilename, checkMsg);
|
||||||
|
|
||||||
mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
|
mFileDialog.show(mActivity.getSupportFragmentManager(), "fileDialog");
|
||||||
}
|
}
|
||||||
@ -111,7 +107,7 @@ public class ExportHelper {
|
|||||||
/**
|
/**
|
||||||
* Export keys
|
* Export keys
|
||||||
*/
|
*/
|
||||||
public void exportKeys(long[] rowIds, int keyType) {
|
public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
|
||||||
Log.d(Constants.TAG, "exportKeys started");
|
Log.d(Constants.TAG, "exportKeys started");
|
||||||
|
|
||||||
// Send all information needed to service to export key in other thread
|
// Send all information needed to service to export key in other thread
|
||||||
@ -123,17 +119,17 @@ public class ExportHelper {
|
|||||||
Bundle data = new Bundle();
|
Bundle data = new Bundle();
|
||||||
|
|
||||||
data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
|
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);
|
data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
|
||||||
} else {
|
} 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);
|
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,
|
KeychainIntentServiceHandler exportHandler = new KeychainIntentServiceHandler(mActivity,
|
||||||
mActivity.getString(R.string.progress_exporting),
|
mActivity.getString(R.string.progress_exporting),
|
||||||
ProgressDialog.STYLE_HORIZONTAL,
|
ProgressDialog.STYLE_HORIZONTAL,
|
||||||
@ -145,7 +141,7 @@ public class ExportHelper {
|
|||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||||
|
@ -17,18 +17,14 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.helper;
|
package org.sufficientlysecure.keychain.helper;
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Spannable;
|
|
||||||
import android.text.SpannableStringBuilder;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -65,81 +61,10 @@ public class OtherHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SpannableStringBuilder colorizeFingerprint(String fingerprint) {
|
public static SpannableStringBuilder strikeOutText(CharSequence text) {
|
||||||
SpannableStringBuilder sb = new SpannableStringBuilder(fingerprint);
|
SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||||
try {
|
sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb;
|
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) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
@ -30,7 +30,7 @@ import java.util.Vector;
|
|||||||
* Singleton Implementation of a Preference Helper
|
* Singleton Implementation of a Preference Helper
|
||||||
*/
|
*/
|
||||||
public class Preferences {
|
public class Preferences {
|
||||||
private static Preferences mPreferences;
|
private static Preferences sPreferences;
|
||||||
private SharedPreferences mSharedPreferences;
|
private SharedPreferences mSharedPreferences;
|
||||||
|
|
||||||
public static synchronized Preferences getPreferences(Context context) {
|
public static synchronized Preferences getPreferences(Context context) {
|
||||||
@ -38,10 +38,10 @@ public class Preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
|
public static synchronized Preferences getPreferences(Context context, boolean forceNew) {
|
||||||
if (mPreferences == null || forceNew) {
|
if (sPreferences == null || forceNew) {
|
||||||
mPreferences = new Preferences(context);
|
sPreferences = new Preferences(context);
|
||||||
}
|
}
|
||||||
return mPreferences;
|
return sPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Preferences(Context context) {
|
private Preferences(Context context) {
|
||||||
@ -58,8 +58,8 @@ public class Preferences {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getPassPhraseCacheTtl() {
|
public long getPassphraseCacheTtl() {
|
||||||
int ttl = mSharedPreferences.getInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, 180);
|
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
|
// fix the value if it was set to "never" in previous versions, which currently is not
|
||||||
// supported
|
// supported
|
||||||
if (ttl == 0) {
|
if (ttl == 0) {
|
||||||
@ -68,9 +68,9 @@ public class Preferences {
|
|||||||
return (long) ttl;
|
return (long) ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassPhraseCacheTtl(int value) {
|
public void setPassphraseCacheTtl(int value) {
|
||||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||||
editor.putInt(Constants.Pref.PASS_PHRASE_CACHE_TTL, value);
|
editor.putInt(Constants.Pref.PASSPHRASE_CACHE_TTL, value);
|
||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,13 +118,13 @@ public class Preferences {
|
|||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getDefaultAsciiArmour() {
|
public boolean getDefaultAsciiArmor() {
|
||||||
return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, false);
|
return mSharedPreferences.getBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultAsciiArmour(boolean value) {
|
public void setDefaultAsciiArmor(boolean value) {
|
||||||
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
SharedPreferences.Editor editor = mSharedPreferences.edit();
|
||||||
editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOUR, value);
|
editor.putBoolean(Constants.Pref.DEFAULT_ASCII_ARMOR, value);
|
||||||
editor.commit();
|
editor.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,13 +61,32 @@ public class PgpConversionHelper {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static ArrayList<PGPSecretKey> BytesToPGPSecretKeyList(byte[] keysBytes) {
|
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>();
|
ArrayList<PGPSecretKey> keys = new ArrayList<PGPSecretKey>();
|
||||||
|
try {
|
||||||
@SuppressWarnings("unchecked")
|
while ((obj = factory.nextObject()) != null) {
|
||||||
Iterator<PGPSecretKey> itr = keyRing.getSecretKeys();
|
PGPSecretKey secKey = null;
|
||||||
while (itr.hasNext()) {
|
if (obj instanceof PGPSecretKey) {
|
||||||
keys.add(itr.next());
|
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;
|
return keys;
|
||||||
|
@ -18,28 +18,58 @@
|
|||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
import org.spongycastle.bcpg.ArmoredInputStream;
|
import org.spongycastle.bcpg.ArmoredInputStream;
|
||||||
import org.spongycastle.bcpg.SignatureSubpacketTags;
|
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.PGPUtil;
|
||||||
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
|
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
|
||||||
import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
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.security.SignatureException;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class uses a Builder pattern!
|
* This class uses a Builder pattern!
|
||||||
@ -50,9 +80,9 @@ public class PgpDecryptVerify {
|
|||||||
private OutputStream mOutStream;
|
private OutputStream mOutStream;
|
||||||
|
|
||||||
private ProgressDialogUpdater mProgressDialogUpdater;
|
private ProgressDialogUpdater mProgressDialogUpdater;
|
||||||
private boolean mAssumeSymmetric;
|
private boolean mAllowSymmetricDecryption;
|
||||||
private String mPassphrase;
|
private String mPassphrase;
|
||||||
private long mEnforcedKeyId;
|
private Set<Long> mAllowedKeyIds;
|
||||||
|
|
||||||
private PgpDecryptVerify(Builder builder) {
|
private PgpDecryptVerify(Builder builder) {
|
||||||
// private Constructor can only be called from Builder
|
// private Constructor can only be called from Builder
|
||||||
@ -61,9 +91,9 @@ public class PgpDecryptVerify {
|
|||||||
this.mOutStream = builder.mOutStream;
|
this.mOutStream = builder.mOutStream;
|
||||||
|
|
||||||
this.mProgressDialogUpdater = builder.mProgressDialogUpdater;
|
this.mProgressDialogUpdater = builder.mProgressDialogUpdater;
|
||||||
this.mAssumeSymmetric = builder.mAssumeSymmetric;
|
this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
|
||||||
this.mPassphrase = builder.mPassphrase;
|
this.mPassphrase = builder.mPassphrase;
|
||||||
this.mEnforcedKeyId = builder.mEnforcedKeyId;
|
this.mAllowedKeyIds = builder.mAllowedKeyIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
@ -74,9 +104,9 @@ public class PgpDecryptVerify {
|
|||||||
|
|
||||||
// optional
|
// optional
|
||||||
private ProgressDialogUpdater mProgressDialogUpdater = null;
|
private ProgressDialogUpdater mProgressDialogUpdater = null;
|
||||||
private boolean mAssumeSymmetric = false;
|
private boolean mAllowSymmetricDecryption = true;
|
||||||
private String mPassphrase = "";
|
private String mPassphrase = null;
|
||||||
private long mEnforcedKeyId = 0;
|
private Set<Long> mAllowedKeyIds = null;
|
||||||
|
|
||||||
public Builder(Context context, InputData data, OutputStream outStream) {
|
public Builder(Context context, InputData data, OutputStream outStream) {
|
||||||
this.mContext = context;
|
this.mContext = context;
|
||||||
@ -89,8 +119,8 @@ public class PgpDecryptVerify {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder assumeSymmetric(boolean assumeSymmetric) {
|
public Builder allowSymmetricDecryption(boolean allowSymmetricDecryption) {
|
||||||
this.mAssumeSymmetric = assumeSymmetric;
|
this.mAllowSymmetricDecryption = allowSymmetricDecryption;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,14 +130,14 @@ public class PgpDecryptVerify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow this key id alone for decryption.
|
* Allow these key ids alone for decryption.
|
||||||
* This means only ciphertexts encrypted for this private key can be decrypted.
|
* This means only ciphertexts encrypted for one of these private key can be decrypted.
|
||||||
*
|
*
|
||||||
* @param enforcedKeyId
|
* @param allowedKeyIds
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Builder enforcedKeyId(long enforcedKeyId) {
|
public Builder allowedKeyIds(Set<Long> allowedKeyIds) {
|
||||||
this.mEnforcedKeyId = enforcedKeyId;
|
this.mAllowedKeyIds = allowedKeyIds;
|
||||||
return this;
|
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
|
* Decrypts and/or verifies data based on parameters of class
|
||||||
*
|
*
|
||||||
@ -221,25 +222,82 @@ public class PgpDecryptVerify {
|
|||||||
|
|
||||||
currentProgress += 5;
|
currentProgress += 5;
|
||||||
|
|
||||||
// TODO: currently we always only look at the first known key or symmetric encryption,
|
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
|
||||||
// there might be more...
|
PGPPBEEncryptedData encryptedDataSymmetric = null;
|
||||||
if (mAssumeSymmetric) {
|
PGPSecretKey secretKey = null;
|
||||||
PGPPBEEncryptedData pbe = null;
|
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
boolean symmetricPacketFound = false;
|
||||||
// find secret key
|
// find secret key
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Object obj = it.next();
|
Object obj = it.next();
|
||||||
if (obj instanceof PGPPBEEncryptedData) {
|
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||||
pbe = (PGPPBEEncryptedData) obj;
|
updateProgress(R.string.progress_finding_key, currentProgress, 100);
|
||||||
break;
|
|
||||||
|
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) {
|
// allow only a specific key for decryption?
|
||||||
throw new PgpGeneralException(
|
if (mAllowedKeyIds != null) {
|
||||||
mContext.getString(R.string.error_no_symmetric_encryption_packet));
|
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);
|
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
|
||||||
|
|
||||||
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
PGPDigestCalculatorProvider digestCalcProvider = new JcaPGPDigestCalculatorProviderBuilder()
|
||||||
@ -248,64 +306,11 @@ public class PgpDecryptVerify {
|
|||||||
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||||
mPassphrase.toCharArray());
|
mPassphrase.toCharArray());
|
||||||
|
|
||||||
clear = pbe.getDataStream(decryptorFactory);
|
clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
|
||||||
|
|
||||||
encryptedData = pbe;
|
encryptedData = encryptedDataSymmetric;
|
||||||
currentProgress += 5;
|
currentProgress += 5;
|
||||||
} else {
|
} 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) {
|
if (secretKey == null) {
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
|
throw new PgpGeneralException(mContext.getString(R.string.error_no_secret_key_found));
|
||||||
}
|
}
|
||||||
@ -331,9 +336,9 @@ public class PgpDecryptVerify {
|
|||||||
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
|
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey);
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(privateKey);
|
||||||
|
|
||||||
clear = pbe.getDataStream(decryptorFactory);
|
clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
|
||||||
|
|
||||||
encryptedData = pbe;
|
encryptedData = encryptedDataAsymmetric;
|
||||||
currentProgress += 5;
|
currentProgress += 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,7 +368,7 @@ public class PgpDecryptVerify {
|
|||||||
for (int i = 0; i < sigList.size(); ++i) {
|
for (int i = 0; i < sigList.size(); ++i) {
|
||||||
signature = sigList.get(i);
|
signature = sigList.get(i);
|
||||||
signatureKey = ProviderHelper
|
signatureKey = ProviderHelper
|
||||||
.getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
|
.getPGPPublicKeyRing(mContext, signature.getKeyID()).getPublicKey();
|
||||||
if (signatureKeyId == 0) {
|
if (signatureKeyId == 0) {
|
||||||
signatureKeyId = signature.getKeyID();
|
signatureKeyId = signature.getKeyID();
|
||||||
}
|
}
|
||||||
@ -373,10 +378,10 @@ public class PgpDecryptVerify {
|
|||||||
signatureIndex = i;
|
signatureIndex = i;
|
||||||
signatureKeyId = signature.getKeyID();
|
signatureKeyId = signature.getKeyID();
|
||||||
String userId = null;
|
String userId = null;
|
||||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(
|
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(
|
||||||
mContext, signatureKeyId);
|
mContext, signatureKeyId);
|
||||||
if (signKeyRing != null) {
|
if (signKeyRing != null) {
|
||||||
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
|
userId = PgpKeyHelper.getMainUserId(signKeyRing.getPublicKey());
|
||||||
}
|
}
|
||||||
signatureResult.setUserId(userId);
|
signatureResult.setUserId(userId);
|
||||||
break;
|
break;
|
||||||
@ -388,7 +393,7 @@ public class PgpDecryptVerify {
|
|||||||
if (signature != null) {
|
if (signature != null) {
|
||||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||||
new JcaPGPContentVerifierBuilderProvider()
|
new JcaPGPContentVerifierBuilderProvider()
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
|
|
||||||
signature.init(contentVerifierBuilderProvider, signatureKey);
|
signature.init(contentVerifierBuilderProvider, signatureKey);
|
||||||
} else {
|
} else {
|
||||||
@ -546,25 +551,27 @@ public class PgpDecryptVerify {
|
|||||||
long signatureKeyId = 0;
|
long signatureKeyId = 0;
|
||||||
PGPPublicKey signatureKey = null;
|
PGPPublicKey signatureKey = null;
|
||||||
for (int i = 0; i < sigList.size(); ++i) {
|
for (int i = 0; i < sigList.size(); ++i) {
|
||||||
|
|
||||||
signature = sigList.get(i);
|
signature = sigList.get(i);
|
||||||
signatureKey = ProviderHelper.getPGPPublicKeyByKeyId(mContext, signature.getKeyID());
|
signatureKeyId = signature.getKeyID();
|
||||||
if (signatureKeyId == 0) {
|
|
||||||
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) {
|
// this one can't fail now (yay database constraints)
|
||||||
signature = null;
|
signatureKey = ProviderHelper.getPGPPublicKeyRing(mContext, (Long) data.get(KeyRings.MASTER_KEY_ID)).getPublicKey();
|
||||||
} else {
|
signatureResult.setUserId((String) data.get(KeyRings.USER_ID));
|
||||||
signatureKeyId = signature.getKeyID();
|
|
||||||
String userId = null;
|
break;
|
||||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(mContext,
|
|
||||||
signatureKeyId);
|
|
||||||
if (signKeyRing != null) {
|
|
||||||
userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signKeyRing));
|
|
||||||
}
|
|
||||||
signatureResult.setUserId(userId);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureResult.setKeyId(signatureKeyId);
|
signatureResult.setKeyId(signatureKeyId);
|
||||||
@ -621,11 +628,11 @@ public class PgpDecryptVerify {
|
|||||||
long signatureKeyId = signature.getKeyID();
|
long signatureKeyId = signature.getKeyID();
|
||||||
boolean validKeyBinding = false;
|
boolean validKeyBinding = false;
|
||||||
|
|
||||||
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingByKeyId(context,
|
PGPPublicKeyRing signKeyRing = ProviderHelper.getPGPPublicKeyRingWithKeyId(context,
|
||||||
signatureKeyId);
|
signatureKeyId);
|
||||||
PGPPublicKey mKey = null;
|
PGPPublicKey mKey = null;
|
||||||
if (signKeyRing != null) {
|
if (signKeyRing != null) {
|
||||||
mKey = PgpKeyHelper.getMasterKey(signKeyRing);
|
mKey = signKeyRing.getPublicKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signature.getKeyID() != mKey.getKeyID()) {
|
if (signature.getKeyID() != mKey.getKeyID()) {
|
||||||
@ -685,7 +692,8 @@ public class PgpDecryptVerify {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts,
|
private static boolean verifyPrimaryKeyBinding(PGPSignatureSubpacketVector pkts,
|
||||||
PGPPublicKey masterPublicKey, PGPPublicKey signingPublicKey) {
|
PGPPublicKey masterPublicKey,
|
||||||
|
PGPPublicKey signingPublicKey) {
|
||||||
boolean validPrimaryKeyBinding = false;
|
boolean validPrimaryKeyBinding = false;
|
||||||
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
JcaPGPContentVerifierBuilderProvider contentVerifierBuilderProvider =
|
||||||
new JcaPGPContentVerifierBuilderProvider()
|
new JcaPGPContentVerifierBuilderProvider()
|
||||||
|
@ -19,27 +19,33 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||||
|
|
||||||
public class PgpDecryptVerifyResult implements Parcelable {
|
public class PgpDecryptVerifyResult implements Parcelable {
|
||||||
boolean mSymmetricPassphraseNeeded;
|
public static final int SUCCESS = 1;
|
||||||
boolean mKeyPassphraseNeeded;
|
public static final int KEY_PASSHRASE_NEEDED = 2;
|
||||||
|
public static final int SYMMETRIC_PASSHRASE_NEEDED = 3;
|
||||||
|
|
||||||
|
int mStatus;
|
||||||
|
long mKeyIdPassphraseNeeded;
|
||||||
|
|
||||||
OpenPgpSignatureResult mSignatureResult;
|
OpenPgpSignatureResult mSignatureResult;
|
||||||
|
|
||||||
public boolean isSymmetricPassphraseNeeded() {
|
public int getStatus() {
|
||||||
return mSymmetricPassphraseNeeded;
|
return mStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSymmetricPassphraseNeeded(boolean symmetricPassphraseNeeded) {
|
public void setStatus(int mStatus) {
|
||||||
this.mSymmetricPassphraseNeeded = symmetricPassphraseNeeded;
|
this.mStatus = mStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isKeyPassphraseNeeded() {
|
public long getKeyIdPassphraseNeeded() {
|
||||||
return mKeyPassphraseNeeded;
|
return mKeyIdPassphraseNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKeyPassphraseNeeded(boolean keyPassphraseNeeded) {
|
public void setKeyIdPassphraseNeeded(long mKeyIdPassphraseNeeded) {
|
||||||
this.mKeyPassphraseNeeded = keyPassphraseNeeded;
|
this.mKeyIdPassphraseNeeded = mKeyIdPassphraseNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public OpenPgpSignatureResult getSignatureResult() {
|
public OpenPgpSignatureResult getSignatureResult() {
|
||||||
@ -55,8 +61,8 @@ public class PgpDecryptVerifyResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) {
|
public PgpDecryptVerifyResult(PgpDecryptVerifyResult b) {
|
||||||
this.mSymmetricPassphraseNeeded = b.mSymmetricPassphraseNeeded;
|
this.mStatus = b.mStatus;
|
||||||
this.mKeyPassphraseNeeded = b.mKeyPassphraseNeeded;
|
this.mKeyIdPassphraseNeeded = b.mKeyIdPassphraseNeeded;
|
||||||
this.mSignatureResult = b.mSignatureResult;
|
this.mSignatureResult = b.mSignatureResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,16 +72,16 @@ public class PgpDecryptVerifyResult implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeByte((byte) (mSymmetricPassphraseNeeded ? 1 : 0));
|
dest.writeInt(mStatus);
|
||||||
dest.writeByte((byte) (mKeyPassphraseNeeded ? 1 : 0));
|
dest.writeLong(mKeyIdPassphraseNeeded);
|
||||||
dest.writeParcelable(mSignatureResult, 0);
|
dest.writeParcelable(mSignatureResult, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() {
|
public static final Creator<PgpDecryptVerifyResult> CREATOR = new Creator<PgpDecryptVerifyResult>() {
|
||||||
public PgpDecryptVerifyResult createFromParcel(final Parcel source) {
|
public PgpDecryptVerifyResult createFromParcel(final Parcel source) {
|
||||||
PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult();
|
PgpDecryptVerifyResult vr = new PgpDecryptVerifyResult();
|
||||||
vr.mSymmetricPassphraseNeeded = source.readByte() == 1;
|
vr.mStatus = source.readInt();
|
||||||
vr.mKeyPassphraseNeeded = source.readByte() == 1;
|
vr.mKeyIdPassphraseNeeded = source.readLong();
|
||||||
vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
vr.mSignatureResult = source.readParcelable(OpenPgpSignatureResult.class.getClassLoader());
|
||||||
return vr;
|
return vr;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,14 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
@ -43,10 +50,10 @@ public class PgpHelper {
|
|||||||
public static final Pattern PGP_MESSAGE = Pattern.compile(
|
public static final Pattern PGP_MESSAGE = Pattern.compile(
|
||||||
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
|
".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*", Pattern.DOTALL);
|
||||||
|
|
||||||
public static final Pattern PGP_SIGNED_MESSAGE = Pattern
|
public static final Pattern PGP_CLEARTEXT_SIGNATURE = Pattern
|
||||||
.compile(
|
.compile(".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----" +
|
||||||
".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
|
"BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
|
||||||
Pattern.DOTALL);
|
Pattern.DOTALL);
|
||||||
|
|
||||||
public static final Pattern PGP_PUBLIC_KEY = Pattern.compile(
|
public static final Pattern PGP_PUBLIC_KEY = Pattern.compile(
|
||||||
".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*",
|
".*?(-----BEGIN PGP PUBLIC KEY BLOCK-----.*?-----END PGP PUBLIC KEY BLOCK-----).*",
|
||||||
@ -96,7 +103,7 @@ public class PgpHelper {
|
|||||||
if (obj instanceof PGPPublicKeyEncryptedData) {
|
if (obj instanceof PGPPublicKeyEncryptedData) {
|
||||||
gotAsymmetricEncryption = true;
|
gotAsymmetricEncryption = true;
|
||||||
PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj;
|
PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) obj;
|
||||||
secretKey = ProviderHelper.getPGPSecretKeyByKeyId(context, pbe.getKeyID());
|
secretKey = ProviderHelper.getPGPSecretKeyRing(context, pbe.getKeyID()).getSecretKey();
|
||||||
if (secretKey != null) {
|
if (secretKey != null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,14 @@ package org.sufficientlysecure.keychain.pgp;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
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.spongycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
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.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
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.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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -158,60 +168,69 @@ public class PgpImportExport {
|
|||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bundle exportKeyRings(ArrayList<Long> keyRingRowIds, int keyType,
|
public Bundle exportKeyRings(ArrayList<Long> publicKeyRingMasterIds,
|
||||||
|
ArrayList<Long> secretKeyRingMasterIds,
|
||||||
OutputStream outStream) throws PgpGeneralException,
|
OutputStream outStream) throws PgpGeneralException,
|
||||||
PGPException, IOException {
|
PGPException, IOException {
|
||||||
Bundle returnData = new Bundle();
|
Bundle returnData = new Bundle();
|
||||||
|
|
||||||
int rowIdsSize = keyRingRowIds.size();
|
int masterKeyIdsSize = publicKeyRingMasterIds.size() + secretKeyRingMasterIds.size();
|
||||||
|
int progress = 0;
|
||||||
|
|
||||||
updateProgress(
|
updateProgress(
|
||||||
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
|
mContext.getResources().getQuantityString(R.plurals.progress_exporting_key,
|
||||||
rowIdsSize), 0, 100);
|
masterKeyIdsSize), 0, 100);
|
||||||
|
|
||||||
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||||
throw new PgpGeneralException(
|
throw new PgpGeneralException(
|
||||||
mContext.getString(R.string.error_external_storage_not_ready));
|
mContext.getString(R.string.error_external_storage_not_ready));
|
||||||
}
|
}
|
||||||
// For each row id
|
// For each public masterKey id
|
||||||
for (int i = 0; i < rowIdsSize; ++i) {
|
for (long pubKeyMasterId : publicKeyRingMasterIds) {
|
||||||
|
progress++;
|
||||||
// Create an output stream
|
// Create an output stream
|
||||||
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
|
ArmoredOutputStream arOutStream = new ArmoredOutputStream(outStream);
|
||||||
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
arOutStream.setHeader("Version", PgpHelper.getFullVersion(mContext));
|
||||||
|
|
||||||
// If the keyType is secret get the PGPSecretKeyRing
|
updateProgress(progress * 100 / masterKeyIdsSize, 100);
|
||||||
// based on the row id and encode it to the output
|
PGPPublicKeyRing publicKeyRing =
|
||||||
if (keyType == Id.type.secret_key) {
|
ProviderHelper.getPGPPublicKeyRing(mContext, pubKeyMasterId);
|
||||||
updateProgress(i * 100 / rowIdsSize / 2, 100);
|
|
||||||
PGPSecretKeyRing secretKeyRing =
|
|
||||||
ProviderHelper.getPGPSecretKeyRingByRowId(mContext, keyRingRowIds.get(i));
|
|
||||||
|
|
||||||
if (secretKeyRing != null) {
|
if (publicKeyRing != null) {
|
||||||
secretKeyRing.encode(arOutStream);
|
publicKeyRing.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) {
|
if (mKeychainServiceListener.hasServiceStopped()) {
|
||||||
publicKeyRing.encode(arOutStream);
|
arOutStream.close();
|
||||||
}
|
return null;
|
||||||
|
|
||||||
if (mKeychainServiceListener.hasServiceStopped()) {
|
|
||||||
arOutStream.close();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
arOutStream.close();
|
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);
|
updateProgress(R.string.progress_done, 100, 100);
|
||||||
|
|
||||||
@ -241,7 +260,6 @@ public class PgpImportExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (save) {
|
if (save) {
|
||||||
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
|
||||||
// TODO: preserve certifications
|
// TODO: preserve certifications
|
||||||
// (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?)
|
// (http://osdir.com/ml/encryption.bouncy-castle.devel/2007-01/msg00054.html ?)
|
||||||
PGPPublicKeyRing newPubRing = null;
|
PGPPublicKeyRing newPubRing = null;
|
||||||
@ -256,6 +274,7 @@ public class PgpImportExport {
|
|||||||
if (newPubRing != null) {
|
if (newPubRing != null) {
|
||||||
ProviderHelper.saveKeyRing(mContext, newPubRing);
|
ProviderHelper.saveKeyRing(mContext, newPubRing);
|
||||||
}
|
}
|
||||||
|
ProviderHelper.saveKeyRing(mContext, secretKeyRing);
|
||||||
// TODO: remove status returns, use exceptions!
|
// TODO: remove status returns, use exceptions!
|
||||||
status = Id.return_value.ok;
|
status = Id.return_value.ok;
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,18 @@
|
|||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import android.content.Context;
|
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.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.spongycastle.util.encoders.Hex;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -43,34 +60,6 @@ public class PgpKeyHelper {
|
|||||||
return key.getPublicKey().getCreationTime();
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) {
|
public static PGPSecretKey getKeyNum(PGPSecretKeyRing keyRing, long num) {
|
||||||
long cnt = 0;
|
long cnt = 0;
|
||||||
@ -202,9 +191,8 @@ public class PgpKeyHelper {
|
|||||||
Calendar calendar = GregorianCalendar.getInstance();
|
Calendar calendar = GregorianCalendar.getInstance();
|
||||||
calendar.setTime(creationDate);
|
calendar.setTime(creationDate);
|
||||||
calendar.add(Calendar.DATE, key.getValidDays());
|
calendar.add(Calendar.DATE, key.getValidDays());
|
||||||
Date expiryDate = calendar.getTime();
|
|
||||||
|
|
||||||
return expiryDate;
|
return calendar.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Date getExpiryDate(PGPSecretKey key) {
|
public static Date getExpiryDate(PGPSecretKey key) {
|
||||||
@ -212,8 +200,7 @@ public class PgpKeyHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) {
|
public static PGPPublicKey getEncryptPublicKey(Context context, long masterKeyId) {
|
||||||
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(context,
|
PGPPublicKeyRing keyRing = ProviderHelper.getPGPPublicKeyRing(context, masterKeyId);
|
||||||
masterKeyId);
|
|
||||||
if (keyRing == null) {
|
if (keyRing == null) {
|
||||||
Log.e(Constants.TAG, "keyRing is null!");
|
Log.e(Constants.TAG, "keyRing is null!");
|
||||||
return null;
|
return null;
|
||||||
@ -227,8 +214,7 @@ public class PgpKeyHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static PGPSecretKey getCertificationKey(Context context, long masterKeyId) {
|
public static PGPSecretKey getCertificationKey(Context context, long masterKeyId) {
|
||||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context,
|
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
|
||||||
masterKeyId);
|
|
||||||
if (keyRing == null) {
|
if (keyRing == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -240,8 +226,7 @@ public class PgpKeyHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static PGPSecretKey getSigningKey(Context context, long masterKeyId) {
|
public static PGPSecretKey getSigningKey(Context context, long masterKeyId) {
|
||||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(context,
|
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(context, masterKeyId);
|
||||||
masterKeyId);
|
|
||||||
if (keyRing == null) {
|
if (keyRing == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -284,6 +269,33 @@ public class PgpKeyHelper {
|
|||||||
return userId;
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
public static boolean isEncryptionKey(PGPPublicKey key) {
|
public static boolean isEncryptionKey(PGPPublicKey key) {
|
||||||
if (!key.isEncryptionKey()) {
|
if (!key.isEncryptionKey()) {
|
||||||
@ -390,6 +402,36 @@ public class PgpKeyHelper {
|
|||||||
return false;
|
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) {
|
public static boolean isCertificationKey(PGPSecretKey key) {
|
||||||
return isCertificationKey(key.getPublicKey());
|
return isCertificationKey(key.getPublicKey());
|
||||||
}
|
}
|
||||||
@ -403,7 +445,7 @@ public class PgpKeyHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getAlgorithmInfo(int algorithm, int keySize) {
|
public static String getAlgorithmInfo(int algorithm, int keySize) {
|
||||||
String algorithmStr = null;
|
String algorithmStr;
|
||||||
|
|
||||||
switch (algorithm) {
|
switch (algorithm) {
|
||||||
case PGPPublicKey.RSA_ENCRYPT:
|
case PGPPublicKey.RSA_ENCRYPT:
|
||||||
@ -434,21 +476,6 @@ public class PgpKeyHelper {
|
|||||||
return algorithmStr;
|
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)
|
* Converts fingerprint to hex (optional: with whitespaces after 4 characters)
|
||||||
* <p/>
|
* <p/>
|
||||||
@ -456,14 +483,10 @@ public class PgpKeyHelper {
|
|||||||
* better differentiate between numbers and letters when letters are lowercase.
|
* better differentiate between numbers and letters when letters are lowercase.
|
||||||
*
|
*
|
||||||
* @param fingerprint
|
* @param fingerprint
|
||||||
* @param split split into 4 character chunks
|
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String convertFingerprintToHex(byte[] fingerprint, boolean split) {
|
public static String convertFingerprintToHex(byte[] fingerprint) {
|
||||||
String hexString = Hex.toHexString(fingerprint);
|
String hexString = Hex.toHexString(fingerprint);
|
||||||
if (split) {
|
|
||||||
hexString = hexString.replaceAll("(.{4})(?!$)", "$1 ");
|
|
||||||
}
|
|
||||||
|
|
||||||
return hexString;
|
return hexString;
|
||||||
}
|
}
|
||||||
@ -479,9 +502,18 @@ public class PgpKeyHelper {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static String convertKeyIdToHex(long keyId) {
|
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);
|
return "0x" + convertKeyIdToHex32bit(keyId >> 32) + convertKeyIdToHex32bit(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String convertKeyIdToHexShort(long keyId) {
|
||||||
|
return "0x" + convertKeyIdToHex32bit(keyId);
|
||||||
|
}
|
||||||
|
|
||||||
private static String convertKeyIdToHex32bit(long keyId) {
|
private static String convertKeyIdToHex32bit(long keyId) {
|
||||||
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
|
String hexString = Long.toHexString(keyId & 0xffffffffL).toLowerCase(Locale.US);
|
||||||
while (hexString.length() < 8) {
|
while (hexString.length() < 8) {
|
||||||
@ -490,17 +522,90 @@ public class PgpKeyHelper {
|
|||||||
return hexString;
|
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
|
* @param bytes
|
||||||
* @return
|
* @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) {
|
private static int[] getRgbForData(byte[] bytes) throws NoSuchAlgorithmException, DigestException {
|
||||||
int len = hexString.length();
|
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||||
String s2 = hexString.substring(len - 8);
|
|
||||||
String s1 = hexString.substring(0, len - 8);
|
md.update(bytes);
|
||||||
return (Long.parseLong(s1, 16) << 32) | Long.parseLong(s2, 16);
|
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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
import org.spongycastle.bcpg.CompressionAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.HashAlgorithmTags;
|
import org.spongycastle.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
|
||||||
import org.spongycastle.bcpg.sig.KeyFlags;
|
import org.spongycastle.bcpg.sig.KeyFlags;
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
|
||||||
import org.spongycastle.jce.spec.ElGamalParameterSpec;
|
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.PGPUtil;
|
||||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
|
import org.spongycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||||
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||||
import org.spongycastle.openpgp.operator.PGPDigestCalculator;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralMsgIdException;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.util.IterableIterator;
|
import org.sufficientlysecure.keychain.util.IterableIterator;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
|
||||||
import org.sufficientlysecure.keychain.util.Primes;
|
import org.sufficientlysecure.keychain.util.Primes;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
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.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
@ -51,8 +72,16 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
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 {
|
public class PgpKeyOperation {
|
||||||
private Context mContext;
|
|
||||||
private ProgressDialogUpdater mProgress;
|
private ProgressDialogUpdater mProgress;
|
||||||
|
|
||||||
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
private static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
|
||||||
@ -65,19 +94,18 @@ public class PgpKeyOperation {
|
|||||||
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
|
CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2,
|
||||||
CompressionAlgorithmTags.ZIP};
|
CompressionAlgorithmTags.ZIP};
|
||||||
|
|
||||||
public PgpKeyOperation(Context context, ProgressDialogUpdater progress) {
|
public PgpKeyOperation(ProgressDialogUpdater progress) {
|
||||||
super();
|
super();
|
||||||
this.mContext = context;
|
|
||||||
this.mProgress = progress;
|
this.mProgress = progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateProgress(int message, int current, int total) {
|
void updateProgress(int message, int current, int total) {
|
||||||
if (mProgress != null) {
|
if (mProgress != null) {
|
||||||
mProgress.setProgress(message, current, total);
|
mProgress.setProgress(message, current, total);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateProgress(int current, int total) {
|
void updateProgress(int current, int total) {
|
||||||
if (mProgress != null) {
|
if (mProgress != null) {
|
||||||
mProgress.setProgress(current, total);
|
mProgress.setProgress(current, total);
|
||||||
}
|
}
|
||||||
@ -90,11 +118,11 @@ public class PgpKeyOperation {
|
|||||||
* @param keySize
|
* @param keySize
|
||||||
* @param passphrase
|
* @param passphrase
|
||||||
* @param isMasterKey
|
* @param isMasterKey
|
||||||
* @return
|
* @return A newly created PGPSecretKey
|
||||||
* @throws NoSuchAlgorithmException
|
* @throws NoSuchAlgorithmException
|
||||||
* @throws PGPException
|
* @throws PGPException
|
||||||
* @throws NoSuchProviderException
|
* @throws NoSuchProviderException
|
||||||
* @throws PgpGeneralException
|
* @throws PgpGeneralMsgIdException
|
||||||
* @throws InvalidAlgorithmParameterException
|
* @throws InvalidAlgorithmParameterException
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -102,18 +130,18 @@ public class PgpKeyOperation {
|
|||||||
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
|
public PGPSecretKey createKey(int algorithmChoice, int keySize, String passphrase,
|
||||||
boolean isMasterKey)
|
boolean isMasterKey)
|
||||||
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
|
||||||
PgpGeneralException, InvalidAlgorithmParameterException {
|
PgpGeneralMsgIdException, InvalidAlgorithmParameterException {
|
||||||
|
|
||||||
if (keySize < 512) {
|
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) {
|
if (passphrase == null) {
|
||||||
passphrase = "";
|
passphrase = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
int algorithm = 0;
|
int algorithm;
|
||||||
KeyPairGenerator keyGen = null;
|
KeyPairGenerator keyGen;
|
||||||
|
|
||||||
switch (algorithmChoice) {
|
switch (algorithmChoice) {
|
||||||
case Id.choice.algorithm.dsa: {
|
case Id.choice.algorithm.dsa: {
|
||||||
@ -125,8 +153,7 @@ public class PgpKeyOperation {
|
|||||||
|
|
||||||
case Id.choice.algorithm.elgamal: {
|
case Id.choice.algorithm.elgamal: {
|
||||||
if (isMasterKey) {
|
if (isMasterKey) {
|
||||||
throw new PgpGeneralException(
|
throw new PgpGeneralMsgIdException(R.string.error_master_key_must_not_be_el_gamal);
|
||||||
mContext.getString(R.string.error_master_key_must_not_be_el_gamal));
|
|
||||||
}
|
}
|
||||||
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
keyGen = KeyPairGenerator.getInstance("ElGamal", Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
BigInteger p = Primes.getBestPrime(keySize);
|
BigInteger p = Primes.getBestPrime(keySize);
|
||||||
@ -148,8 +175,7 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
throw new PgpGeneralException(
|
throw new PgpGeneralMsgIdException(R.string.error_unknown_algorithm_choice);
|
||||||
mContext.getString(R.string.error_unknown_algorithm_choice));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,194 +191,115 @@ public class PgpKeyOperation {
|
|||||||
PGPEncryptedData.CAST5, sha1Calc)
|
PGPEncryptedData.CAST5, sha1Calc)
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||||
|
|
||||||
PGPSecretKey secKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
|
return new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
|
||||||
sha1Calc, isMasterKey, keyEncryptor);
|
sha1Calc, isMasterKey, keyEncryptor);
|
||||||
|
|
||||||
return secKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassPhrase,
|
public PGPSecretKeyRing changeSecretKeyPassphrase(PGPSecretKeyRing keyRing, String oldPassphrase,
|
||||||
String newPassPhrase) throws IOException, PGPException,
|
String newPassphrase)
|
||||||
NoSuchProviderException {
|
throws IOException, PGPException, NoSuchProviderException {
|
||||||
|
|
||||||
updateProgress(R.string.progress_building_key, 0, 100);
|
updateProgress(R.string.progress_building_key, 0, 100);
|
||||||
if (oldPassPhrase == null) {
|
if (oldPassphrase == null) {
|
||||||
oldPassPhrase = "";
|
oldPassphrase = "";
|
||||||
}
|
}
|
||||||
if (newPassPhrase == null) {
|
if (newPassphrase == null) {
|
||||||
newPassPhrase = "";
|
newPassphrase = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword(
|
PGPSecretKeyRing newKeyRing = PGPSecretKeyRing.copyWithNewPassword(
|
||||||
keyRing,
|
keyRing,
|
||||||
new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder()
|
new JcePBESecretKeyDecryptorBuilder(new JcaPGPDigestCalculatorProviderBuilder()
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build()).setProvider(
|
.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()
|
new JcePBESecretKeyEncryptorBuilder(keyRing.getSecretKey()
|
||||||
.getKeyEncryptionAlgorithm()).build(newPassPhrase.toCharArray()));
|
.getKeyEncryptionAlgorithm()).build(newPassphrase.toCharArray()));
|
||||||
|
|
||||||
updateProgress(R.string.progress_saving_key_ring, 50, 100);
|
return newKeyRing;
|
||||||
|
|
||||||
ProviderHelper.saveKeyRing(mContext, newKeyRing);
|
|
||||||
|
|
||||||
updateProgress(R.string.progress_done, 100, 100);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildSecretKey(ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
private Pair<PGPSecretKeyRing, PGPPublicKeyRing> buildNewSecretKey(
|
||||||
ArrayList<Integer> keysUsages, ArrayList<GregorianCalendar> keysExpiryDates,
|
ArrayList<String> userIds, ArrayList<PGPSecretKey> keys,
|
||||||
PGPPublicKey oldPublicKey, String oldPassPhrase,
|
ArrayList<GregorianCalendar> keysExpiryDates,
|
||||||
String newPassPhrase) throws PgpGeneralException, NoSuchProviderException,
|
ArrayList<Integer> keysUsages,
|
||||||
PGPException, NoSuchAlgorithmException, SignatureException, IOException {
|
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) {
|
// this removes all userIds and certifications previously attached to the masterPublicKey
|
||||||
oldPassPhrase = "";
|
PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
||||||
}
|
|
||||||
if (newPassPhrase == null) {
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
newPassPhrase = "";
|
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
|
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
PGPKeyRingGenerator keyGen;
|
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
PGPPublicKey masterPublicKey; {
|
|
||||||
|
|
||||||
String mainUserId = userIds.get(0);
|
hashedPacketsGen.setKeyFlags(true, usageId);
|
||||||
|
|
||||||
// prepare the master key pair
|
hashedPacketsGen.setPreferredSymmetricAlgorithms(true, PREFERRED_SYMMETRIC_ALGORITHMS);
|
||||||
PGPKeyPair masterKeyPair; {
|
hashedPacketsGen.setPreferredHashAlgorithms(true, PREFERRED_HASH_ALGORITHMS);
|
||||||
|
hashedPacketsGen.setPreferredCompressionAlgorithms(true, PREFERRED_COMPRESSION_ALGORITHMS);
|
||||||
|
|
||||||
PGPSecretKey masterKey = keys.get(0);
|
if (keysExpiryDates.get(0) != null) {
|
||||||
|
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||||
// this removes all userIds and certifications previously attached to the masterPublicKey
|
creationDate.setTime(masterPublicKey.getCreationTime());
|
||||||
PGPPublicKey tmpKey = masterKey.getPublicKey();
|
GregorianCalendar expiryDate = keysExpiryDates.get(0);
|
||||||
masterPublicKey = new PGPPublicKey(tmpKey.getAlgorithm(),
|
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
||||||
tmpKey.getKey(new BouncyCastleProvider()), tmpKey.getCreationTime());
|
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||||
|
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
|
||||||
// already done by code above:
|
(creationDate.getTimeInMillis() / 86400000);
|
||||||
// PGPPublicKey masterPublicKey = masterKey.getPublicKey();
|
if (numDays <= 0) {
|
||||||
// // Somehow, the PGPPublicKey already has an empty certification attached to it when the
|
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||||
// // 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);
|
|
||||||
}
|
}
|
||||||
|
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||||
PGPSignatureSubpacketGenerator hashedPacketsGen;
|
} else {
|
||||||
PGPSignatureSubpacketGenerator unhashedPacketsGen; {
|
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
||||||
|
// do this explicitly, although since we're rebuilding,
|
||||||
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
// this happens anyway
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
updateProgress(R.string.progress_adding_sub_keys, 40, 100);
|
||||||
|
|
||||||
for (int i = 1; i < keys.size(); ++i) {
|
for (int i = 1; i < keys.size(); ++i) {
|
||||||
@ -361,27 +308,21 @@ public class PgpKeyOperation {
|
|||||||
PGPSecretKey subKey = keys.get(i);
|
PGPSecretKey subKey = keys.get(i);
|
||||||
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
PGPPublicKey subPublicKey = subKey.getPublicKey();
|
||||||
|
|
||||||
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
|
PBESecretKeyDecryptor keyDecryptor2 = new JcePBESecretKeyDecryptorBuilder()
|
||||||
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
|
||||||
oldPassPhrase.toCharArray());
|
oldPassphrase.toCharArray());
|
||||||
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor);
|
PGPPrivateKey subPrivateKey = subKey.extractPrivateKey(keyDecryptor2);
|
||||||
|
|
||||||
// TODO: now used without algorithm and creation time?! (APG 1)
|
// TODO: now used without algorithm and creation time?! (APG 1)
|
||||||
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
|
PGPKeyPair subKeyPair = new PGPKeyPair(subPublicKey, subPrivateKey);
|
||||||
|
|
||||||
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
hashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
|
|
||||||
int keyFlags = 0;
|
usageId = keysUsages.get(i);
|
||||||
|
canSign = (usageId & KeyFlags.SIGN_DATA) > 0; //todo - separate function for this
|
||||||
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);
|
|
||||||
if (canSign) {
|
if (canSign) {
|
||||||
Date todayDate = new Date(); //both sig times the same
|
Date todayDate = new Date(); //both sig times the same
|
||||||
keyFlags |= KeyFlags.SIGN_DATA;
|
|
||||||
// cross-certify signing keys
|
// cross-certify signing keys
|
||||||
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
|
hashedPacketsGen.setSignatureCreationTime(false, todayDate); //set outer creation time
|
||||||
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
|
||||||
@ -396,10 +337,7 @@ public class PgpKeyOperation {
|
|||||||
subPublicKey);
|
subPublicKey);
|
||||||
unhashedPacketsGen.setEmbeddedSignature(false, certification);
|
unhashedPacketsGen.setEmbeddedSignature(false, certification);
|
||||||
}
|
}
|
||||||
if (canEncrypt) {
|
hashedPacketsGen.setKeyFlags(false, usageId);
|
||||||
keyFlags |= KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE;
|
|
||||||
}
|
|
||||||
hashedPacketsGen.setKeyFlags(false, keyFlags);
|
|
||||||
|
|
||||||
if (keysExpiryDates.get(i) != null) {
|
if (keysExpiryDates.get(i) != null) {
|
||||||
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
GregorianCalendar creationDate = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
|
||||||
@ -407,17 +345,16 @@ public class PgpKeyOperation {
|
|||||||
GregorianCalendar expiryDate = keysExpiryDates.get(i);
|
GregorianCalendar expiryDate = keysExpiryDates.get(i);
|
||||||
//note that the below, (a/c) - (b/c) is *not* the same as (a - b) /c
|
//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!
|
//here we purposefully ignore partial days in each date - long type has no fractional part!
|
||||||
long numDays =
|
long numDays = (expiryDate.getTimeInMillis() / 86400000) -
|
||||||
(expiryDate.getTimeInMillis() / 86400000) - (creationDate.getTimeInMillis() / 86400000);
|
(creationDate.getTimeInMillis() / 86400000);
|
||||||
if (numDays <= 0) {
|
if (numDays <= 0) {
|
||||||
throw new PgpGeneralException
|
throw new PgpGeneralMsgIdException(R.string.error_expiry_must_come_after_creation);
|
||||||
(mContext.getString(R.string.error_expiry_must_come_after_creation));
|
|
||||||
}
|
}
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
hashedPacketsGen.setKeyExpirationTime(false, numDays * 86400);
|
||||||
} else {
|
} else {
|
||||||
//do this explicitly, although since we're rebuilding,
|
|
||||||
hashedPacketsGen.setKeyExpirationTime(false, 0);
|
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());
|
keyGen.addSubKey(subKeyPair, hashedPacketsGen.generate(), unhashedPacketsGen.generate());
|
||||||
@ -426,102 +363,407 @@ public class PgpKeyOperation {
|
|||||||
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
|
PGPSecretKeyRing secretKeyRing = keyGen.generateSecretKeyRing();
|
||||||
PGPPublicKeyRing publicKeyRing = keyGen.generatePublicKeyRing();
|
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
|
updateProgress(R.string.progress_building_key, 0, 100);
|
||||||
Log.d(Constants.TAG, " ------- in private key -------");
|
PGPSecretKey masterKey = saveParcel.keys.get(0);
|
||||||
for(String uid : new IterableIterator<String>(secretKeyRing.getPublicKey().getUserIDs())) {
|
|
||||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(secretKeyRing.getPublicKey().getSignaturesForID(uid))) {
|
if (saveParcel.oldPassphrase == null) {
|
||||||
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
|
saveParcel.oldPassphrase = "";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Log.d(Constants.TAG, " ------- in public key -------");
|
if (saveParcel.newPassphrase == null) {
|
||||||
for(String uid : new IterableIterator<String>(publicKeyRing.getPublicKey().getUserIDs())) {
|
saveParcel.newPassphrase = "";
|
||||||
for(PGPSignature sig : new IterableIterator<PGPSignature>(publicKeyRing.getPublicKey().getSignaturesForID(uid))) {
|
|
||||||
Log.d(Constants.TAG, "sig: " + PgpKeyHelper.convertKeyIdToHex(sig.getKeyID()) + " for " + uid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
if (saveParcel.deletedKeys != null) {
|
||||||
ProviderHelper.saveKeyRing(mContext, publicKeyRing);
|
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.
|
* Certify the given pubkeyid with the given masterkeyid.
|
||||||
*
|
*
|
||||||
* @param masterKeyId Certifying key, must be available as secret key
|
* @param certificationKey Certifying key
|
||||||
* @param pubKeyId ID of public key to certify
|
* @param publicKey public key to certify
|
||||||
* @param userIds User IDs to certify, must not be null or empty
|
* @param userIds User IDs to certify, must not be null or empty
|
||||||
* @param passphrase Passphrase of the secret key
|
* @param passphrase Passphrase of the secret key
|
||||||
* @return A keyring with added certifications
|
* @return A keyring with added certifications
|
||||||
*/
|
*/
|
||||||
public PGPPublicKeyRing certifyKey(long masterKeyId, long pubKeyId, List<String> userIds, String passphrase)
|
public PGPPublicKey certifyKey(PGPSecretKey certificationKey, PGPPublicKey publicKey,
|
||||||
throws PgpGeneralException, NoSuchAlgorithmException, NoSuchProviderException,
|
List<String> userIds, String passphrase)
|
||||||
PGPException, SignatureException {
|
throws PgpGeneralMsgIdException, NoSuchAlgorithmException, NoSuchProviderException,
|
||||||
if (passphrase == null) {
|
PGPException, SignatureException {
|
||||||
throw new PgpGeneralException("Unable to obtain passphrase");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
// create a signatureGenerator from the supplied masterKeyId and passphrase
|
||||||
PGPSignatureGenerator signatureGenerator; {
|
PGPSignatureGenerator signatureGenerator; {
|
||||||
|
|
||||||
PGPSecretKey certificationKey = PgpKeyHelper.getCertificationKey(mContext, masterKeyId);
|
if (certificationKey == null) {
|
||||||
if (certificationKey == null) {
|
throw new PgpGeneralMsgIdException(R.string.error_signature_failed);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // supply signatureGenerator with a SubpacketVector
|
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
|
||||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.toCharArray());
|
||||||
PGPSignatureSubpacketVector packetVector = spGen.generate();
|
PGPPrivateKey signaturePrivateKey = certificationKey.extractPrivateKey(keyDecryptor);
|
||||||
signatureGenerator.setHashedSubpackets(packetVector);
|
if (signaturePrivateKey == null) {
|
||||||
|
throw new PgpGeneralMsgIdException(R.string.error_could_not_extract_private_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch public key ring, add the certification and return it
|
// TODO: SHA256 fixed?
|
||||||
PGPPublicKeyRing pubring = ProviderHelper
|
JcaPGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
|
||||||
.getPGPPublicKeyRingByKeyId(mContext, pubKeyId);
|
certificationKey.getPublicKey().getAlgorithm(), PGPUtil.SHA256)
|
||||||
PGPPublicKey signedKey = pubring.getPublicKey(pubKeyId);
|
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.spongycastle.bcpg.ArmoredOutputStream;
|
import org.spongycastle.bcpg.ArmoredOutputStream;
|
||||||
import org.spongycastle.bcpg.BCPGOutputStream;
|
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.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.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.Log;
|
||||||
import org.sufficientlysecure.keychain.util.ProgressDialogUpdater;
|
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.NoSuchAlgorithmException;
|
||||||
import java.security.NoSuchProviderException;
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
@ -50,7 +71,7 @@ public class PgpSignEncrypt {
|
|||||||
private boolean mEnableAsciiArmorOutput;
|
private boolean mEnableAsciiArmorOutput;
|
||||||
private int mCompressionId;
|
private int mCompressionId;
|
||||||
private long[] mEncryptionKeyIds;
|
private long[] mEncryptionKeyIds;
|
||||||
private String mEncryptionPassphrase;
|
private String mSymmetricPassphrase;
|
||||||
private int mSymmetricEncryptionAlgorithm;
|
private int mSymmetricEncryptionAlgorithm;
|
||||||
private long mSignatureKeyId;
|
private long mSignatureKeyId;
|
||||||
private int mSignatureHashAlgorithm;
|
private int mSignatureHashAlgorithm;
|
||||||
@ -67,7 +88,7 @@ public class PgpSignEncrypt {
|
|||||||
this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
|
this.mEnableAsciiArmorOutput = builder.mEnableAsciiArmorOutput;
|
||||||
this.mCompressionId = builder.mCompressionId;
|
this.mCompressionId = builder.mCompressionId;
|
||||||
this.mEncryptionKeyIds = builder.mEncryptionKeyIds;
|
this.mEncryptionKeyIds = builder.mEncryptionKeyIds;
|
||||||
this.mEncryptionPassphrase = builder.mEncryptionPassphrase;
|
this.mSymmetricPassphrase = builder.mSymmetricPassphrase;
|
||||||
this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
|
this.mSymmetricEncryptionAlgorithm = builder.mSymmetricEncryptionAlgorithm;
|
||||||
this.mSignatureKeyId = builder.mSignatureKeyId;
|
this.mSignatureKeyId = builder.mSignatureKeyId;
|
||||||
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
|
this.mSignatureHashAlgorithm = builder.mSignatureHashAlgorithm;
|
||||||
@ -85,8 +106,8 @@ public class PgpSignEncrypt {
|
|||||||
private ProgressDialogUpdater mProgress = null;
|
private ProgressDialogUpdater mProgress = null;
|
||||||
private boolean mEnableAsciiArmorOutput = false;
|
private boolean mEnableAsciiArmorOutput = false;
|
||||||
private int mCompressionId = Id.choice.compression.none;
|
private int mCompressionId = Id.choice.compression.none;
|
||||||
private long[] mEncryptionKeyIds = new long[0];
|
private long[] mEncryptionKeyIds = null;
|
||||||
private String mEncryptionPassphrase = null;
|
private String mSymmetricPassphrase = null;
|
||||||
private int mSymmetricEncryptionAlgorithm = 0;
|
private int mSymmetricEncryptionAlgorithm = 0;
|
||||||
private long mSignatureKeyId = Id.key.none;
|
private long mSignatureKeyId = Id.key.none;
|
||||||
private int mSignatureHashAlgorithm = 0;
|
private int mSignatureHashAlgorithm = 0;
|
||||||
@ -119,8 +140,8 @@ public class PgpSignEncrypt {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder encryptionPassphrase(String encryptionPassphrase) {
|
public Builder symmetricPassphrase(String symmetricPassphrase) {
|
||||||
this.mEncryptionPassphrase = encryptionPassphrase;
|
this.mSymmetricPassphrase = symmetricPassphrase;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +202,8 @@ public class PgpSignEncrypt {
|
|||||||
NoSuchAlgorithmException, SignatureException {
|
NoSuchAlgorithmException, SignatureException {
|
||||||
|
|
||||||
boolean enableSignature = mSignatureKeyId != Id.key.none;
|
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);
|
boolean enableCompression = (enableEncryption && mCompressionId != Id.choice.compression.none);
|
||||||
|
|
||||||
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
Log.d(Constants.TAG, "enableSignature:" + enableSignature
|
||||||
@ -212,7 +234,7 @@ public class PgpSignEncrypt {
|
|||||||
PGPSecretKeyRing signingKeyRing = null;
|
PGPSecretKeyRing signingKeyRing = null;
|
||||||
PGPPrivateKey signaturePrivateKey = null;
|
PGPPrivateKey signaturePrivateKey = null;
|
||||||
if (enableSignature) {
|
if (enableSignature) {
|
||||||
signingKeyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
|
signingKeyRing = ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId);
|
||||||
signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
|
signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
|
||||||
if (signingKey == null) {
|
if (signingKey == null) {
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||||
@ -246,12 +268,12 @@ public class PgpSignEncrypt {
|
|||||||
|
|
||||||
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
|
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
|
||||||
|
|
||||||
if (mEncryptionKeyIds.length == 0) {
|
if (mSymmetricPassphrase != null) {
|
||||||
// Symmetric encryption
|
// Symmetric encryption
|
||||||
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
|
Log.d(Constants.TAG, "encryptionKeyIds length is 0 -> symmetric encryption");
|
||||||
|
|
||||||
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
|
JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
|
||||||
new JcePBEKeyEncryptionMethodGenerator(mEncryptionPassphrase.toCharArray());
|
new JcePBEKeyEncryptionMethodGenerator(mSymmetricPassphrase.toCharArray());
|
||||||
cPk.addMethod(symmetricEncryptionGenerator);
|
cPk.addMethod(symmetricEncryptionGenerator);
|
||||||
} else {
|
} else {
|
||||||
// Asymmetric encryption
|
// Asymmetric encryption
|
||||||
@ -284,7 +306,7 @@ public class PgpSignEncrypt {
|
|||||||
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
|
||||||
signatureGenerator.init(signatureType, signaturePrivateKey);
|
signatureGenerator.init(signatureType, signaturePrivateKey);
|
||||||
|
|
||||||
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
|
String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey());
|
||||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||||
spGen.setSignerUserID(false, userId);
|
spGen.setSignerUserID(false, userId);
|
||||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||||
@ -442,7 +464,7 @@ public class PgpSignEncrypt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PGPSecretKeyRing signingKeyRing =
|
PGPSecretKeyRing signingKeyRing =
|
||||||
ProviderHelper.getPGPSecretKeyRingByKeyId(mContext, mSignatureKeyId);
|
ProviderHelper.getPGPSecretKeyRingWithKeyId(mContext, mSignatureKeyId);
|
||||||
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
|
PGPSecretKey signingKey = PgpKeyHelper.getSigningKey(mContext, mSignatureKeyId);
|
||||||
if (signingKey == null) {
|
if (signingKey == null) {
|
||||||
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
throw new PgpGeneralException(mContext.getString(R.string.error_signature_failed));
|
||||||
@ -483,7 +505,7 @@ public class PgpSignEncrypt {
|
|||||||
signatureGenerator.init(type, signaturePrivateKey);
|
signatureGenerator.init(type, signaturePrivateKey);
|
||||||
|
|
||||||
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
|
||||||
String userId = PgpKeyHelper.getMainUserId(PgpKeyHelper.getMasterKey(signingKeyRing));
|
String userId = PgpKeyHelper.getMainUserId(signingKeyRing.getSecretKey());
|
||||||
spGen.setSignerUserID(false, userId);
|
spGen.setSignerUserID(false, userId);
|
||||||
signatureGenerator.setHashedSubpackets(spGen.generate());
|
signatureGenerator.setHashedSubpackets(spGen.generate());
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,13 @@
|
|||||||
package org.sufficientlysecure.keychain.pgp;
|
package org.sufficientlysecure.keychain.pgp;
|
||||||
|
|
||||||
import org.spongycastle.asn1.DERObjectIdentifier;
|
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.PGPException;
|
||||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
@ -29,13 +35,14 @@ import org.spongycastle.x509.extension.SubjectKeyIdentifierStructure;
|
|||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
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.io.IOException;
|
||||||
import java.math.BigInteger;
|
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.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
@ -43,6 +50,11 @@ import java.util.Date;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Vector;
|
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 class PgpToX509 {
|
||||||
public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
|
public static final String DN_COMMON_PART_O = "OpenPGP to X.509 Bridge";
|
||||||
public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert";
|
public static final String DN_COMMON_PART_OU = "OpenPGP Keychain cert";
|
||||||
@ -71,9 +83,10 @@ public class PgpToX509 {
|
|||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @author Bruno Harbulot
|
* @author Bruno Harbulot
|
||||||
*/
|
*/
|
||||||
public static X509Certificate createSelfSignedCert(PublicKey pubKey, PrivateKey privKey,
|
public static X509Certificate createSelfSignedCert(
|
||||||
X509Name subject, Date startDate, Date endDate, String subjAltNameURI)
|
PublicKey pubKey, PrivateKey privKey, X509Name subject, Date startDate, Date endDate,
|
||||||
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
|
String subjAltNameURI)
|
||||||
|
throws InvalidKeyException, IllegalStateException, NoSuchAlgorithmException,
|
||||||
SignatureException, CertificateException, NoSuchProviderException {
|
SignatureException, CertificateException, NoSuchProviderException {
|
||||||
|
|
||||||
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
|
X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
|
||||||
@ -170,10 +183,10 @@ public class PgpToX509 {
|
|||||||
/**
|
/**
|
||||||
* Creates a self-signed certificate from a PGP Secret Key.
|
* 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
|
* @param pgpSecKey PGP Secret Key (from which one can extract the public and private
|
||||||
* attributes).
|
* keys and other attributes).
|
||||||
* @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks should be done
|
* @param pgpPrivKey PGP Private Key corresponding to the Secret Key (password callbacks
|
||||||
* before calling this method)
|
* should be done before calling this method)
|
||||||
* @param subjAltNameURI optional URI to embed in the subject alternative-name
|
* @param subjAltNameURI optional URI to embed in the subject alternative-name
|
||||||
* @return self-signed certificate
|
* @return self-signed certificate
|
||||||
* @throws PGPException
|
* @throws PGPException
|
||||||
@ -184,9 +197,9 @@ public class PgpToX509 {
|
|||||||
* @throws CertificateException
|
* @throws CertificateException
|
||||||
* @author Bruno Harbulot
|
* @author Bruno Harbulot
|
||||||
*/
|
*/
|
||||||
public static X509Certificate createSelfSignedCert(PGPSecretKey pgpSecKey,
|
public static X509Certificate createSelfSignedCert(
|
||||||
PGPPrivateKey pgpPrivKey, String subjAltNameURI) throws PGPException,
|
PGPSecretKey pgpSecKey, PGPPrivateKey pgpPrivKey, String subjAltNameURI)
|
||||||
NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
|
throws PGPException, NoSuchProviderException, InvalidKeyException, NoSuchAlgorithmException,
|
||||||
SignatureException, CertificateException {
|
SignatureException, CertificateException {
|
||||||
// get public key from secret key
|
// get public key from secret key
|
||||||
PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey();
|
PGPPublicKey pgpPubKey = pgpSecKey.getPublicKey();
|
||||||
|
@ -23,4 +23,7 @@ public class PgpGeneralException extends Exception {
|
|||||||
public PgpGeneralException(String message) {
|
public PgpGeneralException(String message) {
|
||||||
super(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.net.Uri;
|
||||||
import android.provider.BaseColumns;
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
|
||||||
public class KeychainContract {
|
public class KeychainContract {
|
||||||
|
|
||||||
interface KeyRingsColumns {
|
interface KeyRingsColumns {
|
||||||
String MASTER_KEY_ID = "master_key_id"; // not a database id
|
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
|
String KEY_RING_DATA = "key_ring_data"; // PGPPublicKeyRing / PGPSecretKeyRing blob
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KeysColumns {
|
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 KEY_ID = "key_id"; // not a database id
|
||||||
String TYPE = "type"; // see KeyTypes
|
|
||||||
String IS_MASTER_KEY = "is_master_key";
|
|
||||||
String ALGORITHM = "algorithm";
|
String ALGORITHM = "algorithm";
|
||||||
|
String FINGERPRINT = "fingerprint";
|
||||||
|
|
||||||
String KEY_SIZE = "key_size";
|
String KEY_SIZE = "key_size";
|
||||||
String CAN_CERTIFY = "can_certify";
|
|
||||||
String CAN_SIGN = "can_sign";
|
String CAN_SIGN = "can_sign";
|
||||||
String CAN_ENCRYPT = "can_encrypt";
|
String CAN_ENCRYPT = "can_encrypt";
|
||||||
|
String CAN_CERTIFY = "can_certify";
|
||||||
String IS_REVOKED = "is_revoked";
|
String IS_REVOKED = "is_revoked";
|
||||||
|
|
||||||
String CREATION = "creation";
|
String CREATION = "creation";
|
||||||
String EXPIRY = "expiry";
|
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 {
|
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 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 {
|
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 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 KEY_ID_CERTIFIER = "key_id_certifier"; // verifying id, not a database id
|
||||||
String CREATION = "creation";
|
String CREATION = "creation";
|
||||||
|
String EXPIRY = "expiry";
|
||||||
String VERIFIED = "verified";
|
String VERIFIED = "verified";
|
||||||
String KEY_DATA = "key_data"; // certification blob
|
String KEY_DATA = "key_data"; // certification blob
|
||||||
}
|
}
|
||||||
@ -66,10 +67,15 @@ public class KeychainContract {
|
|||||||
interface ApiAppsColumns {
|
interface ApiAppsColumns {
|
||||||
String PACKAGE_NAME = "package_name";
|
String PACKAGE_NAME = "package_name";
|
||||||
String PACKAGE_SIGNATURE = "package_signature";
|
String PACKAGE_SIGNATURE = "package_signature";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiAppsAccountsColumns {
|
||||||
|
String ACCOUNT_NAME = "account_name";
|
||||||
String KEY_ID = "key_id"; // not a database id
|
String KEY_ID = "key_id"; // not a database id
|
||||||
String ENCRYPTION_ALGORITHM = "encryption_algorithm";
|
String ENCRYPTION_ALGORITHM = "encryption_algorithm";
|
||||||
String HASH_ALORITHM = "hash_algorithm";
|
String HASH_ALORITHM = "hash_algorithm";
|
||||||
String COMPRESSION = "compression";
|
String COMPRESSION = "compression";
|
||||||
|
String PACKAGE_NAME = "package_name"; // foreign key to api_apps.package_name
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class KeyTypes {
|
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_KEY_RINGS = "key_rings";
|
||||||
public static final String BASE_DATA = "data";
|
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_PUBLIC = "public";
|
||||||
public static final String PATH_SECRET = "secret";
|
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_USER_IDS = "user_ids";
|
||||||
public static final String PATH_KEYS = "keys";
|
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 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()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
.appendPath(BASE_KEY_RINGS).build();
|
.appendPath(BASE_KEY_RINGS).build();
|
||||||
|
|
||||||
/**
|
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.sufficientlysecure.openkeychain.key_ring";
|
||||||
* Use if multiple items get returned
|
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.key_ring";
|
||||||
*/
|
|
||||||
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 Uri buildUnifiedKeyRingsUri() {
|
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();
|
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).build();
|
||||||
}
|
}
|
||||||
|
public static Uri buildPublicKeyRingUri(String masterKeyId) {
|
||||||
public static Uri buildPublicKeyRingsUri(String keyRingRowId) {
|
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_PUBLIC).build();
|
||||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId).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) {
|
public static Uri buildSecretKeyRingUri() {
|
||||||
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() {
|
|
||||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
|
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).build();
|
||||||
}
|
}
|
||||||
|
public static Uri buildSecretKeyRingUri(String masterKeyId) {
|
||||||
public static Uri buildSecretKeyRingsUri(String keyRingRowId) {
|
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_SECRET).build();
|
||||||
return CONTENT_URI.buildUpon().appendPath(PATH_SECRET).appendPath(keyRingRowId).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 {
|
public static class Keys implements KeysColumns, BaseColumns {
|
||||||
@ -185,82 +175,42 @@ public class KeychainContract {
|
|||||||
/**
|
/**
|
||||||
* Use if multiple items get returned
|
* 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
|
* 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) {
|
public static Uri buildKeysUri(String masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_KEYS).build();
|
||||||
.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 class UserIds implements UserIdsColumns, BaseColumns {
|
||||||
|
public static final String VERIFIED = "verified";
|
||||||
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
|
||||||
.appendPath(BASE_KEY_RINGS).build();
|
.appendPath(BASE_KEY_RINGS).build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use if multiple items get returned
|
* 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
|
* 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) {
|
public static Uri buildUserIdsUri(String masterKeyId) {
|
||||||
return CONTENT_URI.buildUpon().appendPath(PATH_PUBLIC).appendPath(keyRingRowId)
|
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_USER_IDS).build();
|
||||||
.appendPath(PATH_USER_IDS).build();
|
|
||||||
}
|
}
|
||||||
|
public static Uri buildUserIdsUri(Uri uri) {
|
||||||
public static Uri buildPublicUserIdsUri(String keyRingRowId, String userIdRowId) {
|
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_USER_IDS).build();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,45 +221,55 @@ public class KeychainContract {
|
|||||||
/**
|
/**
|
||||||
* Use if multiple items get returned
|
* 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
|
* 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 final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.sufficientlysecure.openkeychain.api_app";
|
||||||
|
|
||||||
public static Uri buildIdUri(String rowId) {
|
|
||||||
return CONTENT_URI.buildUpon().appendPath(rowId).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Uri buildByPackageNameUri(String packageName) {
|
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();
|
.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 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()
|
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 masterKeyId) {
|
||||||
public static Uri buildCertsUri(String rowId) {
|
return CONTENT_URI.buildUpon().appendPath(masterKeyId).appendPath(PATH_CERTS).build();
|
||||||
return CONTENT_URI.buildUpon().appendPath(rowId).build();
|
|
||||||
}
|
}
|
||||||
|
public static Uri buildCertsUri(Uri uri) {
|
||||||
public static Uri buildCertsByKeyRowIdUri(String keyRingRowId) {
|
return CONTENT_URI.buildUpon().appendPath(uri.getPathSegments().get(1)).appendPath(PATH_CERTS).build();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -18,97 +18,156 @@
|
|||||||
package org.sufficientlysecure.keychain.provider;
|
package org.sufficientlysecure.keychain.provider;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
import android.provider.BaseColumns;
|
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.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiAppsColumns;
|
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.KeyRingsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.CertsColumns;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class KeychainDatabase extends SQLiteOpenHelper {
|
public class KeychainDatabase extends SQLiteOpenHelper {
|
||||||
private static final String DATABASE_NAME = "apg.db";
|
private static final String DATABASE_NAME = "openkeychain.db";
|
||||||
private static final int DATABASE_VERSION = 8;
|
private static final int DATABASE_VERSION = 1;
|
||||||
|
static Boolean apg_hack = false;
|
||||||
|
|
||||||
public interface Tables {
|
public interface Tables {
|
||||||
String KEY_RINGS = "key_rings";
|
String KEY_RINGS_PUBLIC = "keyrings_public";
|
||||||
|
String KEY_RINGS_SECRET = "keyrings_secret";
|
||||||
String KEYS = "keys";
|
String KEYS = "keys";
|
||||||
String USER_IDS = "user_ids";
|
String USER_IDS = "user_ids";
|
||||||
String API_APPS = "api_apps";
|
|
||||||
String CERTS = "certs";
|
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
|
private static final String CREATE_KEYRINGS_PUBLIC =
|
||||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
"CREATE TABLE IF NOT EXISTS keyrings_public ("
|
||||||
+ KeyRingsColumns.MASTER_KEY_ID + " INT64, "
|
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
|
||||||
+ KeyRingsColumns.TYPE + " INTEGER, "
|
+ KeyRingsColumns.KEY_RING_DATA + " BLOB"
|
||||||
+ KeyRingsColumns.KEY_RING_DATA + " BLOB)";
|
+ ")";
|
||||||
|
|
||||||
private static final String CREATE_KEYS = "CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
private static final String CREATE_KEYRINGS_SECRET =
|
||||||
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
"CREATE TABLE IF NOT EXISTS keyrings_secret ("
|
||||||
+ KeysColumns.KEY_ID + " INT64, "
|
+ KeyRingsColumns.MASTER_KEY_ID + " INTEGER PRIMARY KEY,"
|
||||||
+ KeysColumns.TYPE + " INTEGER, "
|
+ KeyRingsColumns.KEY_RING_DATA + " BLOB,"
|
||||||
+ KeysColumns.IS_MASTER_KEY + " INTEGER, "
|
+ "FOREIGN KEY(" + KeyRingsColumns.MASTER_KEY_ID + ") "
|
||||||
+ KeysColumns.ALGORITHM + " INTEGER, "
|
+ "REFERENCES keyrings_public(" + KeyRingsColumns.MASTER_KEY_ID + ") ON DELETE CASCADE"
|
||||||
+ 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_USER_IDS = "CREATE TABLE IF NOT EXISTS " + Tables.USER_IDS
|
private static final String CREATE_KEYS =
|
||||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
"CREATE TABLE IF NOT EXISTS " + Tables.KEYS + " ("
|
||||||
+ UserIdsColumns.USER_ID + " TEXT, "
|
+ KeysColumns.MASTER_KEY_ID + " INTEGER, "
|
||||||
+ UserIdsColumns.RANK + " INTEGER, "
|
+ KeysColumns.RANK + " INTEGER, "
|
||||||
+ UserIdsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL, FOREIGN KEY("
|
|
||||||
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
|
+ KeysColumns.KEY_ID + " INTEGER, "
|
||||||
+ BaseColumns._ID + ") ON DELETE CASCADE)";
|
+ 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
|
private static final String CREATE_API_APPS = "CREATE TABLE IF NOT EXISTS " + Tables.API_APPS
|
||||||
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
+ ApiAppsColumns.PACKAGE_NAME + " TEXT UNIQUE, "
|
+ ApiAppsColumns.PACKAGE_NAME + " TEXT NOT NULL UNIQUE, "
|
||||||
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB, "
|
+ ApiAppsColumns.PACKAGE_SIGNATURE + " BLOB)";
|
||||||
+ ApiAppsColumns.KEY_ID + " INT64, "
|
|
||||||
+ ApiAppsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
|
|
||||||
+ ApiAppsColumns.HASH_ALORITHM + " INTEGER, "
|
|
||||||
+ ApiAppsColumns.COMPRESSION + " INTEGER)";
|
|
||||||
|
|
||||||
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, "
|
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
+ CertsColumns.KEY_RING_ROW_ID + " INTEGER NOT NULL "
|
+ ApiAppsAccountsColumns.ACCOUNT_NAME + " TEXT NOT NULL, "
|
||||||
+ " REFERENCES " + Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE, "
|
+ ApiAppsAccountsColumns.KEY_ID + " INT64, "
|
||||||
+ CertsColumns.KEY_ID + " INTEGER, " // certified key
|
+ ApiAppsAccountsColumns.ENCRYPTION_ALGORITHM + " INTEGER, "
|
||||||
+ CertsColumns.RANK + " INTEGER, " // key rank of certified uid
|
+ ApiAppsAccountsColumns.HASH_ALORITHM + " INTEGER, "
|
||||||
+ CertsColumns.KEY_ID_CERTIFIER + " INTEGER, " // certifying key
|
+ ApiAppsAccountsColumns.COMPRESSION + " INTEGER, "
|
||||||
+ CertsColumns.CREATION + " INTEGER, "
|
+ ApiAppsAccountsColumns.PACKAGE_NAME + " TEXT NOT NULL, "
|
||||||
+ CertsColumns.VERIFIED + " INTEGER, "
|
+ "UNIQUE(" + ApiAppsAccountsColumns.ACCOUNT_NAME + ", "
|
||||||
+ CertsColumns.KEY_DATA + " BLOB)";
|
+ ApiAppsAccountsColumns.PACKAGE_NAME + "), "
|
||||||
|
+ "FOREIGN KEY(" + ApiAppsAccountsColumns.PACKAGE_NAME + ") REFERENCES "
|
||||||
|
+ Tables.API_APPS + "(" + ApiAppsColumns.PACKAGE_NAME + ") ON DELETE CASCADE)";
|
||||||
|
|
||||||
KeychainDatabase(Context context) {
|
KeychainDatabase(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
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
|
@Override
|
||||||
public void onCreate(SQLiteDatabase db) {
|
public void onCreate(SQLiteDatabase db) {
|
||||||
Log.w(Constants.TAG, "Creating database...");
|
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_KEYS);
|
||||||
db.execSQL(CREATE_USER_IDS);
|
db.execSQL(CREATE_USER_IDS);
|
||||||
db.execSQL(CREATE_API_APPS);
|
|
||||||
db.execSQL(CREATE_CERTS);
|
db.execSQL(CREATE_CERTS);
|
||||||
|
db.execSQL(CREATE_API_APPS);
|
||||||
|
db.execSQL(CREATE_API_APPS_ACCOUNTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -117,47 +176,100 @@ public class KeychainDatabase extends SQLiteOpenHelper {
|
|||||||
if (!db.isReadOnly()) {
|
if (!db.isReadOnly()) {
|
||||||
// Enable foreign key constraints
|
// Enable foreign key constraints
|
||||||
db.execSQL("PRAGMA foreign_keys=ON;");
|
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
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
public void onUpgrade(SQLiteDatabase db, int old, int nu) {
|
||||||
Log.w(Constants.TAG, "Upgrading database from version " + oldVersion + " to " + newVersion);
|
// don't care (this is version 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Upgrade from oldVersion through all cases to newest one
|
/** This method tries to import data from a provided database.
|
||||||
for (int version = oldVersion; version < newVersion; ++version) {
|
*
|
||||||
Log.w(Constants.TAG, "Upgrading database to version " + version);
|
* 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) {
|
boolean hasApgDb = false; {
|
||||||
case 3:
|
// It's the Java way =(
|
||||||
db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY
|
String[] dbs = context.databaseList();
|
||||||
+ " INTEGER DEFAULT 0;");
|
for(String db : dbs) {
|
||||||
db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY
|
if(db.equals("apg.db")) {
|
||||||
+ " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
|
hasApgDb = true;
|
||||||
break;
|
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;
|
import org.sufficientlysecure.keychain.provider.KeychainServiceBlobContract.BlobsColumns;
|
||||||
|
|
||||||
public class KeychainServiceBlobDatabase extends SQLiteOpenHelper {
|
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;
|
private static final int DATABASE_VERSION = 2;
|
||||||
|
|
||||||
public static final String TABLE = "data";
|
public static final String TABLE = "data";
|
||||||
|
@ -38,7 +38,7 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class KeychainServiceBlobProvider extends ContentProvider {
|
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;
|
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/>.
|
* 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.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
|
|
||||||
public class AppSettings {
|
public class AccountSettings {
|
||||||
private String mPackageName;
|
private String mAccountName;
|
||||||
private byte[] mPackageSignature;
|
|
||||||
private long mKeyId = Id.key.none;
|
private long mKeyId = Id.key.none;
|
||||||
private int mEncryptionAlgorithm;
|
private int mEncryptionAlgorithm;
|
||||||
private int mHashAlgorithm;
|
private int mHashAlgorithm;
|
||||||
private int mCompression;
|
private int mCompression;
|
||||||
|
|
||||||
public AppSettings() {
|
public AccountSettings() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppSettings(String packageName, byte[] packageSignature) {
|
public AccountSettings(String accountName) {
|
||||||
super();
|
super();
|
||||||
this.mPackageName = packageName;
|
this.mAccountName = accountName;
|
||||||
this.mPackageSignature = packageSignature;
|
|
||||||
// defaults:
|
// defaults:
|
||||||
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
|
this.mEncryptionAlgorithm = PGPEncryptedData.AES_256;
|
||||||
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
|
this.mHashAlgorithm = HashAlgorithmTags.SHA512;
|
||||||
this.mCompression = Id.choice.compression.zlib;
|
this.mCompression = Id.choice.compression.zlib;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPackageName() {
|
public String getAccountName() {
|
||||||
return mPackageName;
|
return mAccountName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPackageName(String packageName) {
|
public void setAccountName(String mAccountName) {
|
||||||
this.mPackageName = packageName;
|
this.mAccountName = mAccountName;
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getPackageSignature() {
|
|
||||||
return mPackageSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPackageSignature(byte[] packageSignature) {
|
|
||||||
this.mPackageSignature = packageSignature;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getKeyId() {
|
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/>.
|
* 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.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -23,6 +23,7 @@ import android.database.Cursor;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
import org.openintents.openpgp.IOpenPgpService;
|
import org.openintents.openpgp.IOpenPgpService;
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
import org.openintents.openpgp.OpenPgpError;
|
||||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
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.PgpDecryptVerifyResult;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
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.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||||
|
import org.sufficientlysecure.keychain.ui.ImportKeysActivity;
|
||||||
import org.sufficientlysecure.keychain.util.InputData;
|
import org.sufficientlysecure.keychain.util.InputData;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class OpenPgpService extends RemoteService {
|
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.
|
* Search database for key ids based on emails.
|
||||||
*
|
*
|
||||||
@ -61,15 +62,15 @@ public class OpenPgpService extends RemoteService {
|
|||||||
ArrayList<Long> keyIds = new ArrayList<Long>();
|
ArrayList<Long> keyIds = new ArrayList<Long>();
|
||||||
|
|
||||||
boolean missingUserIdsCheck = false;
|
boolean missingUserIdsCheck = false;
|
||||||
boolean dublicateUserIdsCheck = false;
|
boolean duplicateUserIdsCheck = false;
|
||||||
ArrayList<String> missingUserIds = new ArrayList<String>();
|
ArrayList<String> missingUserIds = new ArrayList<String>();
|
||||||
ArrayList<String> dublicateUserIds = new ArrayList<String>();
|
ArrayList<String> duplicateUserIds = new ArrayList<String>();
|
||||||
|
|
||||||
for (String email : encryptionUserIds) {
|
for (String email : encryptionUserIds) {
|
||||||
Uri uri = KeychainContract.KeyRings.buildPublicKeyRingsByEmailsUri(email);
|
Uri uri = KeyRings.buildUnifiedKeyRingsFindByEmailUri(email);
|
||||||
Cursor cur = getContentResolver().query(uri, null, null, null, null);
|
Cursor cur = getContentResolver().query(uri, null, null, null, null);
|
||||||
if (cur.moveToFirst()) {
|
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);
|
keyIds.add(id);
|
||||||
} else {
|
} else {
|
||||||
missingUserIdsCheck = true;
|
missingUserIdsCheck = true;
|
||||||
@ -77,8 +78,8 @@ public class OpenPgpService extends RemoteService {
|
|||||||
Log.d(Constants.TAG, "user id missing");
|
Log.d(Constants.TAG, "user id missing");
|
||||||
}
|
}
|
||||||
if (cur.moveToNext()) {
|
if (cur.moveToNext()) {
|
||||||
dublicateUserIdsCheck = true;
|
duplicateUserIdsCheck = true;
|
||||||
dublicateUserIds.add(email);
|
duplicateUserIds.add(email);
|
||||||
Log.d(Constants.TAG, "more than one user id with the same 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
|
// allow the user to verify pub key selection
|
||||||
if (missingUserIdsCheck || dublicateUserIdsCheck) {
|
if (missingUserIdsCheck || duplicateUserIdsCheck) {
|
||||||
// build PendingIntent
|
// build PendingIntent
|
||||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||||
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
|
intent.setAction(RemoteServiceActivity.ACTION_SELECT_PUB_KEYS);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
intent.putExtra(RemoteServiceActivity.EXTRA_SELECTED_MASTER_KEY_IDS, keyIdsArray);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_MISSING_USER_IDS, missingUserIds);
|
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);
|
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
(getBaseContext(), PRIVATE_REQUEST_CODE_USER_IDS, intent, 0);
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
// return PendingIntent to be executed by client
|
// return PendingIntent to be executed by client
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
@ -126,8 +128,9 @@ public class OpenPgpService extends RemoteService {
|
|||||||
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
|
intent.putExtra(RemoteServiceActivity.EXTRA_SECRET_KEY_ID, keyId);
|
||||||
// pass params through to activity that it can be returned again later to repeat pgp operation
|
// pass params through to activity that it can be returned again later to repeat pgp operation
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||||
PendingIntent pi = PendingIntent.getActivity
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
(getBaseContext(), PRIVATE_REQUEST_CODE_PASSPHRASE, intent, 0);
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
// return PendingIntent to be executed by client
|
// return PendingIntent to be executed by client
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
@ -137,7 +140,7 @@ public class OpenPgpService extends RemoteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent signImpl(Intent data, ParcelFileDescriptor input,
|
private Intent signImpl(Intent data, ParcelFileDescriptor input,
|
||||||
ParcelFileDescriptor output, AppSettings appSettings) {
|
ParcelFileDescriptor output, AccountSettings accSettings) {
|
||||||
try {
|
try {
|
||||||
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
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)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||||
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||||
} else {
|
} else {
|
||||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), appSettings.getKeyId());
|
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId());
|
||||||
}
|
}
|
||||||
if (passphrase == null) {
|
if (passphrase == null) {
|
||||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
// 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;
|
return passphraseBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,9 +167,9 @@ public class OpenPgpService extends RemoteService {
|
|||||||
// sign-only
|
// sign-only
|
||||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
||||||
builder.enableAsciiArmorOutput(asciiArmor)
|
builder.enableAsciiArmorOutput(asciiArmor)
|
||||||
.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
.signatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||||
.signatureForceV3(false)
|
.signatureForceV3(false)
|
||||||
.signatureKeyId(appSettings.getKeyId())
|
.signatureKeyId(accSettings.getKeyId())
|
||||||
.signaturePassphrase(passphrase);
|
.signaturePassphrase(passphrase);
|
||||||
builder.build().execute();
|
builder.build().execute();
|
||||||
} finally {
|
} finally {
|
||||||
@ -187,7 +190,8 @@ public class OpenPgpService extends RemoteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
|
private Intent encryptAndSignImpl(Intent data, ParcelFileDescriptor input,
|
||||||
ParcelFileDescriptor output, AppSettings appSettings, boolean sign) {
|
ParcelFileDescriptor output, AccountSettings accSettings,
|
||||||
|
boolean sign) {
|
||||||
try {
|
try {
|
||||||
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
boolean asciiArmor = data.getBooleanExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||||
|
|
||||||
@ -210,14 +214,14 @@ public class OpenPgpService extends RemoteService {
|
|||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
result.putExtra(OpenPgpApi.RESULT_ERROR,
|
||||||
new OpenPgpError(OpenPgpError.GENERIC_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);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add own key for encryption
|
// add own key for encryption
|
||||||
keyIds = Arrays.copyOf(keyIds, keyIds.length + 1);
|
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
|
// build InputData and write into OutputStream
|
||||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||||
@ -229,8 +233,8 @@ public class OpenPgpService extends RemoteService {
|
|||||||
|
|
||||||
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder(getContext(), inputData, os);
|
||||||
builder.enableAsciiArmorOutput(asciiArmor)
|
builder.enableAsciiArmorOutput(asciiArmor)
|
||||||
.compressionId(appSettings.getCompression())
|
.compressionId(accSettings.getCompression())
|
||||||
.symmetricEncryptionAlgorithm(appSettings.getEncryptionAlgorithm())
|
.symmetricEncryptionAlgorithm(accSettings.getEncryptionAlgorithm())
|
||||||
.encryptionKeyIds(keyIds);
|
.encryptionKeyIds(keyIds);
|
||||||
|
|
||||||
if (sign) {
|
if (sign) {
|
||||||
@ -239,18 +243,18 @@ public class OpenPgpService extends RemoteService {
|
|||||||
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE);
|
||||||
} else {
|
} else {
|
||||||
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
|
passphrase = PassphraseCacheService.getCachedPassphrase(getContext(),
|
||||||
appSettings.getKeyId());
|
accSettings.getKeyId());
|
||||||
}
|
}
|
||||||
if (passphrase == null) {
|
if (passphrase == null) {
|
||||||
// get PendingIntent for passphrase input, add it to given params and return to client
|
// 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;
|
return passphraseBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sign and encrypt
|
// sign and encrypt
|
||||||
builder.signatureHashAlgorithm(appSettings.getHashAlgorithm())
|
builder.signatureHashAlgorithm(accSettings.getHashAlgorithm())
|
||||||
.signatureForceV3(false)
|
.signatureForceV3(false)
|
||||||
.signatureKeyId(appSettings.getKeyId())
|
.signatureKeyId(accSettings.getKeyId())
|
||||||
.signaturePassphrase(passphrase);
|
.signaturePassphrase(passphrase);
|
||||||
} else {
|
} else {
|
||||||
// encrypt only
|
// encrypt only
|
||||||
@ -276,7 +280,7 @@ public class OpenPgpService extends RemoteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
|
private Intent decryptAndVerifyImpl(Intent data, ParcelFileDescriptor input,
|
||||||
ParcelFileDescriptor output, AppSettings appSettings) {
|
ParcelFileDescriptor output, Set<Long> allowedKeyIds) {
|
||||||
try {
|
try {
|
||||||
// Get Input- and OutputStream from ParcelFileDescriptor
|
// Get Input- and OutputStream from ParcelFileDescriptor
|
||||||
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(input);
|
||||||
@ -290,19 +294,21 @@ public class OpenPgpService extends RemoteService {
|
|||||||
InputData inputData = new InputData(is, inputLength);
|
InputData inputData = new InputData(is, inputLength);
|
||||||
|
|
||||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
|
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, os);
|
||||||
builder.assumeSymmetric(false) // no support for symmetric encryption
|
builder.allowSymmetricDecryption(false) // no support for symmetric encryption
|
||||||
// allow only the private key for this app for decryption
|
.allowedKeyIds(allowedKeyIds) // allow only private keys associated with
|
||||||
.enforcedKeyId(appSettings.getKeyId())
|
// accounts of this app
|
||||||
.passphrase(passphrase);
|
.passphrase(passphrase);
|
||||||
|
|
||||||
// TODO: currently does not support binary signed-only content
|
// TODO: currently does not support binary signed-only content
|
||||||
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
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
|
// 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;
|
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!");
|
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 (signatureResult.getStatus() == OpenPgpSignatureResult.SIGNATURE_UNKNOWN_PUB_KEY) {
|
||||||
// If signature is unknown we return an _additional_ PendingIntent
|
// If signature is unknown we return an _additional_ PendingIntent
|
||||||
// to retrieve the missing key
|
// to retrieve the missing key
|
||||||
// TODO!!!
|
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, signatureResult.getKeyId());
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
|
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||||
}
|
}
|
||||||
@ -346,19 +352,19 @@ public class OpenPgpService extends RemoteService {
|
|||||||
try {
|
try {
|
||||||
long keyId = data.getLongExtra(OpenPgpApi.EXTRA_KEY_ID, 0);
|
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();
|
Intent result = new Intent();
|
||||||
|
|
||||||
// If keys are not in db we return an additional PendingIntent
|
// If keys are not in db we return an additional PendingIntent
|
||||||
// to retrieve the missing key
|
// to retrieve the missing key
|
||||||
// TODO!!!
|
Intent intent = new Intent(getBaseContext(), ImportKeysActivity.class);
|
||||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
intent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
intent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, keyId);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_ERROR_MESSAGE, "todo");
|
intent.putExtra(ImportKeysActivity.EXTRA_PENDING_INTENT_DATA, data);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
PRIVATE_REQUEST_CODE_GET_KEYS, intent, 0);
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
result.putExtra(OpenPgpApi.RESULT_INTENT, pi);
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
|
||||||
@ -366,6 +372,9 @@ public class OpenPgpService extends RemoteService {
|
|||||||
} else {
|
} else {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||||
|
|
||||||
|
// TODO: also return PendingIntent that opens the key view activity
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -407,7 +416,7 @@ public class OpenPgpService extends RemoteService {
|
|||||||
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
|
if (data.getIntExtra(OpenPgpApi.EXTRA_API_VERSION, -1) != OpenPgpApi.API_VERSION) {
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
OpenPgpError error = new OpenPgpError
|
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_ERROR, error);
|
||||||
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR);
|
||||||
return result;
|
return result;
|
||||||
@ -432,17 +441,30 @@ public class OpenPgpService extends RemoteService {
|
|||||||
return errorResult;
|
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();
|
String action = data.getAction();
|
||||||
if (OpenPgpApi.ACTION_SIGN.equals(action)) {
|
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)) {
|
} 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)) {
|
} 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)) {
|
} 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)) {
|
} else if (OpenPgpApi.ACTION_GET_KEY.equals(action)) {
|
||||||
return getKeyImpl(data);
|
return getKeyImpl(data);
|
||||||
} else if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(action)) {
|
} 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/>.
|
* 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.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
@ -27,12 +27,14 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||||||
import android.content.pm.Signature;
|
import android.content.pm.Signature;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
|
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
import org.openintents.openpgp.OpenPgpError;
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.remote.ui.RemoteServiceActivity;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -44,10 +46,6 @@ import java.util.Arrays;
|
|||||||
public abstract class RemoteService extends Service {
|
public abstract class RemoteService extends Service {
|
||||||
Context mContext;
|
Context mContext;
|
||||||
|
|
||||||
private static final int PRIVATE_REQUEST_CODE_REGISTER = 651;
|
|
||||||
private static final int PRIVATE_REQUEST_CODE_ERROR = 652;
|
|
||||||
|
|
||||||
|
|
||||||
public Context getContext() {
|
public Context getContext() {
|
||||||
return mContext;
|
return mContext;
|
||||||
}
|
}
|
||||||
@ -55,13 +53,10 @@ public abstract class RemoteService extends Service {
|
|||||||
protected Intent isAllowed(Intent data) {
|
protected Intent isAllowed(Intent data) {
|
||||||
try {
|
try {
|
||||||
if (isCallerAllowed(false)) {
|
if (isCallerAllowed(false)) {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
String[] callingPackages = getPackageManager().getPackagesForUid(
|
String packageName = getCurrentCallingPackage();
|
||||||
Binder.getCallingUid());
|
Log.d(Constants.TAG, "isAllowed packageName: " + packageName);
|
||||||
// TODO: currently simply uses first entry
|
|
||||||
String packageName = callingPackages[0];
|
|
||||||
|
|
||||||
byte[] packageSignature;
|
byte[] packageSignature;
|
||||||
try {
|
try {
|
||||||
@ -83,8 +78,9 @@ public abstract class RemoteService extends Service {
|
|||||||
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
intent.putExtra(RemoteServiceActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
PRIVATE_REQUEST_CODE_REGISTER, intent, 0);
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
|
||||||
|
|
||||||
// return PendingIntent to be executed by client
|
// return PendingIntent to be executed by client
|
||||||
Intent result = new Intent();
|
Intent result = new Intent();
|
||||||
@ -99,11 +95,12 @@ public abstract class RemoteService extends Service {
|
|||||||
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
Intent intent = new Intent(getBaseContext(), RemoteServiceActivity.class);
|
||||||
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
intent.setAction(RemoteServiceActivity.ACTION_ERROR_MESSAGE);
|
||||||
intent.putExtra(RemoteServiceActivity.EXTRA_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);
|
intent.putExtra(RemoteServiceActivity.EXTRA_DATA, data);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(getBaseContext(),
|
PendingIntent pi = PendingIntent.getActivity(getBaseContext(), 0,
|
||||||
PRIVATE_REQUEST_CODE_ERROR, intent, 0);
|
intent,
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
// return PendingIntent to be executed by client
|
// return PendingIntent to be executed by client
|
||||||
Intent result = new Intent();
|
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
|
* @return
|
||||||
*/
|
*/
|
||||||
protected AppSettings getAppSettings() {
|
protected AccountSettings getAccSettings(String accountName) {
|
||||||
String[] callingPackages = getPackageManager().getPackagesForUid(Binder.getCallingUid());
|
String currentPkg = getCurrentCallingPackage();
|
||||||
|
Log.d(Constants.TAG, "accountName: " + accountName);
|
||||||
|
|
||||||
// get app settings for this package
|
Uri uri = KeychainContract.ApiAccounts.buildByPackageAndAccountUri(currentPkg, accountName);
|
||||||
for (int i = 0; i < callingPackages.length; i++) {
|
|
||||||
String currentPkg = callingPackages[i];
|
|
||||||
|
|
||||||
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) {
|
protected Intent getCreateAccountIntent(Intent data, String accountName) {
|
||||||
return settings;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +219,7 @@ public abstract class RemoteService extends Service {
|
|||||||
* @throws WrongPackageSignatureException
|
* @throws WrongPackageSignatureException
|
||||||
*/
|
*/
|
||||||
private boolean isPackageAllowed(String packageName) 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);
|
ArrayList<String> allowedPkgs = ProviderHelper.getRegisteredApiApps(this);
|
||||||
Log.d(Constants.TAG, "allowed: " + allowedPkgs);
|
Log.d(Constants.TAG, "allowed: " + allowedPkgs);
|
||||||
@ -216,10 +243,12 @@ public abstract class RemoteService extends Service {
|
|||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new WrongPackageSignatureException(
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 {
|
public class WrongPackageSignatureException extends Exception {
|
||||||
|
|
@ -15,7 +15,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -24,16 +24,18 @@ import android.support.v7.app.ActionBarActivity;
|
|||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.remote.AccountSettings;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
public class AppSettingsActivity extends ActionBarActivity {
|
public class AccountSettingsActivity extends ActionBarActivity {
|
||||||
private Uri mAppUri;
|
private Uri mAccountUri;
|
||||||
|
|
||||||
private AppSettingsFragment mSettingsFragment;
|
private AccountSettingsFragment mAccountSettingsFragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
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(
|
mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||||
R.id.api_app_settings_fragment);
|
R.id.api_account_settings_fragment);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
mAppUri = intent.getData();
|
mAccountUri = intent.getData();
|
||||||
if (mAppUri == null) {
|
if (mAccountUri == null) {
|
||||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
Log.d(Constants.TAG, "uri: " + mAppUri);
|
Log.d(Constants.TAG, "uri: " + mAccountUri);
|
||||||
loadData(mAppUri);
|
loadData(savedInstanceState, mAccountUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
getMenuInflater().inflate(R.menu.api_app_settings, menu);
|
getMenuInflater().inflate(R.menu.api_account_settings, menu);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_api_settings_revoke:
|
case R.id.menu_account_settings_delete:
|
||||||
revokeAccess();
|
deleteAccount();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_api_settings_cancel:
|
case R.id.menu_account_settings_cancel:
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadData(Uri appUri) {
|
private void loadData(Bundle savedInstanceState, Uri accountUri) {
|
||||||
AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri);
|
// TODO: load this also like other fragment with newInstance arguments?
|
||||||
mSettingsFragment.setAppSettings(settings);
|
AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri);
|
||||||
|
mAccountSettingsFragment.setAccSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revokeAccess() {
|
private void deleteAccount() {
|
||||||
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
|
if (getContentResolver().delete(mAccountUri, null, null) <= 0) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
ProviderHelper.updateApiApp(this, mSettingsFragment.getAppSettings(), mAppUri);
|
ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri);
|
||||||
|
|
||||||
finish();
|
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/>.
|
* 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 android.os.Bundle;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.ui.DrawerActivity;
|
import org.sufficientlysecure.keychain.ui.DrawerActivity;
|
||||||
|
|
||||||
public class RegisteredAppsListActivity extends DrawerActivity {
|
public class AppsListActivity extends DrawerActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
@ -15,10 +15,12 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -26,14 +28,20 @@ import android.support.v4.app.ListFragment;
|
|||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||||
|
|
||||||
public class RegisteredAppsListFragment extends ListFragment implements
|
public class AppsListFragment extends ListFragment implements
|
||||||
LoaderManager.LoaderCallbacks<Cursor> {
|
LoaderManager.LoaderCallbacks<Cursor> {
|
||||||
|
|
||||||
// This is the Adapter being used to display the list's data.
|
// 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() {
|
getListView().setOnItemClickListener(new OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||||
|
String selectedPackageName = mAdapter.getItemPackageName(position);
|
||||||
// edit app settings
|
// edit app settings
|
||||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||||
intent.setData(ContentUris.withAppendedId(KeychainContract.ApiApps.CONTENT_URI, id));
|
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -70,7 +79,10 @@ public class RegisteredAppsListFragment extends ListFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
// These are the Contacts rows that we will retrieve.
|
// 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) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
// This is called when a new Loader needs to be created. This
|
// 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);
|
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/>.
|
* 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.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -24,6 +24,7 @@ import android.os.Message;
|
|||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.openintents.openpgp.util.OpenPgpApi;
|
import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
import org.sufficientlysecure.htmltextview.HtmlTextView;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
@ -31,7 +32,10 @@ import org.sufficientlysecure.keychain.Id;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
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.SelectPublicKeyFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -41,6 +45,8 @@ import java.util.ArrayList;
|
|||||||
public class RemoteServiceActivity extends ActionBarActivity {
|
public class RemoteServiceActivity extends ActionBarActivity {
|
||||||
|
|
||||||
public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER";
|
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
|
public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX
|
||||||
+ "API_ACTIVITY_CACHE_PASSPHRASE";
|
+ "API_ACTIVITY_CACHE_PASSPHRASE";
|
||||||
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
|
public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX
|
||||||
@ -57,6 +63,8 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
|||||||
// register action
|
// register action
|
||||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||||
public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature";
|
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
|
// select pub keys action
|
||||||
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids";
|
||||||
public static final String EXTRA_MISSING_USER_IDS = "missing_user_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";
|
public static final String EXTRA_ERROR_MESSAGE = "error_message";
|
||||||
|
|
||||||
// register view
|
// register view
|
||||||
private AppSettingsFragment mSettingsFragment;
|
private AppSettingsFragment mAppSettingsFragment;
|
||||||
|
// create acc view
|
||||||
|
private AccountSettingsFragment mAccSettingsFragment;
|
||||||
// select pub keys view
|
// select pub keys view
|
||||||
private SelectPublicKeyFragment mSelectFragment;
|
private SelectPublicKeyFragment mSelectFragment;
|
||||||
|
|
||||||
@ -85,6 +95,7 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
|||||||
if (ACTION_REGISTER.equals(action)) {
|
if (ACTION_REGISTER.equals(action)) {
|
||||||
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
final String packageName = extras.getString(EXTRA_PACKAGE_NAME);
|
||||||
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE);
|
||||||
|
Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName);
|
||||||
|
|
||||||
// Inflate a "Done"/"Cancel" custom action bar view
|
// Inflate a "Done"/"Cancel" custom action bar view
|
||||||
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
ActionBarHelper.setTwoButtonView(getSupportActionBar(),
|
||||||
@ -94,13 +105,52 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
|||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// Allow
|
// 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!
|
// user needs to select a key!
|
||||||
if (mSettingsFragment.getAppSettings().getKeyId() == Id.key.none) {
|
if (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) {
|
||||||
mSettingsFragment.setErrorOnSelectKeyFragment(
|
mAccSettingsFragment.setErrorOnSelectKeyFragment(
|
||||||
getString(R.string.api_register_error_select_key));
|
getString(R.string.api_register_error_select_key));
|
||||||
} else {
|
} else {
|
||||||
ProviderHelper.insertApiApp(RemoteServiceActivity.this,
|
ProviderHelper.insertApiAccount(RemoteServiceActivity.this,
|
||||||
mSettingsFragment.getAppSettings());
|
KeychainContract.ApiAccounts.buildBaseUri(packageName),
|
||||||
|
mAccSettingsFragment.getAccSettings());
|
||||||
|
|
||||||
// give data through for new service call
|
// give data through for new service call
|
||||||
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
Intent resultData = extras.getParcelable(EXTRA_DATA);
|
||||||
@ -108,29 +158,43 @@ public class RemoteServiceActivity extends ActionBarActivity {
|
|||||||
RemoteServiceActivity.this.finish();
|
RemoteServiceActivity.this.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, R.string.api_register_disallow, R.drawable.ic_action_cancel,
|
}, R.string.api_settings_cancel, R.drawable.ic_action_cancel,
|
||||||
new View.OnClickListener() {
|
new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// Disallow
|
// Cancel
|
||||||
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
RemoteServiceActivity.this.setResult(RESULT_CANCELED);
|
||||||
RemoteServiceActivity.this.finish();
|
RemoteServiceActivity.this.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
setContentView(R.layout.api_app_register_activity);
|
setContentView(R.layout.api_remote_create_account);
|
||||||
|
|
||||||
mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById(
|
mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById(
|
||||||
R.id.api_app_settings_fragment);
|
R.id.api_account_settings_fragment);
|
||||||
|
|
||||||
AppSettings settings = new AppSettings(packageName, packageSignature);
|
AccountSettings settings = new AccountSettings(accName);
|
||||||
mSettingsFragment.setAppSettings(settings);
|
mAccSettingsFragment.setAccSettings(settings);
|
||||||
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
|
} else if (ACTION_CACHE_PASSPHRASE.equals(action)) {
|
||||||
long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
|
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)) {
|
} else if (ACTION_SELECT_PUB_KEYS.equals(action)) {
|
||||||
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS);
|
||||||
ArrayList<String> missingUserIds = intent
|
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
|
// set text on view
|
||||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text);
|
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
|
// set text on view
|
||||||
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
|
HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text);
|
@ -18,30 +18,61 @@
|
|||||||
package org.sufficientlysecure.keychain.service;
|
package org.sufficientlysecure.keychain.service;
|
||||||
|
|
||||||
import android.app.IntentService;
|
import android.app.IntentService;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
import org.sufficientlysecure.keychain.helper.FileHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
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.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.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
|
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.ArrayList;
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,35 +115,26 @@ public class KeychainIntentService extends IntentService
|
|||||||
// possible targets:
|
// possible targets:
|
||||||
public static final int TARGET_BYTES = 1;
|
public static final int TARGET_BYTES = 1;
|
||||||
public static final int TARGET_URI = 2;
|
public static final int TARGET_URI = 2;
|
||||||
public static final int TARGET_STREAM = 3;
|
|
||||||
|
|
||||||
// encrypt
|
// 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_USE_ASCII_ARMOR = "use_ascii_armor";
|
||||||
public static final String ENCRYPT_ENCRYPTION_KEYS_IDS = "encryption_keys_ids";
|
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_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_MESSAGE_BYTES = "message_bytes";
|
||||||
public static final String ENCRYPT_INPUT_FILE = "input_file";
|
public static final String ENCRYPT_INPUT_FILE = "input_file";
|
||||||
public static final String ENCRYPT_OUTPUT_FILE = "output_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
|
// 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_CIPHERTEXT_BYTES = "ciphertext_bytes";
|
||||||
public static final String DECRYPT_ASSUME_SYMMETRIC = "assume_symmetric";
|
public static final String DECRYPT_PASSPHRASE = "passphrase";
|
||||||
|
|
||||||
// save keyring
|
// save keyring
|
||||||
public static final String SAVE_KEYRING_NEW_PASSPHRASE = "new_passphrase";
|
public static final String SAVE_KEYRING_PARCEL = "save_parcel";
|
||||||
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_CAN_SIGN = "can_sign";
|
public static final String SAVE_KEYRING_CAN_SIGN = "can_sign";
|
||||||
|
|
||||||
|
|
||||||
// generate key
|
// generate key
|
||||||
public static final String GENERATE_KEY_ALGORITHM = "algorithm";
|
public static final String GENERATE_KEY_ALGORITHM = "algorithm";
|
||||||
public static final String GENERATE_KEY_KEY_SIZE = "key_size";
|
public static final String GENERATE_KEY_KEY_SIZE = "key_size";
|
||||||
@ -128,10 +150,9 @@ public class KeychainIntentService extends IntentService
|
|||||||
// export key
|
// export key
|
||||||
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
public static final String EXPORT_OUTPUT_STREAM = "export_output_stream";
|
||||||
public static final String EXPORT_FILENAME = "export_filename";
|
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_ALL = "export_all";
|
||||||
public static final String EXPORT_KEY_RING_MASTER_KEY_ID = "export_key_ring_id";
|
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
|
// upload key
|
||||||
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
|
public static final String UPLOAD_KEY_SERVER = "upload_key_server";
|
||||||
@ -150,17 +171,12 @@ public class KeychainIntentService extends IntentService
|
|||||||
*/
|
*/
|
||||||
// keys
|
// keys
|
||||||
public static final String RESULT_NEW_KEY = "new_key";
|
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
|
// encrypt
|
||||||
public static final String RESULT_SIGNATURE_BYTES = "signature_data";
|
public static final String RESULT_BYTES = "encrypted_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";
|
|
||||||
|
|
||||||
// decrypt/verify
|
// 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_DECRYPTED_BYTES = "decrypted_data";
|
||||||
public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature";
|
public static final String RESULT_DECRYPT_VERIFY_RESULT = "signature";
|
||||||
|
|
||||||
@ -172,10 +188,6 @@ public class KeychainIntentService extends IntentService
|
|||||||
// export
|
// export
|
||||||
public static final String RESULT_EXPORT = "exported";
|
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;
|
Messenger mMessenger;
|
||||||
|
|
||||||
private boolean mIsCanceled;
|
private boolean mIsCanceled;
|
||||||
@ -225,20 +237,17 @@ public class KeychainIntentService extends IntentService
|
|||||||
/* Input */
|
/* Input */
|
||||||
int target = data.getInt(TARGET);
|
int target = data.getInt(TARGET);
|
||||||
|
|
||||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
long signatureKeyId = data.getLong(ENCRYPT_SIGNATURE_KEY_ID);
|
||||||
String encryptionPassphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
String symmetricPassphrase = data.getString(ENCRYPT_SYMMETRIC_PASSPHRASE);
|
||||||
|
|
||||||
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
boolean useAsciiArmor = data.getBoolean(ENCRYPT_USE_ASCII_ARMOR);
|
||||||
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
long encryptionKeyIds[] = data.getLongArray(ENCRYPT_ENCRYPTION_KEYS_IDS);
|
||||||
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
|
int compressionId = data.getInt(ENCRYPT_COMPRESSION_ID);
|
||||||
boolean generateSignature = data.getBoolean(ENCRYPT_GENERATE_SIGNATURE);
|
InputStream inStream;
|
||||||
boolean signOnly = data.getBoolean(ENCRYPT_SIGN_ONLY);
|
long inLength;
|
||||||
|
InputData inputData;
|
||||||
InputStream inStream = null;
|
OutputStream outStream;
|
||||||
long inLength = -1;
|
// String streamFilename = null;
|
||||||
InputData inputData = null;
|
|
||||||
OutputStream outStream = null;
|
|
||||||
String streamFilename = null;
|
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case TARGET_BYTES: /* encrypting bytes directly */
|
case TARGET_BYTES: /* encrypting bytes directly */
|
||||||
byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES);
|
byte[] bytes = data.getByteArray(ENCRYPT_MESSAGE_BYTES);
|
||||||
@ -270,29 +279,30 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TARGET_STREAM: /* Encrypting stream from content uri */
|
// TODO: not used currently
|
||||||
Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
// case TARGET_STREAM: /* Encrypting stream from content uri */
|
||||||
|
// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||||
// InputStream
|
//
|
||||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
// // InputStream
|
||||||
inLength = PgpHelper.getLengthOfStream(in);
|
// InputStream in = getContentResolver().openInputStream(providerUri);
|
||||||
inputData = new InputData(in, inLength);
|
// inLength = PgpHelper.getLengthOfStream(in);
|
||||||
|
// inputData = new InputData(in, inLength);
|
||||||
// OutputStream
|
//
|
||||||
try {
|
// // OutputStream
|
||||||
while (true) {
|
// try {
|
||||||
streamFilename = PgpHelper.generateRandomFilename(32);
|
// while (true) {
|
||||||
if (streamFilename == null) {
|
// streamFilename = PgpHelper.generateRandomFilename(32);
|
||||||
throw new PgpGeneralException("couldn't generate random file name");
|
// if (streamFilename == null) {
|
||||||
}
|
// throw new PgpGeneralException("couldn't generate random file name");
|
||||||
openFileInput(streamFilename).close();
|
// }
|
||||||
}
|
// openFileInput(streamFilename).close();
|
||||||
} catch (FileNotFoundException e) {
|
// }
|
||||||
// found a name that isn't used yet
|
// } catch (FileNotFoundException e) {
|
||||||
}
|
// // found a name that isn't used yet
|
||||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
// }
|
||||||
|
// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||||
break;
|
//
|
||||||
|
// break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new PgpGeneralException("No target choosen!");
|
throw new PgpGeneralException("No target choosen!");
|
||||||
@ -304,45 +314,20 @@ public class KeychainIntentService extends IntentService
|
|||||||
new PgpSignEncrypt.Builder(this, inputData, outStream);
|
new PgpSignEncrypt.Builder(this, inputData, outStream);
|
||||||
builder.progress(this);
|
builder.progress(this);
|
||||||
|
|
||||||
if (generateSignature) {
|
builder.enableAsciiArmorOutput(useAsciiArmor)
|
||||||
Log.d(Constants.TAG, "generating signature...");
|
.compressionId(compressionId)
|
||||||
builder.enableAsciiArmorOutput(useAsciiArmor)
|
.symmetricEncryptionAlgorithm(
|
||||||
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
Preferences.getPreferences(this).getDefaultEncryptionAlgorithm())
|
||||||
.signatureKeyId(secretKeyId)
|
.signatureForceV3(Preferences.getPreferences(this).getForceV3Signatures())
|
||||||
.signatureHashAlgorithm(
|
.encryptionKeyIds(encryptionKeyIds)
|
||||||
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
.symmetricPassphrase(symmetricPassphrase)
|
||||||
.signaturePassphrase(
|
.signatureKeyId(signatureKeyId)
|
||||||
PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
.signatureHashAlgorithm(
|
||||||
|
Preferences.getPreferences(this).getDefaultHashAlgorithm())
|
||||||
|
.signaturePassphrase(
|
||||||
|
PassphraseCacheService.getCachedPassphrase(this, signatureKeyId));
|
||||||
|
|
||||||
builder.build().generateSignature();
|
builder.build().execute();
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
outStream.close();
|
outStream.close();
|
||||||
|
|
||||||
@ -352,33 +337,20 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case TARGET_BYTES:
|
case TARGET_BYTES:
|
||||||
if (useAsciiArmor) {
|
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||||
String output = new String(
|
|
||||||
((ByteArrayOutputStream) outStream).toByteArray());
|
resultData.putByteArray(RESULT_BYTES, output);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TARGET_URI:
|
case TARGET_URI:
|
||||||
// nothing, file was written, just send okay
|
// nothing, file was written, just send okay
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TARGET_STREAM:
|
// case TARGET_STREAM:
|
||||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
// String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||||
resultData.putString(RESULT_URI, uri);
|
// resultData.putString(RESULT_URI, uri);
|
||||||
|
//
|
||||||
break;
|
// break;
|
||||||
}
|
}
|
||||||
|
|
||||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||||
@ -392,15 +364,13 @@ public class KeychainIntentService extends IntentService
|
|||||||
/* Input */
|
/* Input */
|
||||||
int target = data.getInt(TARGET);
|
int target = data.getInt(TARGET);
|
||||||
|
|
||||||
long secretKeyId = data.getLong(ENCRYPT_SECRET_KEY_ID);
|
|
||||||
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
|
byte[] bytes = data.getByteArray(DECRYPT_CIPHERTEXT_BYTES);
|
||||||
boolean returnBytes = data.getBoolean(DECRYPT_RETURN_BYTES);
|
String passphrase = data.getString(DECRYPT_PASSPHRASE);
|
||||||
boolean assumeSymmetricEncryption = data.getBoolean(DECRYPT_ASSUME_SYMMETRIC);
|
|
||||||
|
|
||||||
InputStream inStream = null;
|
InputStream inStream;
|
||||||
long inLength = -1;
|
long inLength;
|
||||||
InputData inputData = null;
|
InputData inputData;
|
||||||
OutputStream outStream = null;
|
OutputStream outStream;
|
||||||
String streamFilename = null;
|
String streamFilename = null;
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case TARGET_BYTES: /* decrypting bytes directly */
|
case TARGET_BYTES: /* decrypting bytes directly */
|
||||||
@ -435,29 +405,30 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TARGET_STREAM: /* decrypting stream from content uri */
|
// TODO: not used, maybe contains code useful for new decrypt method for files?
|
||||||
Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
// case TARGET_STREAM: /* decrypting stream from content uri */
|
||||||
|
// Uri providerUri = (Uri) data.getParcelable(ENCRYPT_PROVIDER_URI);
|
||||||
// InputStream
|
//
|
||||||
InputStream in = getContentResolver().openInputStream(providerUri);
|
// // InputStream
|
||||||
inLength = PgpHelper.getLengthOfStream(in);
|
// InputStream in = getContentResolver().openInputStream(providerUri);
|
||||||
inputData = new InputData(in, inLength);
|
// inLength = PgpHelper.getLengthOfStream(in);
|
||||||
|
// inputData = new InputData(in, inLength);
|
||||||
// OutputStream
|
//
|
||||||
try {
|
// // OutputStream
|
||||||
while (true) {
|
// try {
|
||||||
streamFilename = PgpHelper.generateRandomFilename(32);
|
// while (true) {
|
||||||
if (streamFilename == null) {
|
// streamFilename = PgpHelper.generateRandomFilename(32);
|
||||||
throw new PgpGeneralException("couldn't generate random file name");
|
// if (streamFilename == null) {
|
||||||
}
|
// throw new PgpGeneralException("couldn't generate random file name");
|
||||||
openFileInput(streamFilename).close();
|
// }
|
||||||
}
|
// openFileInput(streamFilename).close();
|
||||||
} catch (FileNotFoundException e) {
|
// }
|
||||||
// found a name that isn't used yet
|
// } catch (FileNotFoundException e) {
|
||||||
}
|
// // found a name that isn't used yet
|
||||||
outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
// }
|
||||||
|
// outStream = openFileOutput(streamFilename, Context.MODE_PRIVATE);
|
||||||
break;
|
//
|
||||||
|
// break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new PgpGeneralException("No target choosen!");
|
throw new PgpGeneralException("No target choosen!");
|
||||||
@ -473,8 +444,8 @@ public class KeychainIntentService extends IntentService
|
|||||||
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
|
PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder(this, inputData, outStream);
|
||||||
builder.progressDialogUpdater(this);
|
builder.progressDialogUpdater(this);
|
||||||
|
|
||||||
builder.assumeSymmetric(assumeSymmetricEncryption)
|
builder.allowSymmetricDecryption(true)
|
||||||
.passphrase(PassphraseCacheService.getCachedPassphrase(this, secretKeyId));
|
.passphrase(passphrase);
|
||||||
|
|
||||||
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
PgpDecryptVerifyResult decryptVerifyResult = builder.build().execute();
|
||||||
|
|
||||||
@ -486,25 +457,18 @@ public class KeychainIntentService extends IntentService
|
|||||||
|
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case TARGET_BYTES:
|
case TARGET_BYTES:
|
||||||
if (returnBytes) {
|
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
||||||
byte output[] = ((ByteArrayOutputStream) outStream).toByteArray();
|
resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
|
||||||
resultData.putByteArray(RESULT_DECRYPTED_BYTES, output);
|
|
||||||
} else {
|
|
||||||
String output = new String(
|
|
||||||
((ByteArrayOutputStream) outStream).toByteArray());
|
|
||||||
resultData.putString(RESULT_DECRYPTED_STRING, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TARGET_URI:
|
case TARGET_URI:
|
||||||
// nothing, file was written, just send okay and verification bundle
|
// nothing, file was written, just send okay and verification bundle
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TARGET_STREAM:
|
// case TARGET_STREAM:
|
||||||
String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
// String uri = DataStream.buildDataStreamUri(streamFilename).toString();
|
||||||
resultData.putString(RESULT_URI, uri);
|
// resultData.putString(RESULT_URI, uri);
|
||||||
|
//
|
||||||
break;
|
// break;
|
||||||
}
|
}
|
||||||
|
|
||||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||||
@ -516,38 +480,42 @@ public class KeychainIntentService extends IntentService
|
|||||||
} else if (ACTION_SAVE_KEYRING.equals(action)) {
|
} else if (ACTION_SAVE_KEYRING.equals(action)) {
|
||||||
try {
|
try {
|
||||||
/* Input */
|
/* Input */
|
||||||
String oldPassPhrase = data.getString(SAVE_KEYRING_CURRENT_PASSPHRASE);
|
SaveKeyringParcel saveParams = data.getParcelable(SAVE_KEYRING_PARCEL);
|
||||||
String newPassPhrase = data.getString(SAVE_KEYRING_NEW_PASSPHRASE);
|
String oldPassphrase = saveParams.oldPassphrase;
|
||||||
|
String newPassphrase = saveParams.newPassphrase;
|
||||||
boolean canSign = true;
|
boolean canSign = true;
|
||||||
|
|
||||||
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
|
if (data.containsKey(SAVE_KEYRING_CAN_SIGN)) {
|
||||||
canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
|
canSign = data.getBoolean(SAVE_KEYRING_CAN_SIGN);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newPassPhrase == null) {
|
if (newPassphrase == null) {
|
||||||
newPassPhrase = oldPassPhrase;
|
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 */
|
/* Operation */
|
||||||
if (!canSign) {
|
if (!canSign) {
|
||||||
keyOperations.changeSecretKeyPassphrase(
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 50, 100));
|
||||||
ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId),
|
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
||||||
oldPassPhrase, newPassPhrase);
|
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 {
|
} else {
|
||||||
PGPPublicKey pubkey = ProviderHelper.getPGPPublicKeyByKeyId(this, masterKeyId);
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 90, 100));
|
||||||
keyOperations.buildSecretKey(userIds, keys, keysUsages, keysExpiryDates,
|
PGPSecretKeyRing privkey = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
||||||
pubkey, oldPassPhrase, newPassPhrase);
|
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 */
|
/* Output */
|
||||||
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
sendMessageToHandler(KeychainIntentServiceHandler.MESSAGE_OKAY);
|
||||||
@ -563,7 +531,7 @@ public class KeychainIntentService extends IntentService
|
|||||||
boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
|
boolean masterKey = data.getBoolean(GENERATE_KEY_MASTER_KEY);
|
||||||
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
PgpKeyOperation keyOperations = new PgpKeyOperation(this, this);
|
PgpKeyOperation keyOperations = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||||
PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize,
|
PGPSecretKey newKey = keyOperations.createKey(algorithm, keysize,
|
||||||
passphrase, masterKey);
|
passphrase, masterKey);
|
||||||
|
|
||||||
@ -583,24 +551,37 @@ public class KeychainIntentService extends IntentService
|
|||||||
try {
|
try {
|
||||||
/* Input */
|
/* Input */
|
||||||
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
String passphrase = data.getString(GENERATE_KEY_SYMMETRIC_PASSPHRASE);
|
||||||
|
ArrayList<PGPSecretKey> newKeys = new ArrayList<PGPSecretKey>();
|
||||||
|
ArrayList<Integer> keyUsageList = new ArrayList<Integer>();
|
||||||
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
int keysTotal = 2;
|
int keysTotal = 3;
|
||||||
int keysCreated = 0;
|
int keysCreated = 0;
|
||||||
setProgress(
|
setProgress(
|
||||||
getApplicationContext().getResources().
|
getApplicationContext().getResources().
|
||||||
getQuantityString(R.plurals.progress_generating, keysTotal),
|
getQuantityString(R.plurals.progress_generating, keysTotal),
|
||||||
keysCreated,
|
keysCreated,
|
||||||
keysTotal);
|
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,
|
PGPSecretKey masterKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||||
4096, passphrase, true);
|
4096, passphrase, true);
|
||||||
|
newKeys.add(masterKey);
|
||||||
|
keyUsageList.add(KeyFlags.CERTIFY_OTHER);
|
||||||
keysCreated++;
|
keysCreated++;
|
||||||
setProgress(keysCreated, keysTotal);
|
setProgress(keysCreated, keysTotal);
|
||||||
|
|
||||||
PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
PGPSecretKey subKey = keyOperations.createKey(Id.choice.algorithm.rsa,
|
||||||
4096, passphrase, false);
|
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++;
|
keysCreated++;
|
||||||
setProgress(keysCreated, keysTotal);
|
setProgress(keysCreated, keysTotal);
|
||||||
|
|
||||||
@ -608,11 +589,11 @@ public class KeychainIntentService extends IntentService
|
|||||||
// for sign
|
// for sign
|
||||||
|
|
||||||
/* Output */
|
/* Output */
|
||||||
|
|
||||||
Bundle resultData = new Bundle();
|
Bundle resultData = new Bundle();
|
||||||
resultData.putByteArray(RESULT_NEW_KEY,
|
resultData.putByteArray(RESULT_NEW_KEY,
|
||||||
PgpConversionHelper.PGPSecretKeyToBytes(masterKey));
|
PgpConversionHelper.PGPSecretKeyArrayListToBytes(newKeys));
|
||||||
resultData.putByteArray(RESULT_NEW_KEY2,
|
resultData.putIntegerArrayList(RESULT_KEY_USAGES, keyUsageList);
|
||||||
PgpConversionHelper.PGPSecretKeyToBytes(subKey));
|
|
||||||
|
|
||||||
OtherHelper.logDebugBundle(resultData, "resultData");
|
OtherHelper.logDebugBundle(resultData, "resultData");
|
||||||
|
|
||||||
@ -657,53 +638,50 @@ public class KeychainIntentService extends IntentService
|
|||||||
} else if (ACTION_EXPORT_KEYRING.equals(action)) {
|
} else if (ACTION_EXPORT_KEYRING.equals(action)) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
/* Input */
|
boolean exportSecret = data.getBoolean(EXPORT_SECRET, false);
|
||||||
int keyType = Id.type.public_key;
|
long[] masterKeyIds = data.getLongArray(EXPORT_KEY_RING_MASTER_KEY_ID);
|
||||||
if (data.containsKey(EXPORT_KEY_TYPE)) {
|
|
||||||
keyType = data.getInt(EXPORT_KEY_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
String outputFile = data.getString(EXPORT_FILENAME);
|
String outputFile = data.getString(EXPORT_FILENAME);
|
||||||
|
|
||||||
long[] rowIds = new long[0];
|
// If not exporting all keys get the masterKeyIds of the keys to export from the intent
|
||||||
|
|
||||||
// If not exporting all keys get the rowIds of the keys to export from the intent
|
|
||||||
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
boolean exportAll = data.getBoolean(EXPORT_ALL);
|
||||||
if (!exportAll) {
|
|
||||||
rowIds = data.getLongArray(EXPORT_KEY_RING_ROW_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Operation */
|
|
||||||
|
|
||||||
// check if storage is ready
|
// check if storage is ready
|
||||||
if (!FileHelper.isStorageMounted(outputFile)) {
|
if (!FileHelper.isStorageMounted(outputFile)) {
|
||||||
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
|
throw new PgpGeneralException(getString(R.string.error_external_storage_not_ready));
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutputStream
|
ArrayList<Long> publicMasterKeyIds = new ArrayList<Long>();
|
||||||
FileOutputStream outStream = new FileOutputStream(outputFile);
|
ArrayList<Long> secretMasterKeyIds = new ArrayList<Long>();
|
||||||
|
|
||||||
ArrayList<Long> keyRingRowIds = new ArrayList<Long>();
|
String selection = null;
|
||||||
if (exportAll) {
|
if(!exportAll) {
|
||||||
|
selection = KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN( ";
|
||||||
// get all key ring row ids based on export type
|
for(long l : masterKeyIds) {
|
||||||
if (keyType == Id.type.public_key) {
|
selection += Long.toString(l) + ",";
|
||||||
keyRingRowIds = ProviderHelper.getPublicKeyRingsRowIds(this);
|
|
||||||
} else {
|
|
||||||
keyRingRowIds = ProviderHelper.getSecretKeyRingsRowIds(this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (long rowId : rowIds) {
|
|
||||||
keyRingRowIds.add(rowId);
|
|
||||||
}
|
}
|
||||||
|
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);
|
PgpImportExport pgpImportExport = new PgpImportExport(this, this, this);
|
||||||
|
Bundle resultData = pgpImportExport
|
||||||
resultData = pgpImportExport
|
.exportKeyRings(publicMasterKeyIds, secretMasterKeyIds,
|
||||||
.exportKeyRings(keyRingRowIds, keyType, outStream);
|
new FileOutputStream(outputFile));
|
||||||
|
|
||||||
if (mIsCanceled) {
|
if (mIsCanceled) {
|
||||||
boolean isDeleted = new File(outputFile).delete();
|
boolean isDeleted = new File(outputFile).delete();
|
||||||
@ -747,45 +725,54 @@ public class KeychainIntentService extends IntentService
|
|||||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||||
|
|
||||||
for (ImportKeysListEntry entry : entries) {
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// create PGPKeyRing object based on downloaded armored key
|
||||||
* TODO: copied from ImportKeysListLoader
|
PGPKeyRing downloadedKey = null;
|
||||||
*
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
BufferedInputStream bufferedInput =
|
BufferedInputStream bufferedInput =
|
||||||
new BufferedInputStream(new ByteArrayInputStream(downloadedKey));
|
new BufferedInputStream(new ByteArrayInputStream(downloadedKeyBytes));
|
||||||
try {
|
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)
|
// get first object in block
|
||||||
while (bufferedInput.available() > 0) {
|
Object obj;
|
||||||
InputStream in = PGPUtil.getDecoderStream(bufferedInput);
|
if ((obj = objectFactory.nextObject()) != null) {
|
||||||
PGPObjectFactory objectFactory = new PGPObjectFactory(in);
|
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
||||||
|
|
||||||
// go through all objects in this block
|
if (obj instanceof PGPKeyRing) {
|
||||||
Object obj;
|
downloadedKey = (PGPKeyRing) obj;
|
||||||
while ((obj = objectFactory.nextObject()) != null) {
|
} else {
|
||||||
Log.d(Constants.TAG, "Found class: " + obj.getClass());
|
throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
|
||||||
|
|
||||||
if (obj instanceof PGPKeyRing) {
|
|
||||||
PGPKeyRing newKeyring = (PGPKeyRing) obj;
|
|
||||||
|
|
||||||
entry.setBytes(newKeyring.getEncoded());
|
|
||||||
} else {
|
|
||||||
Log.e(Constants.TAG, "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);
|
Intent importIntent = new Intent(this, KeychainIntentService.class);
|
||||||
importIntent.setAction(ACTION_IMPORT_KEYRING);
|
importIntent.setAction(ACTION_IMPORT_KEYRING);
|
||||||
Bundle importData = new Bundle();
|
Bundle importData = new Bundle();
|
||||||
@ -809,16 +796,24 @@ public class KeychainIntentService extends IntentService
|
|||||||
ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
|
ArrayList<String> userIds = data.getStringArrayList(CERTIFY_KEY_UIDS);
|
||||||
|
|
||||||
/* Operation */
|
/* Operation */
|
||||||
String signaturePassPhrase = PassphraseCacheService.getCachedPassphrase(this,
|
String signaturePassphrase = PassphraseCacheService.getCachedPassphrase(this,
|
||||||
masterKeyId);
|
masterKeyId);
|
||||||
|
if (signaturePassphrase == null) {
|
||||||
|
throw new PgpGeneralException("Unable to obtain passphrase");
|
||||||
|
}
|
||||||
|
|
||||||
PgpKeyOperation keyOperation = new PgpKeyOperation(this, this);
|
PgpKeyOperation keyOperation = new PgpKeyOperation(new ProgressScaler(this, 0, 100, 100));
|
||||||
PGPPublicKeyRing signedPubKeyRing = keyOperation.certifyKey(masterKeyId, pubKeyId,
|
PGPPublicKeyRing publicRing = ProviderHelper.getPGPPublicKeyRing(this, pubKeyId);
|
||||||
userIds, signaturePassPhrase);
|
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
|
// store the signed key in our local cache
|
||||||
PgpImportExport pgpImportExport = new PgpImportExport(this, null);
|
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) {
|
if (retval != Id.return_value.ok && retval != Id.return_value.updated) {
|
||||||
throw new PgpGeneralException("Failed to store signed key in local cache");
|
throw new PgpGeneralException("Failed to store signed key in local cache");
|
||||||
}
|
}
|
||||||
@ -835,6 +830,10 @@ public class KeychainIntentService extends IntentService
|
|||||||
if (this.mIsCanceled) {
|
if (this.mIsCanceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// contextualize the exception, if necessary
|
||||||
|
if (e instanceof PgpGeneralMsgIdException) {
|
||||||
|
e = ((PgpGeneralMsgIdException) e).getContextualized(this);
|
||||||
|
}
|
||||||
Log.e(Constants.TAG, "ApgService Exception: ", e);
|
Log.e(Constants.TAG, "ApgService Exception: ", e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
||||||
|
@ -24,19 +24,29 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
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.Log;
|
||||||
import android.util.LongSparseArray;
|
|
||||||
import org.spongycastle.openpgp.PGPException;
|
import org.spongycastle.openpgp.PGPException;
|
||||||
import org.spongycastle.openpgp.PGPPrivateKey;
|
import org.spongycastle.openpgp.PGPPrivateKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKey;
|
import org.spongycastle.openpgp.PGPSecretKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -86,7 +96,7 @@ public class PassphraseCacheService extends Service {
|
|||||||
|
|
||||||
Intent intent = new Intent(context, PassphraseCacheService.class);
|
Intent intent = new Intent(context, PassphraseCacheService.class);
|
||||||
intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);
|
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_PASSPHRASE, passphrase);
|
||||||
intent.putExtra(EXTRA_KEY_ID, keyId);
|
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
|
// try to get master key id which is used as an identifier for cached passphrases
|
||||||
long masterKeyId = keyId;
|
long masterKeyId = keyId;
|
||||||
if (masterKeyId != Id.key.symmetric) {
|
if (masterKeyId != Id.key.symmetric) {
|
||||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(this, keyId);
|
masterKeyId = ProviderHelper.getMasterKeyId(this,
|
||||||
if (keyRing == null) {
|
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(keyId)));
|
||||||
|
// Failure
|
||||||
|
if(masterKeyId == 0)
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
PGPSecretKey masterKey = PgpKeyHelper.getMasterKey(keyRing);
|
|
||||||
if (masterKey == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
masterKeyId = masterKey.getKeyID();
|
|
||||||
}
|
}
|
||||||
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
|
Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId " + masterKeyId);
|
||||||
|
|
||||||
@ -202,8 +208,7 @@ public class PassphraseCacheService extends Service {
|
|||||||
public static boolean hasPassphrase(Context context, long secretKeyId) {
|
public static boolean hasPassphrase(Context context, long secretKeyId) {
|
||||||
// check if the key has no passphrase
|
// check if the key has no passphrase
|
||||||
try {
|
try {
|
||||||
PGPSecretKeyRing secRing = ProviderHelper
|
PGPSecretKeyRing secRing = ProviderHelper.getPGPSecretKeyRing(context, secretKeyId);
|
||||||
.getPGPSecretKeyRingByKeyId(context, secretKeyId);
|
|
||||||
PGPSecretKey secretKey = null;
|
PGPSecretKey secretKey = null;
|
||||||
boolean foundValidKey = false;
|
boolean foundValidKey = false;
|
||||||
for (Iterator keys = secRing.getSecretKeys(); keys.hasNext(); ) {
|
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) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2011 Senecaso
|
* Copyright (C) 2011 Senecaso
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* 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.support.v7.app.ActionBarActivity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
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.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
import org.spongycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
import org.sufficientlysecure.keychain.helper.Preferences;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
@ -140,8 +144,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
}
|
}
|
||||||
Log.e(Constants.TAG, "uri: " + mDataUri);
|
Log.e(Constants.TAG, "uri: " + mDataUri);
|
||||||
|
|
||||||
PGPPublicKeyRing signKey = (PGPPublicKeyRing) ProviderHelper.getPGPKeyRing(this, mDataUri);
|
|
||||||
|
|
||||||
mUserIds = (ListView) findViewById(R.id.user_ids);
|
mUserIds = (ListView) findViewById(R.id.user_ids);
|
||||||
|
|
||||||
mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true);
|
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_KEYRING, null, this);
|
||||||
getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, 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 =
|
static final String[] KEYRING_PROJECTION =
|
||||||
new String[] {
|
new String[] {
|
||||||
KeychainContract.KeyRings._ID,
|
KeychainContract.KeyRings._ID,
|
||||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
KeychainContract.Keys.MASTER_KEY_ID,
|
||||||
KeychainContract.Keys.FINGERPRINT,
|
KeychainContract.Keys.FINGERPRINT,
|
||||||
KeychainContract.UserIds.USER_ID,
|
KeychainContract.UserIds.USER_ID,
|
||||||
};
|
};
|
||||||
@ -184,11 +178,13 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
switch(id) {
|
switch(id) {
|
||||||
case LOADER_ID_KEYRING:
|
case LOADER_ID_KEYRING: {
|
||||||
return new CursorLoader(this, mDataUri, KEYRING_PROJECTION, null, null, null);
|
Uri uri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||||
|
return new CursorLoader(this, uri, KEYRING_PROJECTION, null, null, null);
|
||||||
|
}
|
||||||
case LOADER_ID_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
Uri uri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
||||||
return new CursorLoader(this, baseUri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER);
|
return new CursorLoader(this, uri, USER_IDS_PROJECTION, null, null, USER_IDS_SORT_ORDER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -200,20 +196,18 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
case LOADER_ID_KEYRING:
|
case LOADER_ID_KEYRING:
|
||||||
// the first key here is our master key
|
// the first key here is our master key
|
||||||
if (data.moveToFirst()) {
|
if (data.moveToFirst()) {
|
||||||
long keyId = data.getLong(INDEX_MASTER_KEY_ID);
|
// TODO: put findViewById in onCreate!
|
||||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
mPubKeyId = data.getLong(INDEX_MASTER_KEY_ID);
|
||||||
|
String keyIdStr = PgpKeyHelper.convertKeyIdToHexShort(mPubKeyId);
|
||||||
((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
|
((TextView) findViewById(R.id.key_id)).setText(keyIdStr);
|
||||||
|
|
||||||
String mainUserId = data.getString(INDEX_USER_ID);
|
String mainUserId = data.getString(INDEX_USER_ID);
|
||||||
((TextView) findViewById(R.id.main_user_id)).setText(mainUserId);
|
((TextView) findViewById(R.id.main_user_id)).setText(mainUserId);
|
||||||
|
|
||||||
byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
|
byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
|
||||||
if (fingerprintBlob == null) {
|
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
|
||||||
// FALLBACK for old database entries
|
((TextView) findViewById(R.id.fingerprint))
|
||||||
fingerprintBlob = ProviderHelper.getFingerprint(this, mDataUri);
|
.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
|
||||||
}
|
|
||||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
|
||||||
((TextView) findViewById(R.id.fingerprint)).setText(OtherHelper.colorizeFingerprint(fingerprint));
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LOADER_ID_USER_IDS:
|
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
|
* handles the UI bits of the signing process on the UI thread
|
||||||
*/
|
*/
|
||||||
private void initiateSigning() {
|
private void initiateSigning() {
|
||||||
PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRingByMasterKeyId(this, mPubKeyId);
|
PGPPublicKeyRing pubring = ProviderHelper.getPGPPublicKeyRing(this, mPubKeyId);
|
||||||
if (pubring != null) {
|
if (pubring != null) {
|
||||||
// if we have already signed this key, dont bother doing it again
|
// if we have already signed this key, dont bother doing it again
|
||||||
boolean alreadySigned = false;
|
boolean alreadySigned = false;
|
||||||
@ -284,7 +252,15 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
*/
|
*/
|
||||||
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
String passphrase = PassphraseCacheService.getCachedPassphrase(this, mMasterKeyId);
|
||||||
if (passphrase == null) {
|
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
|
// bail out; need to wait until the user has entered the passphrase before trying again
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@ -307,7 +283,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
|
|
||||||
// Bail out if there is not at least one user id selected
|
// Bail out if there is not at least one user id selected
|
||||||
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
|
ArrayList<String> userIds = mUserIdsAdapter.getSelectedUserIds();
|
||||||
if(userIds.isEmpty()) {
|
if (userIds.isEmpty()) {
|
||||||
Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
|
Toast.makeText(CertifyKeyActivity.this, "No User IDs to sign selected!",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
@ -327,11 +303,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
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,
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||||
getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
|
getString(R.string.progress_signing), ProgressDialog.STYLE_SPINNER) {
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||||
@ -380,11 +356,11 @@ public class CertifyKeyActivity extends ActionBarActivity implements
|
|||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
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,
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
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>
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -17,180 +17,48 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.support.v4.view.PagerTabStrip;
|
||||||
import android.os.Message;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.os.Messenger;
|
import android.widget.Toast;
|
||||||
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 org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
|
||||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
||||||
import org.sufficientlysecure.keychain.helper.FileHelper;
|
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.PgpHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
|
||||||
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.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
public class DecryptActivity extends DrawerActivity {
|
public class DecryptActivity extends DrawerActivity {
|
||||||
|
|
||||||
/* Intents */
|
/* Intents */
|
||||||
// without permission
|
|
||||||
public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
|
public static final String ACTION_DECRYPT = Constants.INTENT_PREFIX + "DECRYPT";
|
||||||
|
|
||||||
/* EXTRA keys for input */
|
/* EXTRA keys for input */
|
||||||
public static final String EXTRA_TEXT = "text";
|
public static final String EXTRA_TEXT = "text";
|
||||||
|
|
||||||
private static final int RESULT_CODE_LOOKUP_KEY = 0x00007006;
|
ViewPager mViewPager;
|
||||||
private static final int RESULT_CODE_FILE = 0x00007003;
|
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;
|
private static final int PAGER_TAB_MESSAGE = 0;
|
||||||
|
private static final int PAGER_TAB_FILE = 1;
|
||||||
// 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 void initView() {
|
private void initView() {
|
||||||
mSource = (ViewFlipper) findViewById(R.id.source);
|
mViewPager = (ViewPager) findViewById(R.id.decrypt_pager);
|
||||||
mSourceLabel = (TextView) findViewById(R.id.sourceLabel);
|
mPagerTabStrip = (PagerTabStrip) findViewById(R.id.decrypt_pager_tab_strip);
|
||||||
mSourcePrevious = (ImageView) findViewById(R.id.sourcePrevious);
|
|
||||||
mSourceNext = (ImageView) findViewById(R.id.sourceNext);
|
|
||||||
|
|
||||||
mSourcePrevious.setClickable(true);
|
mTabsAdapter = new PagerTabStripAdapter(this);
|
||||||
mSourcePrevious.setOnClickListener(new OnClickListener() {
|
mViewPager.setAdapter(mTabsAdapter);
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -206,68 +74,17 @@ public class DecryptActivity extends DrawerActivity {
|
|||||||
|
|
||||||
setupDrawerNavigation(savedInstanceState);
|
setupDrawerNavigation(savedInstanceState);
|
||||||
|
|
||||||
// Handle intent actions
|
// Handle intent actions, maybe changes the bundles
|
||||||
handleActions(getIntent());
|
handleActions(getIntent());
|
||||||
|
|
||||||
if (mSource.getCurrentView().getId() == R.id.sourceMessage
|
mTabsAdapter.addTab(DecryptMessageFragment.class,
|
||||||
&& mMessage.getText().length() == 0) {
|
mMessageFragmentBundle, getString(R.string.label_message));
|
||||||
|
mTabsAdapter.addTab(DecryptFileFragment.class,
|
||||||
CharSequence clipboardText = ClipboardReflection.getClipboardText(this);
|
mFileFragmentBundle, getString(R.string.label_file));
|
||||||
|
mViewPager.setCurrentItem(mSwitchToTab);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all actions with this intent
|
* Handles all actions with this intent
|
||||||
*
|
*
|
||||||
@ -316,22 +133,26 @@ public class DecryptActivity extends DrawerActivity {
|
|||||||
* Main Actions
|
* Main Actions
|
||||||
*/
|
*/
|
||||||
if (ACTION_DECRYPT.equals(action) && textData != null) {
|
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);
|
Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(textData);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
Log.d(Constants.TAG, "PGP_MESSAGE matched");
|
||||||
textData = matcher.group(1);
|
textData = matcher.group(1);
|
||||||
// replace non breakable spaces
|
// replace non breakable spaces
|
||||||
textData = textData.replaceAll("\\xa0", " ");
|
textData = textData.replaceAll("\\xa0", " ");
|
||||||
mMessage.setText(textData);
|
|
||||||
|
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
|
||||||
|
mSwitchToTab = PAGER_TAB_MESSAGE;
|
||||||
} else {
|
} else {
|
||||||
matcher = PgpHelper.PGP_SIGNED_MESSAGE.matcher(textData);
|
matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(textData);
|
||||||
if (matcher.matches()) {
|
if (matcher.matches()) {
|
||||||
Log.d(Constants.TAG, "PGP_SIGNED_MESSAGE matched");
|
Log.d(Constants.TAG, "PGP_CLEARTEXT_SIGNATURE matched");
|
||||||
textData = matcher.group(1);
|
textData = matcher.group(1);
|
||||||
// replace non breakable spaces
|
// replace non breakable spaces
|
||||||
textData = textData.replaceAll("\\xa0", " ");
|
textData = textData.replaceAll("\\xa0", " ");
|
||||||
mMessage.setText(textData);
|
|
||||||
|
mMessageFragmentBundle.putString(DecryptMessageFragment.ARG_CIPHERTEXT, textData);
|
||||||
|
mSwitchToTab = PAGER_TAB_MESSAGE;
|
||||||
} else {
|
} else {
|
||||||
Log.d(Constants.TAG, "Nothing matched!");
|
Log.d(Constants.TAG, "Nothing matched!");
|
||||||
}
|
}
|
||||||
@ -341,17 +162,12 @@ public class DecryptActivity extends DrawerActivity {
|
|||||||
String path = FileHelper.getPath(this, uri);
|
String path = FileHelper.getPath(this, uri);
|
||||||
|
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
mInputFilename = path;
|
mFileFragmentBundle.putString(DecryptFileFragment.ARG_FILENAME, path);
|
||||||
mFilename.setText(mInputFilename);
|
mSwitchToTab = PAGER_TAB_FILE;
|
||||||
guessOutputFilename();
|
|
||||||
mSource.setInAnimation(null);
|
|
||||||
mSource.setOutAnimation(null);
|
|
||||||
while (mSource.getCurrentView().getId() != R.id.sourceFile) {
|
|
||||||
mSource.showNext();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.e(Constants.TAG,
|
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)
|
Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
// end activity
|
// 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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ActionBarDrawerToggle;
|
import android.support.v4.app.ActionBarDrawerToggle;
|
||||||
import android.support.v4.view.GravityCompat;
|
import android.support.v4.view.GravityCompat;
|
||||||
import android.support.v4.widget.DrawerLayout;
|
import android.support.v4.widget.DrawerLayout;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
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.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.FontAwesomeText;
|
import com.beardedhen.androidbootstrap.FontAwesomeText;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.service.remote.RegisteredAppsListActivity;
|
|
||||||
|
|
||||||
public class DrawerActivity extends ActionBarActivity {
|
public class DrawerActivity extends ActionBarActivity {
|
||||||
private DrawerLayout mDrawerLayout;
|
private DrawerLayout mDrawerLayout;
|
||||||
@ -42,10 +49,8 @@ public class DrawerActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
private CharSequence mDrawerTitle;
|
private CharSequence mDrawerTitle;
|
||||||
private CharSequence mTitle;
|
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 Class mSelectedItem;
|
||||||
|
|
||||||
private static final int MENU_ID_PREFERENCE = 222;
|
private static final int MENU_ID_PREFERENCE = 222;
|
||||||
@ -55,10 +60,22 @@ public class DrawerActivity extends ActionBarActivity {
|
|||||||
mDrawerTitle = getString(R.string.app_name);
|
mDrawerTitle = getString(R.string.app_name);
|
||||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||||
mDrawerList = (ListView) findViewById(R.id.left_drawer);
|
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
|
// if the left margin of the loaded layout is close to the
|
||||||
// opens
|
// one used in tablets then set drawer as open and locked
|
||||||
mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
|
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[]{
|
NavItem mItemIconTexts[] = new NavItem[]{
|
||||||
new NavItem("fa-user", getString(R.string.nav_contacts)),
|
new NavItem("fa-user", getString(R.string.nav_contacts)),
|
||||||
@ -73,8 +90,11 @@ public class DrawerActivity extends ActionBarActivity {
|
|||||||
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
|
||||||
|
|
||||||
// enable ActionBar app icon to behave as action to toggle nav drawer
|
// enable ActionBar app icon to behave as action to toggle nav drawer
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
// if the drawer is not locked
|
||||||
getSupportActionBar().setHomeButtonEnabled(true);
|
if (!mIsDrawerLocked) {
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
// ActionBarDrawerToggle ties together the the proper interactions
|
// ActionBarDrawerToggle ties together the the proper interactions
|
||||||
// between the sliding drawer and the action bar app icon
|
// between the sliding drawer and the action bar app icon
|
||||||
@ -86,19 +106,8 @@ public class DrawerActivity extends ActionBarActivity {
|
|||||||
) {
|
) {
|
||||||
public void onDrawerClosed(View view) {
|
public void onDrawerClosed(View view) {
|
||||||
getSupportActionBar().setTitle(mTitle);
|
getSupportActionBar().setTitle(mTitle);
|
||||||
// creates call to onPrepareOptionsMenu()
|
|
||||||
supportInvalidateOptionsMenu();
|
|
||||||
|
|
||||||
// call intent activity if selected
|
callIntentForDrawerItem(mSelectedItem);
|
||||||
if (mSelectedItem != null) {
|
|
||||||
finish();
|
|
||||||
overridePendingTransition(0, 0);
|
|
||||||
|
|
||||||
Intent intent = new Intent(DrawerActivity.this, mSelectedItem);
|
|
||||||
startActivity(intent);
|
|
||||||
// disable animation of activity start
|
|
||||||
overridePendingTransition(0, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDrawerOpened(View drawerView) {
|
public void onDrawerOpened(View drawerView) {
|
||||||
@ -108,33 +117,56 @@ public class DrawerActivity extends ActionBarActivity {
|
|||||||
supportInvalidateOptionsMenu();
|
supportInvalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
mDrawerLayout.setDrawerListener(mDrawerToggle);
|
|
||||||
|
|
||||||
// if (savedInstanceState == null) {
|
if (!mIsDrawerLocked) {
|
||||||
// selectItem(0);
|
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
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
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_PREFERENCE, 100, R.string.menu_preferences);
|
||||||
menu.add(42, MENU_ID_HELP, 101, R.string.menu_help);
|
menu.add(42, MENU_ID_HELP, 101, R.string.menu_help);
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
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
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (mDrawerToggle == null) {
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
// The action bar home/up action should open or close the drawer.
|
// The action bar home/up action should open or close the drawer.
|
||||||
// ActionBarDrawerToggle will take care of this.
|
// ActionBarDrawerToggle will take care of this.
|
||||||
if (mDrawerToggle.onOptionsItemSelected(item)) {
|
if (mDrawerToggle.onOptionsItemSelected(item)) {
|
||||||
@ -155,26 +187,11 @@ public class DrawerActivity extends ActionBarActivity {
|
|||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
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 {
|
private class DrawerItemClickListener implements ListView.OnItemClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
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) {
|
private void selectItem(int position) {
|
||||||
// update selected item and title, then close the drawer
|
// update selected item and title, then close the drawer
|
||||||
mDrawerList.setItemChecked(position, true);
|
mDrawerList.setItemChecked(position, true);
|
||||||
// setTitle(mDrawerTitles[position]);
|
|
||||||
mDrawerLayout.closeDrawer(mDrawerList);
|
|
||||||
// set selected class
|
// 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) {
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
// Sync the toggle state after onRestoreInstanceState has occurred.
|
// Sync the toggle state after onRestoreInstanceState has occurred.
|
||||||
mDrawerToggle.syncState();
|
if (mDrawerToggle != null) {
|
||||||
|
mDrawerToggle.syncState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigurationChanged(Configuration newConfig) {
|
public void onConfigurationChanged(Configuration newConfig) {
|
||||||
super.onConfigurationChanged(newConfig);
|
super.onConfigurationChanged(newConfig);
|
||||||
// Pass any configuration change to the drawer toggles
|
// Pass any configuration change to the drawer toggles
|
||||||
mDrawerToggle.onConfigurationChanged(newConfig);
|
if (mDrawerToggle != null) {
|
||||||
|
mDrawerToggle.onConfigurationChanged(newConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NavItem {
|
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>
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -18,6 +18,7 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@ -27,32 +28,45 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
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.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
|
import com.devspark.appmsg.AppMsg;
|
||||||
|
|
||||||
import org.spongycastle.openpgp.PGPSecretKey;
|
import org.spongycastle.openpgp.PGPSecretKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
|
|
||||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
import org.sufficientlysecure.keychain.service.KeychainIntentService;
|
||||||
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
|
||||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
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.DeleteKeyDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
|
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.KeyEditor;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.SectionView;
|
import org.sufficientlysecure.keychain.ui.widget.SectionView;
|
||||||
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
|
import org.sufficientlysecure.keychain.ui.widget.UserIdEditor;
|
||||||
@ -61,9 +75,10 @@ import org.sufficientlysecure.keychain.util.Log;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
public class EditKeyActivity extends ActionBarActivity {
|
public class EditKeyActivity extends ActionBarActivity implements EditorListener {
|
||||||
|
|
||||||
// Actions for internal use only:
|
// Actions for internal use only:
|
||||||
public static final String ACTION_CREATE_KEY = Constants.INTENT_PREFIX + "CREATE_KEY";
|
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_NO_PASSPHRASE = "no_passphrase";
|
||||||
public static final String EXTRA_GENERATE_DEFAULT_KEYS = "generate_default_keys";
|
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
|
// EDIT
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
|
||||||
@ -87,9 +98,11 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
private SectionView mKeysView;
|
private SectionView mKeysView;
|
||||||
|
|
||||||
private String mCurrentPassphrase = null;
|
private String mCurrentPassphrase = null;
|
||||||
private String mNewPassPhrase = null;
|
private String mNewPassphrase = null;
|
||||||
private String mSavedNewPassPhrase = null;
|
private String mSavedNewPassphrase = null;
|
||||||
private boolean mIsPassPhraseSet;
|
private boolean mIsPassphraseSet;
|
||||||
|
private boolean mNeedsSaving;
|
||||||
|
private boolean mIsBrandNewKeyring = false;
|
||||||
|
|
||||||
private BootstrapButton mChangePassphrase;
|
private BootstrapButton mChangePassphrase;
|
||||||
|
|
||||||
@ -102,12 +115,37 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
ExportHelper mExportHelper;
|
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
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mExportHelper = new ExportHelper(this);
|
mExportHelper = new ExportHelper(this);
|
||||||
|
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setIcon(android.R.color.transparent);
|
||||||
|
getSupportActionBar().setHomeButtonEnabled(true);
|
||||||
|
|
||||||
mUserIds = new Vector<String>();
|
mUserIds = new Vector<String>();
|
||||||
mKeys = new Vector<PGPSecretKey>();
|
mKeys = new Vector<PGPSecretKey>();
|
||||||
mKeysUsages = new Vector<Integer>();
|
mKeysUsages = new Vector<Integer>();
|
||||||
@ -128,24 +166,10 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
* @param intent
|
* @param intent
|
||||||
*/
|
*/
|
||||||
private void handleActionCreateKey(Intent 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();
|
Bundle extras = intent.getExtras();
|
||||||
|
|
||||||
mCurrentPassphrase = "";
|
mCurrentPassphrase = "";
|
||||||
|
mIsBrandNewKeyring = true;
|
||||||
|
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
// if userId is given, prefill the fields
|
// if userId is given, prefill the fields
|
||||||
@ -180,7 +204,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
serviceIntent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
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(
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||||
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
|
this, getResources().getQuantityString(R.plurals.progress_generating, 1),
|
||||||
ProgressDialog.STYLE_HORIZONTAL, true,
|
ProgressDialog.STYLE_HORIZONTAL, true,
|
||||||
@ -197,28 +221,28 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||||
// get new key from data bundle returned from service
|
// get new key from data bundle returned from service
|
||||||
Bundle data = message.getData();
|
Bundle data = message.getData();
|
||||||
PGPSecretKey masterKey = (PGPSecretKey) PgpConversionHelper
|
|
||||||
.BytesToPGPSecretKey(data
|
ArrayList<PGPSecretKey> newKeys =
|
||||||
|
PgpConversionHelper.BytesToPGPSecretKeyList(data
|
||||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
.getByteArray(KeychainIntentService.RESULT_NEW_KEY));
|
||||||
PGPSecretKey subKey = (PGPSecretKey) PgpConversionHelper
|
|
||||||
.BytesToPGPSecretKey(data
|
|
||||||
.getByteArray(KeychainIntentService.RESULT_NEW_KEY2));
|
|
||||||
|
|
||||||
// add master key
|
ArrayList<Integer> keyUsageFlags = data.getIntegerArrayList(
|
||||||
mKeys.add(masterKey);
|
KeychainIntentService.RESULT_KEY_USAGES);
|
||||||
mKeysUsages.add(Id.choice.usage.sign_only); //TODO: get from key flags
|
|
||||||
|
|
||||||
// add sub key
|
if (newKeys.size() == keyUsageFlags.size()) {
|
||||||
mKeys.add(subKey);
|
for (int i = 0; i < newKeys.size(); ++i) {
|
||||||
mKeysUsages.add(Id.choice.usage.encrypt_only); //TODO: get from key flags
|
mKeys.add(newKeys.get(i));
|
||||||
|
mKeysUsages.add(keyUsageFlags.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildLayout();
|
buildLayout(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -234,7 +258,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buildLayout();
|
buildLayout(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,67 +268,16 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
* @param intent
|
* @param intent
|
||||||
*/
|
*/
|
||||||
private void handleActionEditKey(Intent 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();
|
mDataUri = intent.getData();
|
||||||
if (mDataUri == null) {
|
if (mDataUri == null) {
|
||||||
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
|
||||||
finish();
|
finish();
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(Constants.TAG, "uri: " + mDataUri);
|
Log.d(Constants.TAG, "uri: " + mDataUri);
|
||||||
|
|
||||||
// get master key id using row id
|
// get master key id using row id
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||||
|
finallyEdit(masterKeyId);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,45 +285,66 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
getMenuInflater().inflate(R.menu.key_edit, 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
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:
|
case R.id.menu_key_edit_cancel:
|
||||||
cancelClicked();
|
cancelClicked();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_key_edit_export_file:
|
case R.id.menu_key_edit_export_file:
|
||||||
long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
|
if (needsSaving()) {
|
||||||
mExportHelper.showExportKeysDialog(ids, Id.type.secret_key, Constants.Path.APP_DIR_FILE_SEC);
|
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;
|
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 true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void finallyEdit(final long masterKeyId, final boolean masterCanSign) {
|
private void finallyEdit(final long masterKeyId) {
|
||||||
if (masterKeyId != 0) {
|
if (masterKeyId != 0) {
|
||||||
PGPSecretKey masterKey = null;
|
PGPSecretKey masterKey = null;
|
||||||
mKeyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(this, masterKeyId);
|
mKeyRing = ProviderHelper.getPGPSecretKeyRing(this, masterKeyId);
|
||||||
if (mKeyRing != null) {
|
if (mKeyRing != null) {
|
||||||
masterKey = PgpKeyHelper.getMasterKey(mKeyRing);
|
masterKey = mKeyRing.getSecretKey();
|
||||||
|
mMasterCanSign = PgpKeyHelper.isCertificationKey(mKeyRing.getSecretKey());
|
||||||
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
for (PGPSecretKey key : new IterableIterator<PGPSecretKey>(mKeyRing.getSecretKeys())) {
|
||||||
mKeys.add(key);
|
mKeys.add(key);
|
||||||
mKeysUsages.add(-1); // get usage when view is created
|
mKeysUsages.add(-1); // get usage when view is created
|
||||||
@ -358,20 +352,29 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
} else {
|
} else {
|
||||||
Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
|
Log.e(Constants.TAG, "Keyring not found with masterKeyId: " + masterKeyId);
|
||||||
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.error_no_secret_key_found, Toast.LENGTH_LONG).show();
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
if (masterKey != null) {
|
if (masterKey != null) {
|
||||||
|
boolean isSet = false;
|
||||||
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
for (String userId : new IterableIterator<String>(masterKey.getUserIDs())) {
|
||||||
Log.d(Constants.TAG, "Added userId " + userId);
|
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);
|
mUserIds.add(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mCurrentPassphrase = "";
|
mCurrentPassphrase = "";
|
||||||
|
buildLayout(false);
|
||||||
|
|
||||||
buildLayout();
|
mIsPassphraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
|
||||||
mIsPassPhraseSet = PassphraseCacheService.hasPassphrase(this, masterKeyId);
|
if (!mIsPassphraseSet) {
|
||||||
if (!mIsPassPhraseSet) {
|
|
||||||
// check "no passphrase" checkbox and remove button
|
// check "no passphrase" checkbox and remove button
|
||||||
mNoPassphrase.setChecked(true);
|
mNoPassphrase.setChecked(true);
|
||||||
mChangePassphrase.setVisibility(View.GONE);
|
mChangePassphrase.setVisibility(View.GONE);
|
||||||
@ -390,10 +393,11 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
Bundle data = message.getData();
|
Bundle data = message.getData();
|
||||||
|
|
||||||
// set new returned passphrase!
|
// set new returned passphrase!
|
||||||
mNewPassPhrase = data
|
mNewPassphrase = data
|
||||||
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
.getString(SetPassphraseDialogFragment.MESSAGE_NEW_PASSPHRASE);
|
||||||
|
|
||||||
updatePassPhraseButtonText();
|
updatePassphraseButtonText();
|
||||||
|
somethingChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -402,7 +406,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
Messenger messenger = new Messenger(returnHandler);
|
Messenger messenger = new Messenger(returnHandler);
|
||||||
|
|
||||||
// set title based on isPassphraseSet()
|
// set title based on isPassphraseSet()
|
||||||
int title = -1;
|
int title;
|
||||||
if (isPassphraseSet()) {
|
if (isPassphraseSet()) {
|
||||||
title = R.string.title_change_passphrase;
|
title = R.string.title_change_passphrase;
|
||||||
} else {
|
} 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
|
* Build layout based on mUserId, mKeys and mKeysUsages Vectors. It creates Views for every user
|
||||||
* id and key.
|
* id and key.
|
||||||
|
*
|
||||||
|
* @param newKeys
|
||||||
*/
|
*/
|
||||||
private void buildLayout() {
|
private void buildLayout(boolean newKeys) {
|
||||||
setContentView(R.layout.edit_key_activity);
|
setContentView(R.layout.edit_key_activity);
|
||||||
|
|
||||||
// find views
|
// find views
|
||||||
mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
|
mChangePassphrase = (BootstrapButton) findViewById(R.id.edit_key_btn_change_passphrase);
|
||||||
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
mNoPassphrase = (CheckBox) findViewById(R.id.edit_key_no_passphrase);
|
||||||
|
|
||||||
// Build layout based on given userIds and keys
|
// Build layout based on given userIds and keys
|
||||||
|
|
||||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
|
||||||
LinearLayout container = (LinearLayout) findViewById(R.id.edit_key_container);
|
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 = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||||
mUserIdsView.setType(Id.type.user_id);
|
mUserIdsView.setType(Id.type.user_id);
|
||||||
mUserIdsView.setCanEdit(mMasterCanSign);
|
mUserIdsView.setCanBeEdited(mMasterCanSign);
|
||||||
mUserIdsView.setUserIds(mUserIds);
|
mUserIdsView.setUserIds(mUserIds);
|
||||||
|
mUserIdsView.setEditorListener(this);
|
||||||
container.addView(mUserIdsView);
|
container.addView(mUserIdsView);
|
||||||
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
mKeysView = (SectionView) inflater.inflate(R.layout.edit_key_section, container, false);
|
||||||
mKeysView.setType(Id.type.key);
|
mKeysView.setType(Id.type.key);
|
||||||
mKeysView.setCanEdit(mMasterCanSign);
|
mKeysView.setCanBeEdited(mMasterCanSign);
|
||||||
mKeysView.setKeys(mKeys, mKeysUsages);
|
mKeysView.setKeys(mKeys, mKeysUsages, newKeys);
|
||||||
|
mKeysView.setEditorListener(this);
|
||||||
container.addView(mKeysView);
|
container.addView(mKeysView);
|
||||||
|
|
||||||
updatePassPhraseButtonText();
|
updatePassphraseButtonText();
|
||||||
|
|
||||||
mChangePassphrase.setOnClickListener(new OnClickListener() {
|
mChangePassphrase.setOnClickListener(new OnClickListener() {
|
||||||
public void onClick(View v) {
|
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() {
|
mNoPassphrase.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
// remove passphrase
|
// remove passphrase
|
||||||
mSavedNewPassPhrase = mNewPassPhrase;
|
mSavedNewPassphrase = mNewPassphrase;
|
||||||
mNewPassPhrase = "";
|
mNewPassphrase = "";
|
||||||
mChangePassphrase.setVisibility(View.GONE);
|
mChangePassphrase.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
mNewPassPhrase = mSavedNewPassPhrase;
|
mNewPassphrase = mSavedNewPassphrase;
|
||||||
mChangePassphrase.setVisibility(View.VISIBLE);
|
mChangePassphrase.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
somethingChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -477,37 +489,116 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
public boolean isPassphraseSet() {
|
public boolean isPassphraseSet() {
|
||||||
if (mNoPassphrase.isChecked()) {
|
if (mNoPassphrase.isChecked()) {
|
||||||
return true;
|
return true;
|
||||||
} else if ((mIsPassPhraseSet)
|
} else if ((mIsPassphraseSet)
|
||||||
|| (mNewPassPhrase != null && !mNewPassPhrase.equals(""))) {
|
|| (mNewPassphrase != null && !mNewPassphrase.equals(""))) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveClicked() {
|
public boolean hasPassphraseChanged() {
|
||||||
long masterKeyId = getMasterKeyId();
|
if (mNoPassphrase != null) {
|
||||||
try {
|
if (mNoPassphrase.isChecked()) {
|
||||||
if (!isPassphraseSet()) {
|
return mIsPassphraseSet;
|
||||||
throw new PgpGeneralException(this.getString(R.string.set_a_passphrase));
|
} else {
|
||||||
|
return (mNewPassphrase != null && !mNewPassphrase.equals(""));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String passphrase = null;
|
private void saveClicked() {
|
||||||
if (mIsPassPhraseSet) {
|
final long masterKeyId = getMasterKeyId();
|
||||||
passphrase = PassphraseCacheService.getCachedPassphrase(this, masterKeyId);
|
if (needsSaving()) { //make sure, as some versions don't support invalidateOptionsMenu
|
||||||
} else {
|
try {
|
||||||
passphrase = "";
|
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) {
|
} else {
|
||||||
showPassphraseDialog(masterKeyId, mMasterCanSign);
|
AppMsg.makeText(this, R.string.error_change_something_first, AppMsg.STYLE_ALERT).show();
|
||||||
} else {
|
}
|
||||||
mCurrentPassphrase = passphrase;
|
}
|
||||||
finallySaveClicked();
|
|
||||||
|
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) {
|
} catch (PgpGeneralException e) {
|
||||||
//Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||||
// Toast.LENGTH_SHORT).show();
|
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() {
|
private void finallySaveClicked() {
|
||||||
@ -517,42 +608,45 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
intent.setAction(KeychainIntentService.ACTION_SAVE_KEYRING);
|
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
|
// fill values for this action
|
||||||
Bundle data = new Bundle();
|
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.putBoolean(KeychainIntentService.SAVE_KEYRING_CAN_SIGN, mMasterCanSign);
|
||||||
|
data.putParcelable(KeychainIntentService.SAVE_KEYRING_PARCEL, saveParams);
|
||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
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,
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||||
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
|
getString(R.string.progress_saving), ProgressDialog.STYLE_HORIZONTAL) {
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||||
Intent data = new Intent();
|
Intent data = new Intent();
|
||||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, getMasterKeyId());
|
|
||||||
ArrayList<String> userIds = null;
|
// return uri pointing to new created key
|
||||||
try {
|
Uri uri = KeychainContract.KeyRings.buildGenericKeyRingUri(
|
||||||
userIds = getUserIds(mUserIdsView);
|
String.valueOf(getMasterKeyId()));
|
||||||
} catch (PgpGeneralException e) {
|
data.setData(uri);
|
||||||
Log.e(Constants.TAG, "exception while getting user ids", e);
|
|
||||||
}
|
|
||||||
data.putExtra(RESULT_EXTRA_USER_ID, userIds.get(0));
|
|
||||||
setResult(RESULT_OK, data);
|
setResult(RESULT_OK, data);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@ -568,14 +662,42 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
// start service with intent
|
// start service with intent
|
||||||
startService(intent);
|
startService(intent);
|
||||||
} catch (PgpGeneralException e) {
|
} catch (PgpGeneralException e) {
|
||||||
//Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
Log.e(Constants.TAG, getString(R.string.error_message, e.getMessage()));
|
||||||
// Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, getString(R.string.error_message, e.getMessage()),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelClicked() {
|
private void cancelClicked() {
|
||||||
setResult(RESULT_CANCELED);
|
if (needsSaving()) { //ask if we want to save
|
||||||
finish();
|
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;
|
boolean gotMainUserId = false;
|
||||||
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
for (int i = 0; i < userIdEditors.getChildCount(); ++i) {
|
||||||
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
UserIdEditor editor = (UserIdEditor) userIdEditors.getChildAt(i);
|
||||||
String userId = null;
|
String userId;
|
||||||
try {
|
userId = editor.getValue();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor.isMainUserId()) {
|
if (editor.isMainUserId()) {
|
||||||
userIds.add(0, userId);
|
userIds.add(0, userId);
|
||||||
@ -688,7 +799,7 @@ public class EditKeyActivity extends ActionBarActivity {
|
|||||||
return keysExpiryDates;
|
return keysExpiryDates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePassPhraseButtonText() {
|
private void updatePassphraseButtonText() {
|
||||||
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
|
mChangePassphrase.setText(isPassphraseSet() ? getString(R.string.btn_change_passphrase)
|
||||||
: getString(R.string.btn_set_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) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
|
||||||
* Copyright (C) 2011 Senecaso
|
* Copyright (C) 2011 Senecaso
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
@ -18,6 +18,7 @@
|
|||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -34,8 +35,10 @@ import android.support.v7.app.ActionBar;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
import com.devspark.appmsg.AppMsg;
|
import com.devspark.appmsg.AppMsg;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
@ -54,7 +57,6 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
+ "IMPORT_KEY_FROM_QR_CODE";
|
+ "IMPORT_KEY_FROM_QR_CODE";
|
||||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX
|
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER = Constants.INTENT_PREFIX
|
||||||
+ "IMPORT_KEY_FROM_KEYSERVER";
|
+ "IMPORT_KEY_FROM_KEYSERVER";
|
||||||
// TODO: implement:
|
|
||||||
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
|
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
|
||||||
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
|
+ "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_KEY_ID = "key_id";
|
||||||
public static final String EXTRA_FINGERPRINT = "fingerprint";
|
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
|
// view
|
||||||
private ImportKeysListFragment mListFragment;
|
private ImportKeysListFragment mListFragment;
|
||||||
private String[] mNavigationStrings;
|
private String[] mNavigationStrings;
|
||||||
@ -86,7 +92,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
ImportKeysNFCFragment.class
|
ImportKeysNFCFragment.class
|
||||||
};
|
};
|
||||||
|
|
||||||
private int mCurrentNavPostition = -1;
|
private int mCurrentNavPosition = -1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
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);
|
mNavigationStrings = getResources().getStringArray(R.array.import_action_list);
|
||||||
Context context = getSupportActionBar().getThemedContext();
|
|
||||||
ArrayAdapter<CharSequence> navigationAdapter = ArrayAdapter.createFromResource(context,
|
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
|
||||||
R.array.import_action_list, android.R.layout.simple_spinner_dropdown_item);
|
setTitle(R.string.nav_import);
|
||||||
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
|
} else {
|
||||||
getSupportActionBar().setListNavigationCallbacks(navigationAdapter, this);
|
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());
|
handleActions(savedInstanceState, getIntent());
|
||||||
}
|
}
|
||||||
@ -152,33 +163,52 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
// action: directly load data
|
// action: directly load data
|
||||||
startListFragment(savedInstanceState, importData, null, null);
|
startListFragment(savedInstanceState, importData, null, null);
|
||||||
}
|
}
|
||||||
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)) {
|
} else if (ACTION_IMPORT_KEY_FROM_KEYSERVER.equals(action)
|
||||||
String query = null;
|
|| ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(action)) {
|
||||||
if (extras.containsKey(EXTRA_QUERY)) {
|
|
||||||
query = extras.getString(EXTRA_QUERY);
|
// only used for OpenPgpService
|
||||||
} else if (extras.containsKey(EXTRA_KEY_ID)) {
|
if (extras.containsKey(EXTRA_PENDING_INTENT_DATA)) {
|
||||||
long keyId = intent.getLongExtra(EXTRA_KEY_ID, 0);
|
mPendingIntentData = extras.getParcelable(EXTRA_PENDING_INTENT_DATA);
|
||||||
if (keyId != 0) {
|
}
|
||||||
query = PgpKeyHelper.convertKeyIdToHex(keyId);
|
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)) {
|
} 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);
|
String fingerprint = intent.getStringExtra(EXTRA_FINGERPRINT);
|
||||||
if (fingerprint != null) {
|
loadFromFingerprint(savedInstanceState, fingerprint);
|
||||||
query = "0x" + fingerprint;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Log.e(Constants.TAG,
|
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;
|
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)) {
|
} else if (ACTION_IMPORT_KEY_FROM_FILE.equals(action)) {
|
||||||
|
|
||||||
// NOTE: this only displays the appropriate fragment, no actions are taken
|
// 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
|
* onNavigationItemSelected() should check whether the Fragment is already in existence
|
||||||
* inside your Activity."
|
* inside your Activity."
|
||||||
* <p/>
|
* <p/>
|
||||||
* from http://stackoverflow.com/questions/10983396/fragment-oncreateview-and-onactivitycreated-called-twice/14295474#14295474
|
* from http://stackoverflow.com/a/14295474
|
||||||
* <p/>
|
* <p/>
|
||||||
* In our case, if we start ImportKeysActivity with parameters to directly search using a fingerprint,
|
* 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.
|
* the fragment would be loaded twice resulting in the query being empty after the second load.
|
||||||
* <p/>
|
* <p/>
|
||||||
* Our solution:
|
* Our solution:
|
||||||
* To prevent that a fragment will be loaded again even if it was already loaded loadNavFragment
|
* 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 itemPosition
|
||||||
* @param itemId
|
* @param itemId
|
||||||
@ -256,10 +286,12 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadNavFragment(int itemPosition, Bundle args) {
|
private void loadNavFragment(int itemPosition, Bundle args) {
|
||||||
if (mCurrentNavPostition != itemPosition) {
|
if (mCurrentNavPosition != itemPosition) {
|
||||||
getSupportActionBar().setSelectedNavigationItem(itemPosition);
|
if (ActionBar.NAVIGATION_MODE_LIST == getSupportActionBar().getNavigationMode()) {
|
||||||
|
getSupportActionBar().setSelectedNavigationItem(itemPosition);
|
||||||
|
}
|
||||||
loadFragment(NAVIGATION_CLASSES[itemPosition], args, mNavigationStrings[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);
|
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.makeText(this, R.string.import_qr_code_too_short_fingerprint,
|
||||||
AppMsg.STYLE_ALERT).show();
|
AppMsg.STYLE_ALERT).show();
|
||||||
return;
|
return;
|
||||||
@ -290,6 +326,7 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
// display keyserver fragment with query
|
// display keyserver fragment with query
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
|
args.putString(ImportKeysServerFragment.ARG_QUERY, query);
|
||||||
|
args.putBoolean(ImportKeysServerFragment.ARG_DISABLE_QUERY_EDIT, true);
|
||||||
loadNavFragment(0, args);
|
loadNavFragment(0, args);
|
||||||
|
|
||||||
// action: search directly
|
// action: search directly
|
||||||
@ -300,70 +337,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
mListFragment.loadNew(importData, dataUri, serverQuery, keyServer);
|
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
|
* Import keys with mImportData
|
||||||
*/
|
*/
|
||||||
public void importKeys() {
|
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(
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(
|
||||||
this,
|
this,
|
||||||
getString(R.string.progress_importing),
|
getString(R.string.progress_importing),
|
||||||
@ -403,6 +381,11 @@ public class ImportKeysActivity extends DrawerActivity implements ActionBar.OnNa
|
|||||||
BadImportKeyDialogFragment.newInstance(bad);
|
BadImportKeyDialogFragment.newInstance(bad);
|
||||||
badImportKeyDialogFragment.show(getSupportFragmentManager(), "badKeyDialog");
|
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;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -24,9 +25,13 @@ import android.view.View;
|
|||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class ImportKeysClipboardFragment extends Fragment {
|
public class ImportKeysClipboardFragment extends Fragment {
|
||||||
|
|
||||||
private ImportKeysActivity mImportActivity;
|
private ImportKeysActivity mImportActivity;
|
||||||
@ -60,6 +65,10 @@ public class ImportKeysClipboardFragment extends Fragment {
|
|||||||
String sendText = "";
|
String sendText = "";
|
||||||
if (clipboardText != null) {
|
if (clipboardText != null) {
|
||||||
sendText = clipboardText.toString();
|
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);
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.Preferences;
|
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.InputData;
|
||||||
import org.sufficientlysecure.keychain.util.KeyServer;
|
import org.sufficientlysecure.keychain.util.KeyServer;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
@ -57,7 +57,7 @@ public class ImportKeysNFCFragment extends Fragment {
|
|||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
// show nfc help
|
// show nfc help
|
||||||
Intent intent = new Intent(getActivity(), HelpActivity.class);
|
Intent intent = new Intent(getActivity(), HelpActivity.class);
|
||||||
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 1);
|
intent.putExtra(HelpActivity.EXTRA_SELECTED_TAB, 2);
|
||||||
startActivityForResult(intent, 0);
|
startActivityForResult(intent, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import com.google.zxing.integration.android.IntentResult;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -28,8 +30,9 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
import com.google.zxing.integration.android.IntentResult;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
|
import org.sufficientlysecure.keychain.util.IntentIntegratorSupportV4;
|
||||||
|
@ -40,6 +40,7 @@ import org.sufficientlysecure.keychain.util.Log;
|
|||||||
public class ImportKeysServerFragment extends Fragment {
|
public class ImportKeysServerFragment extends Fragment {
|
||||||
public static final String ARG_QUERY = "query";
|
public static final String ARG_QUERY = "query";
|
||||||
public static final String ARG_KEY_SERVER = "key_server";
|
public static final String ARG_KEY_SERVER = "key_server";
|
||||||
|
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
|
||||||
|
|
||||||
private ImportKeysActivity mImportActivity;
|
private ImportKeysActivity mImportActivity;
|
||||||
|
|
||||||
@ -140,6 +141,10 @@ public class ImportKeysServerFragment extends Fragment {
|
|||||||
|
|
||||||
Log.d(Constants.TAG, "keyServer: " + keyServer);
|
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.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||||
|
|
||||||
@ -53,27 +53,21 @@ public class KeyListActivity extends DrawerActivity {
|
|||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_key_list_import:
|
case R.id.menu_key_list_import:
|
||||||
Intent intentImport = new Intent(this, ImportKeysActivity.class);
|
callIntentForDrawerItem(Constants.DrawerItems.IMPORT_KEYS);
|
||||||
startActivityForResult(intentImport, 0);
|
|
||||||
|
|
||||||
return true;
|
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:
|
case R.id.menu_key_list_create:
|
||||||
createKey();
|
createKey();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case R.id.menu_key_list_create_expert:
|
case R.id.menu_key_list_create_expert:
|
||||||
createKeyExpert();
|
createKeyExpert();
|
||||||
|
|
||||||
return true;
|
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;
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,11 @@ import android.content.Intent;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
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.Fragment;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
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.app.ActionBarActivity;
|
||||||
import android.support.v7.widget.SearchView;
|
import android.support.v7.widget.SearchView;
|
||||||
import android.text.TextUtils;
|
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.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.widget.*;
|
|
||||||
import android.widget.AbsListView.MultiChoiceModeListener;
|
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 com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
import org.sufficientlysecure.keychain.helper.ExportHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
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.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.HighlightQueryCursorAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.DeleteKeyDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
@ -56,7 +67,6 @@ import se.emilsjolander.stickylistheaders.ApiLevelTooLowException;
|
|||||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
||||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,9 +152,6 @@ public class KeyListFragment extends Fragment
|
|||||||
} catch (ApiLevelTooLowException e) {
|
} 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
|
* ActionBarSherlock does not support MultiChoiceModeListener. Thus multi-selection is only
|
||||||
* available for Android >= 3.0
|
* available for Android >= 3.0
|
||||||
@ -178,18 +185,15 @@ public class KeyListFragment extends Fragment
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.menu_key_list_multi_delete: {
|
case R.id.menu_key_list_multi_delete: {
|
||||||
ids = mStickyList.getWrappedList().getCheckedItemIds();
|
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||||
showDeleteKeyDialog(mode, ids);
|
showDeleteKeyDialog(mode, ids);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.menu_key_list_multi_export: {
|
case R.id.menu_key_list_multi_export: {
|
||||||
// todo: public/secret needs to be handled differently here
|
ids = mAdapter.getCurrentSelectedMasterKeyIds();
|
||||||
ids = mStickyList.getWrappedList().getCheckedItemIds();
|
|
||||||
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
|
ExportHelper mExportHelper = new ExportHelper((ActionBarActivity) getActivity());
|
||||||
mExportHelper
|
mExportHelper.showExportKeysDialog(
|
||||||
.showExportKeysDialog(ids,
|
ids, Constants.Path.APP_DIR_FILE_PUB, mAdapter.isAnySecretSelected());
|
||||||
Id.type.public_key,
|
|
||||||
Constants.Path.APP_DIR_FILE_PUB);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case R.id.menu_key_list_multi_select_all: {
|
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.
|
// These are the rows that we will retrieve.
|
||||||
static final String[] PROJECTION = new String[]{
|
static final String[] PROJECTION = new String[]{
|
||||||
KeychainContract.KeyRings._ID,
|
KeyRings._ID,
|
||||||
KeychainContract.KeyRings.TYPE,
|
KeyRings.MASTER_KEY_ID,
|
||||||
KeychainContract.KeyRings.MASTER_KEY_ID,
|
KeyRings.USER_ID,
|
||||||
KeychainContract.UserIds.USER_ID,
|
KeyRings.IS_REVOKED,
|
||||||
KeychainContract.Keys.IS_REVOKED
|
KeyRings.HAS_SECRET
|
||||||
};
|
};
|
||||||
|
|
||||||
static final int INDEX_TYPE = 1;
|
static final int INDEX_MASTER_KEY_ID = 1;
|
||||||
static final int INDEX_MASTER_KEY_ID = 2;
|
static final int INDEX_USER_ID = 2;
|
||||||
static final int INDEX_USER_ID = 3;
|
static final int INDEX_IS_REVOKED = 3;
|
||||||
static final int INDEX_IS_REVOKED = 4;
|
static final int INDEX_HAS_SECRET = 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";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
@ -269,12 +267,12 @@ public class KeyListFragment extends Fragment
|
|||||||
String where = null;
|
String where = null;
|
||||||
String whereArgs[] = null;
|
String whereArgs[] = null;
|
||||||
if (mCurQuery != null) {
|
if (mCurQuery != null) {
|
||||||
where = KeychainContract.UserIds.USER_ID + " LIKE ?";
|
where = KeyRings.USER_ID + " LIKE ?";
|
||||||
whereArgs = new String[]{"%" + mCurQuery + "%"};
|
whereArgs = new String[]{"%" + mCurQuery + "%"};
|
||||||
}
|
}
|
||||||
// Now create and return a CursorLoader that will take care of
|
// Now create and return a CursorLoader that will take care of
|
||||||
// creating a Cursor for the data being displayed.
|
// 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
|
@Override
|
||||||
@ -286,6 +284,9 @@ public class KeyListFragment extends Fragment
|
|||||||
|
|
||||||
mStickyList.setAdapter(mAdapter);
|
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
|
// NOTE: Not supported by StickyListHeader, but reimplemented here
|
||||||
// The list should now be shown.
|
// The list should now be shown.
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
@ -315,17 +316,15 @@ public class KeyListFragment extends Fragment
|
|||||||
viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);
|
viewIntent = new Intent(getActivity(), ViewKeyActivityJB.class);
|
||||||
}
|
}
|
||||||
viewIntent.setData(
|
viewIntent.setData(
|
||||||
KeychainContract
|
KeyRings.buildGenericKeyRingUri(Long.toString(mAdapter.getMasterKeyId(position))));
|
||||||
.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
|
|
||||||
Long.toString(mAdapter.getMasterKeyId(position))));
|
|
||||||
startActivity(viewIntent);
|
startActivity(viewIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(11)
|
@TargetApi(11)
|
||||||
protected void encrypt(ActionMode mode, long[] keyRingMasterKeyIds) {
|
protected void encrypt(ActionMode mode, long[] masterKeyIds) {
|
||||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
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
|
// used instead of startActivity set actionbar based on callingPackage
|
||||||
startActivityForResult(intent, 0);
|
startActivityForResult(intent, 0);
|
||||||
|
|
||||||
@ -335,35 +334,17 @@ public class KeyListFragment extends Fragment
|
|||||||
/**
|
/**
|
||||||
* Show dialog to delete key
|
* Show dialog to delete key
|
||||||
*
|
*
|
||||||
* @param keyRingRowIds
|
* @param masterKeyIds
|
||||||
*/
|
*/
|
||||||
@TargetApi(11)
|
@TargetApi(11)
|
||||||
// TODO: this method needs an overhaul to handle both public and secret keys gracefully!
|
// 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
|
// Message is received after key is deleted
|
||||||
Handler returnHandler = new Handler() {
|
Handler returnHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
||||||
Bundle returnData = message.getData();
|
mode.finish();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -372,7 +353,7 @@ public class KeyListFragment extends Fragment
|
|||||||
Messenger messenger = new Messenger(returnHandler);
|
Messenger messenger = new Messenger(returnHandler);
|
||||||
|
|
||||||
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger,
|
||||||
keyRingRowIds, Id.type.public_key);
|
masterKeyIds);
|
||||||
|
|
||||||
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
|
deleteKeyDialog.show(getActivity().getSupportFragmentManager(), "deleteKeyDialog");
|
||||||
}
|
}
|
||||||
@ -506,11 +487,15 @@ public class KeyListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // set edit button and revoked info, specific by key type
|
{ // 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);
|
Button button = (Button) view.findViewById(R.id.edit);
|
||||||
TextView revoked = (TextView) view.findViewById(R.id.revoked);
|
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
|
// this is a secret key - show the edit button
|
||||||
|
statusDivider.setVisibility(View.VISIBLE);
|
||||||
|
statusLayout.setVisibility(View.VISIBLE);
|
||||||
revoked.setVisibility(View.GONE);
|
revoked.setVisibility(View.GONE);
|
||||||
button.setVisibility(View.VISIBLE);
|
button.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
@ -518,26 +503,31 @@ public class KeyListFragment extends Fragment
|
|||||||
button.setOnClickListener(new OnClickListener() {
|
button.setOnClickListener(new OnClickListener() {
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
|
||||||
editIntent.setData(
|
editIntent.setData(KeyRingData.buildSecretKeyRingUri(Long.toString(id)));
|
||||||
KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(
|
|
||||||
Long.toString(id)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
|
||||||
startActivityForResult(editIntent, 0);
|
startActivityForResult(editIntent, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// this is a public key - hide the edit button, show if it's revoked
|
// this is a public key - hide the edit button, show if it's revoked
|
||||||
|
statusDivider.setVisibility(View.GONE);
|
||||||
button.setVisibility(View.GONE);
|
button.setVisibility(View.GONE);
|
||||||
|
|
||||||
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
boolean isRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0;
|
||||||
|
statusLayout.setVisibility(isRevoked ? View.VISIBLE : View.GONE);
|
||||||
revoked.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) {
|
public long getMasterKeyId(int id) {
|
||||||
if (!mCursor.moveToPosition(id)) {
|
if (!mCursor.moveToPosition(id)) {
|
||||||
throw new IllegalStateException("couldn't move cursor to position " + 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);
|
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
|
{ // set contact count
|
||||||
int num = mCursor.getCount();
|
int num = mCursor.getCount();
|
||||||
String contactsTotal = getResources().getQuantityString(R.plurals.n_contacts, num, num);
|
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
|
// 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;
|
return 1L;
|
||||||
}
|
}
|
||||||
// otherwise, return the first character of the name as ID
|
// otherwise, return the first character of the name as ID
|
||||||
@ -645,6 +635,14 @@ public class KeyListFragment extends Fragment
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAnySecretSelected() {
|
||||||
|
for (int pos : mSelection.keySet()) {
|
||||||
|
if(mAdapter.isSecretAvailable(pos))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public long[] getCurrentSelectedMasterKeyIds() {
|
public long[] getCurrentSelectedMasterKeyIds() {
|
||||||
long[] ids = new long[mSelection.size()];
|
long[] ids = new long[mSelection.size()];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
@ -20,9 +20,15 @@ import android.annotation.SuppressLint;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
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.bcpg.HashAlgorithmTags;
|
||||||
import org.spongycastle.openpgp.PGPEncryptedData;
|
import org.spongycastle.openpgp.PGPEncryptedData;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
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";
|
public static final String ACTION_PREFS_ADV = "org.sufficientlysecure.keychain.ui.PREFS_ADV";
|
||||||
|
|
||||||
private PreferenceScreen mKeyServerPreference = null;
|
private PreferenceScreen mKeyServerPreference = null;
|
||||||
private static Preferences mPreferences;
|
private static Preferences sPreferences;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
mPreferences = Preferences.getPreferences(this);
|
sPreferences = Preferences.getPreferences(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// final ActionBar actionBar = getSupportActionBar();
|
// final ActionBar actionBar = getSupportActionBar();
|
||||||
@ -55,11 +61,11 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
if (action != null && action.equals(ACTION_PREFS_GEN)) {
|
if (action != null && action.equals(ACTION_PREFS_GEN)) {
|
||||||
addPreferencesFromResource(R.xml.gen_preferences);
|
addPreferencesFromResource(R.xml.gen_preferences);
|
||||||
|
|
||||||
initializePassPassPhraceCacheTtl(
|
initializePassPassphraceCacheTtl(
|
||||||
(IntegerListPreference) findPreference(Constants.Pref.PASS_PHRASE_CACHE_TTL));
|
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
|
||||||
|
|
||||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
||||||
String servers[] = mPreferences.getKeyServers();
|
String servers[] = sPreferences.getKeyServers();
|
||||||
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
||||||
servers.length, servers.length));
|
servers.length, servers.length));
|
||||||
mKeyServerPreference
|
mKeyServerPreference
|
||||||
@ -68,7 +74,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
Intent intent = new Intent(PreferencesActivity.this,
|
Intent intent = new Intent(PreferencesActivity.this,
|
||||||
PreferencesKeyServerActivity.class);
|
PreferencesKeyServerActivity.class);
|
||||||
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
||||||
mPreferences.getKeyServers());
|
sPreferences.getKeyServers());
|
||||||
startActivityForResult(intent, Id.request.key_server_preference);
|
startActivityForResult(intent, Id.request.key_server_preference);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -104,8 +110,8 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
|
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
|
||||||
entries, values);
|
entries, values);
|
||||||
|
|
||||||
initializeAsciiArmour(
|
initializeAsciiArmor(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOUR));
|
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
|
||||||
|
|
||||||
initializeForceV3Signatures(
|
initializeForceV3Signatures(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||||
@ -125,7 +131,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
}
|
}
|
||||||
String servers[] = data
|
String servers[] = data
|
||||||
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
||||||
mPreferences.setKeyServers(servers);
|
sPreferences.setKeyServers(servers);
|
||||||
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
||||||
R.plurals.n_key_servers, servers.length, servers.length));
|
R.plurals.n_key_servers, servers.length, servers.length));
|
||||||
break;
|
break;
|
||||||
@ -159,11 +165,11 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
addPreferencesFromResource(R.xml.gen_preferences);
|
addPreferencesFromResource(R.xml.gen_preferences);
|
||||||
|
|
||||||
initializePassPassPhraceCacheTtl(
|
initializePassPassphraceCacheTtl(
|
||||||
(IntegerListPreference) findPreference(Constants.Pref.PASS_PHRASE_CACHE_TTL));
|
(IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL));
|
||||||
|
|
||||||
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
mKeyServerPreference = (PreferenceScreen) findPreference(Constants.Pref.KEY_SERVERS);
|
||||||
String servers[] = mPreferences.getKeyServers();
|
String servers[] = sPreferences.getKeyServers();
|
||||||
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
mKeyServerPreference.setSummary(getResources().getQuantityString(R.plurals.n_key_servers,
|
||||||
servers.length, servers.length));
|
servers.length, servers.length));
|
||||||
mKeyServerPreference
|
mKeyServerPreference
|
||||||
@ -172,7 +178,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
Intent intent = new Intent(getActivity(),
|
Intent intent = new Intent(getActivity(),
|
||||||
PreferencesKeyServerActivity.class);
|
PreferencesKeyServerActivity.class);
|
||||||
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
intent.putExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS,
|
||||||
mPreferences.getKeyServers());
|
sPreferences.getKeyServers());
|
||||||
startActivityForResult(intent, Id.request.key_server_preference);
|
startActivityForResult(intent, Id.request.key_server_preference);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -188,7 +194,7 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
}
|
}
|
||||||
String servers[] = data
|
String servers[] = data
|
||||||
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
.getStringArrayExtra(PreferencesKeyServerActivity.EXTRA_KEY_SERVERS);
|
||||||
mPreferences.setKeyServers(servers);
|
sPreferences.setKeyServers(servers);
|
||||||
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
mKeyServerPreference.setSummary(getResources().getQuantityString(
|
||||||
R.plurals.n_key_servers, servers.length, servers.length));
|
R.plurals.n_key_servers, servers.length, servers.length));
|
||||||
break;
|
break;
|
||||||
@ -241,8 +247,8 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
|
(IntegerListPreference) findPreference(Constants.Pref.DEFAULT_FILE_COMPRESSION),
|
||||||
entries, values);
|
entries, values);
|
||||||
|
|
||||||
initializeAsciiArmour(
|
initializeAsciiArmor(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOUR));
|
(CheckBoxPreference) findPreference(Constants.Pref.DEFAULT_ASCII_ARMOR));
|
||||||
|
|
||||||
initializeForceV3Signatures(
|
initializeForceV3Signatures(
|
||||||
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
(CheckBoxPreference) findPreference(Constants.Pref.FORCE_V3_SIGNATURES));
|
||||||
@ -255,15 +261,15 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
|| super.isValidFragment(fragmentName);
|
|| super.isValidFragment(fragmentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializePassPassPhraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) {
|
private static void initializePassPassphraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) {
|
||||||
mPassphraseCacheTtl.setValue("" + mPreferences.getPassPhraseCacheTtl());
|
mPassphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl());
|
||||||
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
|
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
|
||||||
mPassphraseCacheTtl
|
mPassphraseCacheTtl
|
||||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mPassphraseCacheTtl.setValue(newValue.toString());
|
mPassphraseCacheTtl.setValue(newValue.toString());
|
||||||
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
|
mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry());
|
||||||
mPreferences.setPassPhraseCacheTtl(Integer.parseInt(newValue.toString()));
|
sPreferences.setPassphraseCacheTtl(Integer.parseInt(newValue.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -282,14 +288,14 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
}
|
}
|
||||||
mEncryptionAlgorithm.setEntries(entries);
|
mEncryptionAlgorithm.setEntries(entries);
|
||||||
mEncryptionAlgorithm.setEntryValues(values);
|
mEncryptionAlgorithm.setEntryValues(values);
|
||||||
mEncryptionAlgorithm.setValue("" + mPreferences.getDefaultEncryptionAlgorithm());
|
mEncryptionAlgorithm.setValue("" + sPreferences.getDefaultEncryptionAlgorithm());
|
||||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||||
mEncryptionAlgorithm
|
mEncryptionAlgorithm
|
||||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mEncryptionAlgorithm.setValue(newValue.toString());
|
mEncryptionAlgorithm.setValue(newValue.toString());
|
||||||
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
mEncryptionAlgorithm.setSummary(mEncryptionAlgorithm.getEntry());
|
||||||
mPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
|
sPreferences.setDefaultEncryptionAlgorithm(Integer.parseInt(newValue
|
||||||
.toString()));
|
.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -309,13 +315,13 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
}
|
}
|
||||||
mHashAlgorithm.setEntries(entries);
|
mHashAlgorithm.setEntries(entries);
|
||||||
mHashAlgorithm.setEntryValues(values);
|
mHashAlgorithm.setEntryValues(values);
|
||||||
mHashAlgorithm.setValue("" + mPreferences.getDefaultHashAlgorithm());
|
mHashAlgorithm.setValue("" + sPreferences.getDefaultHashAlgorithm());
|
||||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||||
mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mHashAlgorithm.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mHashAlgorithm.setValue(newValue.toString());
|
mHashAlgorithm.setValue(newValue.toString());
|
||||||
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
mHashAlgorithm.setSummary(mHashAlgorithm.getEntry());
|
||||||
mPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
sPreferences.setDefaultHashAlgorithm(Integer.parseInt(newValue.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -326,14 +332,14 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
int[] valueIds, String[] entries, String[] values) {
|
int[] valueIds, String[] entries, String[] values) {
|
||||||
mMessageCompression.setEntries(entries);
|
mMessageCompression.setEntries(entries);
|
||||||
mMessageCompression.setEntryValues(values);
|
mMessageCompression.setEntryValues(values);
|
||||||
mMessageCompression.setValue("" + mPreferences.getDefaultMessageCompression());
|
mMessageCompression.setValue("" + sPreferences.getDefaultMessageCompression());
|
||||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||||
mMessageCompression
|
mMessageCompression
|
||||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mMessageCompression.setValue(newValue.toString());
|
mMessageCompression.setValue(newValue.toString());
|
||||||
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
mMessageCompression.setSummary(mMessageCompression.getEntry());
|
||||||
mPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
|
sPreferences.setDefaultMessageCompression(Integer.parseInt(newValue
|
||||||
.toString()));
|
.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -344,36 +350,36 @@ public class PreferencesActivity extends PreferenceActivity {
|
|||||||
(final IntegerListPreference mFileCompression, String[] entries, String[] values) {
|
(final IntegerListPreference mFileCompression, String[] entries, String[] values) {
|
||||||
mFileCompression.setEntries(entries);
|
mFileCompression.setEntries(entries);
|
||||||
mFileCompression.setEntryValues(values);
|
mFileCompression.setEntryValues(values);
|
||||||
mFileCompression.setValue("" + mPreferences.getDefaultFileCompression());
|
mFileCompression.setValue("" + sPreferences.getDefaultFileCompression());
|
||||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||||
mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mFileCompression.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mFileCompression.setValue(newValue.toString());
|
mFileCompression.setValue(newValue.toString());
|
||||||
mFileCompression.setSummary(mFileCompression.getEntry());
|
mFileCompression.setSummary(mFileCompression.getEntry());
|
||||||
mPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
sPreferences.setDefaultFileCompression(Integer.parseInt(newValue.toString()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializeAsciiArmour(final CheckBoxPreference mAsciiArmour) {
|
private static void initializeAsciiArmor(final CheckBoxPreference mAsciiArmor) {
|
||||||
mAsciiArmour.setChecked(mPreferences.getDefaultAsciiArmour());
|
mAsciiArmor.setChecked(sPreferences.getDefaultAsciiArmor());
|
||||||
mAsciiArmour.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
mAsciiArmor.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mAsciiArmour.setChecked((Boolean) newValue);
|
mAsciiArmor.setChecked((Boolean) newValue);
|
||||||
mPreferences.setDefaultAsciiArmour((Boolean) newValue);
|
sPreferences.setDefaultAsciiArmor((Boolean) newValue);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
|
private static void initializeForceV3Signatures(final CheckBoxPreference mForceV3Signatures) {
|
||||||
mForceV3Signatures.setChecked(mPreferences.getForceV3Signatures());
|
mForceV3Signatures.setChecked(sPreferences.getForceV3Signatures());
|
||||||
mForceV3Signatures
|
mForceV3Signatures
|
||||||
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
mForceV3Signatures.setChecked((Boolean) newValue);
|
mForceV3Signatures.setChecked((Boolean) newValue);
|
||||||
mPreferences.setForceV3Signatures((Boolean) newValue);
|
sPreferences.setForceV3Signatures((Boolean) newValue);
|
||||||
return false;
|
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
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEdited() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
|
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
|
||||||
mEditors, false);
|
mEditors, false);
|
||||||
|
@ -32,7 +32,13 @@ import android.view.Gravity;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
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.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround;
|
||||||
@ -248,9 +254,7 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
// This is called when a new Loader needs to be created. This
|
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||||
// sample only has one Loader, so we don't care about the ID.
|
|
||||||
Uri baseUri = KeyRings.buildPublicKeyRingsUri();
|
|
||||||
|
|
||||||
// These are the rows that we will retrieve.
|
// These are the rows that we will retrieve.
|
||||||
long now = new Date().getTime() / 1000;
|
long now = new Date().getTime() / 1000;
|
||||||
@ -258,24 +262,24 @@ public class SelectPublicKeyFragment extends ListFragmentWorkaround implements T
|
|||||||
KeyRings._ID,
|
KeyRings._ID,
|
||||||
KeyRings.MASTER_KEY_ID,
|
KeyRings.MASTER_KEY_ID,
|
||||||
UserIds.USER_ID,
|
UserIds.USER_ID,
|
||||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
+" WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||||
+ Keys.CAN_ENCRYPT + " = '1') AS "
|
+ " AND k." + Keys.CAN_ENCRYPT + " = '1'"
|
||||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
+ " WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||||
+ " AND valid_keys." + Keys.IS_REVOKED + " = '0' AND valid_keys."
|
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||||
+ Keys.CAN_ENCRYPT + " = '1' AND valid_keys." + Keys.CREATION + " <= '"
|
+ " AND k." + Keys.CAN_ENCRYPT + " = '1'"
|
||||||
+ now + "' AND " + "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys."
|
+ " AND k." + Keys.CREATION + " <= '" + now + "'"
|
||||||
+ Keys.EXPIRY + " >= '" + now + "')) AS "
|
+ " AND ( k." + Keys.EXPIRY + " IS NULL OR k." + Keys.EXPIRY + " >= '" + now + "' )"
|
||||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
||||||
|
|
||||||
String inMasterKeyList = null;
|
String inMasterKeyList = null;
|
||||||
if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) {
|
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) {
|
for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) {
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
inMasterKeyList += ", ";
|
inMasterKeyList += ", ";
|
||||||
|
@ -1,42 +1,37 @@
|
|||||||
/*
|
/*
|
||||||
* 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");
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* you may not use this file except in compliance with the License.
|
* it under the terms of the GNU General Public License as published by
|
||||||
* You may obtain a copy of the License at
|
* 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
|
* You should have received a copy of the GNU General Public License
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* 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.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.view.Menu;
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
|
||||||
public class SelectSecretKeyActivity extends ActionBarActivity {
|
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 EXTRA_FILTER_CERTIFY = "filter_certify";
|
||||||
|
|
||||||
public static final String RESULT_EXTRA_MASTER_KEY_ID = "master_key_id";
|
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;
|
private SelectSecretKeyFragment mSelectFragment;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -50,23 +45,8 @@ public class SelectSecretKeyActivity extends ActionBarActivity {
|
|||||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
actionBar.setHomeButtonEnabled(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);
|
mFilterCertify = getIntent().getBooleanExtra(EXTRA_FILTER_CERTIFY, false);
|
||||||
|
|
||||||
handleIntent(getIntent());
|
|
||||||
|
|
||||||
// Check that the activity is using the layout version with
|
// Check that the activity is using the layout version with
|
||||||
// the fragment_container FrameLayout
|
// the fragment_container FrameLayout
|
||||||
if (findViewById(R.id.select_secret_key_fragment_container) != null) {
|
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
|
* This is executed by SelectSecretKeyFragment after clicking on an item
|
||||||
*
|
*
|
||||||
* @param masterKeyId
|
* @param selectedUri
|
||||||
* @param userId
|
|
||||||
*/
|
*/
|
||||||
public void afterListSelection(long masterKeyId, String userId) {
|
public void afterListSelection(Uri selectedUri) {
|
||||||
Intent data = new Intent();
|
Intent data = new Intent();
|
||||||
data.putExtra(RESULT_EXTRA_MASTER_KEY_ID, masterKeyId);
|
data.setData(selectedUri);
|
||||||
data.putExtra(RESULT_EXTRA_USER_ID, (String) userId);
|
|
||||||
setResult(RESULT_OK, data);
|
setResult(RESULT_OK, data);
|
||||||
finish();
|
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>
|
* Copyright (C) 2010 Thialfihar <thi@thialfihar.org>
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
@ -55,10 +56,9 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
|||||||
*/
|
*/
|
||||||
public static SelectSecretKeyFragment newInstance(boolean filterCertify) {
|
public static SelectSecretKeyFragment newInstance(boolean filterCertify) {
|
||||||
SelectSecretKeyFragment frag = new SelectSecretKeyFragment();
|
SelectSecretKeyFragment frag = new SelectSecretKeyFragment();
|
||||||
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
args.putBoolean(ARG_FILTER_CERTIFY, filterCertify);
|
args.putBoolean(ARG_FILTER_CERTIFY, filterCertify);
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
return frag;
|
return frag;
|
||||||
@ -85,10 +85,10 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
|||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
|
||||||
long masterKeyId = mAdapter.getMasterKeyId(position);
|
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
|
// 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) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
// This is called when a new Loader needs to be created. This
|
// 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.
|
// sample only has one Loader, so we don't care about the ID.
|
||||||
Uri baseUri = KeyRings.buildSecretKeyRingsUri();
|
Uri baseUri = KeyRings.buildUnifiedKeyRingsUri();
|
||||||
|
|
||||||
String capFilter = null;
|
|
||||||
if (mFilterCertify) {
|
|
||||||
capFilter = "(cert > 0)";
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are the rows that we will retrieve.
|
// These are the rows that we will retrieve.
|
||||||
long now = new Date().getTime() / 1000;
|
long now = new Date().getTime() / 1000;
|
||||||
@ -125,29 +120,36 @@ public class SelectSecretKeyFragment extends ListFragment implements
|
|||||||
KeyRings._ID,
|
KeyRings._ID,
|
||||||
KeyRings.MASTER_KEY_ID,
|
KeyRings.MASTER_KEY_ID,
|
||||||
UserIds.USER_ID,
|
UserIds.USER_ID,
|
||||||
"(SELECT COUNT(cert_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||||
+ " AS cert_keys WHERE cert_keys." + Keys.KEY_RING_ROW_ID + " = "
|
+ " WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND cert_keys."
|
+ KeychainDatabase.Tables.KEYS + "." + KeyRings.MASTER_KEY_ID
|
||||||
+ Keys.CAN_CERTIFY + " = '1') AS cert",
|
+ " AND k." + Keys.CAN_CERTIFY + " = '1'"
|
||||||
"(SELECT COUNT(available_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
+ ") AS cert",
|
||||||
+ " AS available_keys WHERE available_keys." + Keys.KEY_RING_ROW_ID + " = "
|
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
|
+" WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||||
+ " AND available_keys." + Keys.IS_REVOKED + " = '0' AND available_keys."
|
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||||
+ Keys.CAN_SIGN + " = '1') AS "
|
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||||
+ SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
+ " AND k." + Keys.CAN_SIGN + " = '1'"
|
||||||
"(SELECT COUNT(valid_keys." + Keys._ID + ") FROM " + Tables.KEYS
|
+ ") AS " + SelectKeyCursorAdapter.PROJECTION_ROW_AVAILABLE,
|
||||||
+ " AS valid_keys WHERE valid_keys." + Keys.KEY_RING_ROW_ID + " = "
|
"(SELECT COUNT(*) FROM " + Tables.KEYS + " AS k"
|
||||||
+ KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND valid_keys."
|
+ " WHERE k." + Keys.MASTER_KEY_ID + " = "
|
||||||
+ Keys.IS_REVOKED + " = '0' AND valid_keys." + Keys.CAN_SIGN
|
+ KeychainDatabase.Tables.KEYS + "." + Keys.MASTER_KEY_ID
|
||||||
+ " = '1' AND valid_keys." + Keys.CREATION + " <= '" + now + "' AND "
|
+ " AND k." + Keys.IS_REVOKED + " = '0'"
|
||||||
+ "(valid_keys." + Keys.EXPIRY + " IS NULL OR valid_keys." + Keys.EXPIRY
|
+ " AND k." + Keys.CAN_SIGN + " = '1'"
|
||||||
+ " >= '" + now + "')) AS " + SelectKeyCursorAdapter.PROJECTION_ROW_VALID, };
|
+ " 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 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
|
// Now create and return a CursorLoader that will take care of
|
||||||
// creating a Cursor for the data being displayed.
|
// 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
|
@Override
|
||||||
|
@ -19,22 +19,26 @@ package org.sufficientlysecure.keychain.ui;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
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.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
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 mKeyUserId;
|
||||||
private TextView mKeyUserIdRest;
|
private TextView mKeyUserIdRest;
|
||||||
@ -43,10 +47,23 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
private BootstrapButton mSelectKeyButton;
|
private BootstrapButton mSelectKeyButton;
|
||||||
private Boolean mFilterCertify;
|
private Boolean mFilterCertify;
|
||||||
|
|
||||||
|
private Uri mReceivedUri = null;
|
||||||
|
|
||||||
private SelectSecretKeyCallback mCallback;
|
private SelectSecretKeyCallback mCallback;
|
||||||
|
|
||||||
private static final int REQUEST_CODE_SELECT_KEY = 8882;
|
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 {
|
public interface SelectSecretKeyCallback {
|
||||||
void onKeySelected(long secretKeyId);
|
void onKeySelected(long secretKeyId);
|
||||||
}
|
}
|
||||||
@ -59,68 +76,30 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
mFilterCertify = filterCertify;
|
mFilterCertify = filterCertify;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectKey(long secretKeyId) {
|
public void setNoKeySelected() {
|
||||||
if (secretKeyId == Id.key.none) {
|
mNoKeySelected.setVisibility(View.VISIBLE);
|
||||||
mNoKeySelected.setVisibility(View.VISIBLE);
|
mKeyUserId.setVisibility(View.GONE);
|
||||||
mKeyUserId.setVisibility(View.GONE);
|
mKeyUserIdRest.setVisibility(View.GONE);
|
||||||
mKeyUserIdRest.setVisibility(View.GONE);
|
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
||||||
mKeyMasterKeyIdHex.setVisibility(View.GONE);
|
}
|
||||||
|
|
||||||
} else {
|
public void setSelectedKeyData(String userName, String email, String masterKeyHex) {
|
||||||
PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByMasterKeyId(
|
|
||||||
getActivity(), secretKeyId);
|
|
||||||
if (keyRing != null) {
|
|
||||||
PGPSecretKey key = PgpKeyHelper.getMasterKey(keyRing);
|
|
||||||
String masterkeyIdHex = PgpKeyHelper.convertKeyIdToHex(secretKeyId);
|
|
||||||
|
|
||||||
if (key != null) {
|
mNoKeySelected.setVisibility(View.GONE);
|
||||||
String userId = PgpKeyHelper.getMainUserIdSafe(getActivity(), key);
|
|
||||||
|
|
||||||
String[] userIdSplit = PgpKeyHelper.splitUserId(userId);
|
mKeyUserId.setText(userName);
|
||||||
String userName, userEmail;
|
mKeyUserIdRest.setText(email);
|
||||||
|
mKeyMasterKeyIdHex.setText(masterKeyHex);
|
||||||
|
|
||||||
if (userIdSplit[0] != null) {
|
mKeyUserId.setVisibility(View.VISIBLE);
|
||||||
userName = userIdSplit[0];
|
mKeyUserIdRest.setVisibility(View.VISIBLE);
|
||||||
} else {
|
mKeyMasterKeyIdHex.setVisibility(View.VISIBLE);
|
||||||
userName = getActivity().getResources().getString(R.string.user_id_no_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
public void setError(String error) {
|
||||||
mKeyUserId.requestFocus();
|
mNoKeySelected.requestFocus();
|
||||||
mKeyUserId.setError(error);
|
mNoKeySelected.setError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,29 +126,78 @@ public class SelectSecretKeyLayoutFragment extends Fragment {
|
|||||||
return view;
|
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() {
|
private void startSelectKeyActivity() {
|
||||||
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
Intent intent = new Intent(getActivity(), SelectSecretKeyActivity.class);
|
||||||
intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_CERTIFY, mFilterCertify);
|
intent.putExtra(SelectSecretKeyActivity.EXTRA_FILTER_CERTIFY, mFilterCertify);
|
||||||
startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
|
startActivityForResult(intent, REQUEST_CODE_SELECT_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select Secret Key Activity delivers the intent which was sent by it using interface to Select
|
@Override
|
||||||
// Secret Key Fragment.Intent contains Master Key Id, User Email, User Name, Master Key Id Hex.
|
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
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
switch (requestCode & 0xFFFF) {
|
switch (requestCode) {
|
||||||
case REQUEST_CODE_SELECT_KEY: {
|
case REQUEST_CODE_SELECT_KEY: {
|
||||||
long secretKeyId;
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
Bundle bundle = data.getExtras();
|
mReceivedUri = data.getData();
|
||||||
secretKeyId = bundle.getLong(SelectSecretKeyActivity.RESULT_EXTRA_MASTER_KEY_ID);
|
|
||||||
selectKey(secretKeyId);
|
//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);
|
mKeyUserId.setError(null);
|
||||||
|
|
||||||
// give value back to callback
|
|
||||||
mCallback.onKeySelected(secretKeyId);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -98,11 +98,11 @@ public class UploadKeyActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
|
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,
|
KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this,
|
||||||
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
getString(R.string.progress_exporting), ProgressDialog.STYLE_HORIZONTAL) {
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentServiceHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
||||||
|
@ -37,7 +37,9 @@ import org.sufficientlysecure.keychain.Constants;
|
|||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpConversionHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
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 org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@ -50,15 +52,14 @@ public class ViewCertActivity extends ActionBarActivity
|
|||||||
|
|
||||||
// These are the rows that we will retrieve.
|
// These are the rows that we will retrieve.
|
||||||
static final String[] PROJECTION = new String[] {
|
static final String[] PROJECTION = new String[] {
|
||||||
KeychainContract.Certs._ID,
|
Certs.MASTER_KEY_ID,
|
||||||
KeychainContract.Certs.KEY_ID,
|
Certs.USER_ID,
|
||||||
KeychainContract.UserIds.USER_ID,
|
Certs.CREATION,
|
||||||
KeychainContract.Certs.CREATION,
|
Certs.KEY_ID_CERTIFIER,
|
||||||
KeychainContract.Certs.KEY_ID_CERTIFIER,
|
Certs.SIGNER_UID,
|
||||||
"signer_uid",
|
Certs.KEY_DATA
|
||||||
KeychainContract.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_USER_ID = 2;
|
||||||
private static final int INDEX_CREATION = 3;
|
private static final int INDEX_CREATION = 3;
|
||||||
private static final int INDEX_KEY_ID_CERTIFIER = 4;
|
private static final int INDEX_KEY_ID_CERTIFIER = 4;
|
||||||
@ -112,7 +113,7 @@ public class ViewCertActivity extends ActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
if(data.moveToFirst()) {
|
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);
|
mSigneeKey.setText(signeeKey);
|
||||||
|
|
||||||
String signeeUid = data.getString(INDEX_USER_ID);
|
String signeeUid = data.getString(INDEX_USER_ID);
|
||||||
@ -179,17 +180,21 @@ public class ViewCertActivity extends ActionBarActivity
|
|||||||
return true;
|
return true;
|
||||||
case R.id.menu_view_cert_view_signer:
|
case R.id.menu_view_cert_view_signer:
|
||||||
// can't do this before the data is initialized
|
// 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;
|
Intent viewIntent = null;
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
viewIntent = new Intent(this, ViewKeyActivity.class);
|
viewIntent = new Intent(this, ViewKeyActivity.class);
|
||||||
} else {
|
} else {
|
||||||
viewIntent = new Intent(this, ViewKeyActivityJB.class);
|
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);
|
startActivity(viewIntent);
|
||||||
return true;
|
return true;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.ui;
|
package org.sufficientlysecure.keychain.ui;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -28,6 +29,7 @@ import android.support.v7.app.ActionBar;
|
|||||||
import android.support.v7.app.ActionBarActivity;
|
import android.support.v7.app.ActionBarActivity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.Window;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
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.KeychainContract;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
|
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.ShareNfcDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
|
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@ -60,6 +60,7 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
mExportHelper = new ExportHelper(this);
|
mExportHelper = new ExportHelper(this);
|
||||||
@ -83,11 +84,7 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
|
selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize mDataUri to a "by row id" query, to ensure it works with any
|
mDataUri = getIntent().getData();
|
||||||
// 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));
|
|
||||||
|
|
||||||
Bundle mainBundle = new Bundle();
|
Bundle mainBundle = new Bundle();
|
||||||
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri);
|
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri);
|
||||||
@ -95,7 +92,7 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
ViewKeyMainFragment.class, mainBundle, (selectedTab == 0));
|
ViewKeyMainFragment.class, mainBundle, (selectedTab == 0));
|
||||||
|
|
||||||
Bundle certBundle = new Bundle();
|
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)),
|
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)),
|
||||||
ViewKeyCertsFragment.class, certBundle, (selectedTab == 1));
|
ViewKeyCertsFragment.class, certBundle, (selectedTab == 1));
|
||||||
}
|
}
|
||||||
@ -122,8 +119,12 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
uploadToKeyserver(mDataUri);
|
uploadToKeyserver(mDataUri);
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_key_view_export_file:
|
case R.id.menu_key_view_export_file:
|
||||||
long[] ids = new long[]{Long.valueOf(mDataUri.getLastPathSegment())};
|
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||||
mExportHelper.showExportKeysDialog(ids, Id.type.public_key, Constants.Path.APP_DIR_FILE_PUB);
|
mExportHelper.showExportKeysDialog(
|
||||||
|
new long[] { masterKeyId } , Constants.Path.APP_DIR_FILE_PUB,
|
||||||
|
// TODO this doesn't work?
|
||||||
|
((ViewKeyMainFragment) mTabsAdapter.getItem(0)).isSecretAvailable()
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_key_view_share_default_fingerprint:
|
case R.id.menu_key_view_share_default_fingerprint:
|
||||||
shareKey(mDataUri, true);
|
shareKey(mDataUri, true);
|
||||||
@ -158,33 +159,37 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateFromKeyserver(Uri dataUri) {
|
private void updateFromKeyserver(Uri dataUri) {
|
||||||
long updateKeyId = ProviderHelper.getMasterKeyId(ViewKeyActivity.this, dataUri);
|
byte[] blob = (byte[]) ProviderHelper.getGenericData(
|
||||||
|
this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||||
if (updateKeyId == 0) {
|
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||||
Log.e(Constants.TAG, "this shouldn't happen. KeyId == 0!");
|
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent queryIntent = new Intent(this, ImportKeysActivity.class);
|
Intent queryIntent = new Intent(this, ImportKeysActivity.class);
|
||||||
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER);
|
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN);
|
||||||
queryIntent.putExtra(ImportKeysActivity.EXTRA_KEY_ID, updateKeyId);
|
queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
|
||||||
|
|
||||||
// TODO: lookup with onactivityresult!
|
|
||||||
startActivityForResult(queryIntent, RESULT_CODE_LOOKUP_KEY);
|
startActivityForResult(queryIntent, RESULT_CODE_LOOKUP_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shareKey(Uri dataUri, boolean fingerprintOnly) {
|
private void shareKey(Uri dataUri, boolean fingerprintOnly) {
|
||||||
String content;
|
String content;
|
||||||
if (fingerprintOnly) {
|
if (fingerprintOnly) {
|
||||||
byte[] fingerprintBlob = ProviderHelper.getFingerprint(this, dataUri);
|
byte[] data = (byte[]) ProviderHelper.getGenericData(
|
||||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false);
|
this, KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
|
||||||
|
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
|
||||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
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 {
|
} else {
|
||||||
// get public keyring as ascii armored string
|
// get public keyring as ascii armored string
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this,
|
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||||
dataUri, new long[]{masterKeyId});
|
this, new long[]{ masterKeyId });
|
||||||
|
|
||||||
content = keyringArmored.get(0);
|
content = keyringArmored.get(0);
|
||||||
|
|
||||||
@ -214,8 +219,8 @@ public class ViewKeyActivity extends ActionBarActivity {
|
|||||||
private void copyToClipboard(Uri dataUri) {
|
private void copyToClipboard(Uri dataUri) {
|
||||||
// get public keyring as ascii armored string
|
// get public keyring as ascii armored string
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
long masterKeyId = ProviderHelper.getMasterKeyId(this, dataUri);
|
||||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(this, dataUri,
|
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||||
new long[]{masterKeyId});
|
this, new long[]{ masterKeyId });
|
||||||
|
|
||||||
ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
|
ClipboardReflection.copyToClipboard(this, keyringArmored.get(0));
|
||||||
Toast.makeText(getApplicationContext(), R.string.key_copied_to_clipboard, Toast.LENGTH_LONG)
|
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() {
|
Handler returnHandler = new Handler() {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
if (message.what == DeleteKeyDialogFragment.MESSAGE_OKAY) {
|
setResult(RESULT_CANCELED);
|
||||||
Bundle returnData = message.getData();
|
finish();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||||
public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
|
public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMessageCallback,
|
||||||
@ -47,26 +50,18 @@ public class ViewKeyActivityJB extends ViewKeyActivity implements CreateNdefMess
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
initNfc(mDataUri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NFC: Initialize NFC sharing if OS and device supports it
|
* 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)
|
// check if NFC Beam is supported (>= Android 4.1)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
// Check for available NFC Adapter
|
// Check for available NFC Adapter
|
||||||
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||||
if (mNfcAdapter != null) {
|
if (mNfcAdapter != null) {
|
||||||
// init nfc
|
// 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
|
// Register callback to set NDEF message
|
||||||
mNfcAdapter.setNdefPushMessageCallback(this, this);
|
mNfcAdapter.setNdefPushMessageCallback(this, this);
|
||||||
// Register callback to listen for message-sent success
|
// 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
|
* guarantee that this activity starts when receiving a beamed message. For now, this code
|
||||||
* uses the tag dispatch system.
|
* uses the tag dispatch system.
|
||||||
*/
|
*/
|
||||||
NdefMessage msg = new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME,
|
// get public keyring as byte array
|
||||||
mSharedKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME));
|
long masterKeyId = ProviderHelper.getMasterKeyId(this, mDataUri);
|
||||||
return msg;
|
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.
|
// These are the rows that we will retrieve.
|
||||||
static final String[] PROJECTION = new String[] {
|
static final String[] PROJECTION = new String[] {
|
||||||
KeychainContract.Certs._ID,
|
KeychainContract.Certs._ID,
|
||||||
|
KeychainContract.Certs.MASTER_KEY_ID,
|
||||||
KeychainContract.Certs.VERIFIED,
|
KeychainContract.Certs.VERIFIED,
|
||||||
KeychainContract.Certs.RANK,
|
KeychainContract.Certs.RANK,
|
||||||
KeychainContract.Certs.KEY_ID_CERTIFIER,
|
KeychainContract.Certs.KEY_ID_CERTIFIER,
|
||||||
KeychainContract.UserIds.USER_ID,
|
KeychainContract.Certs.USER_ID,
|
||||||
"signer_uid"
|
KeychainContract.Certs.SIGNER_UID
|
||||||
};
|
};
|
||||||
|
|
||||||
// sort by our user id,
|
// sort by our user id,
|
||||||
static final String SORT_ORDER =
|
static final String SORT_ORDER =
|
||||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " ASC, "
|
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " ASC, "
|
||||||
+ KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.VERIFIED + " DESC, "
|
+ 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 StickyListHeadersListView mStickyList;
|
||||||
private Spinner mSpinner;
|
private Spinner mSpinner;
|
||||||
@ -125,14 +126,14 @@ public class ViewKeyCertsFragment extends Fragment
|
|||||||
|
|
||||||
mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
|
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!");
|
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
|
||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
long rowId = getArguments().getLong(ARG_KEYRING_ROW_ID);
|
Uri uri = getArguments().getParcelable(ARG_DATA_URI);
|
||||||
mBaseUri = KeychainContract.Certs.buildCertsByKeyRowIdUri(Long.toString(rowId));
|
mBaseUri = KeychainContract.Certs.buildCertsUri(uri);
|
||||||
|
|
||||||
mStickyList.setAreHeadersSticky(true);
|
mStickyList.setAreHeadersSticky(true);
|
||||||
mStickyList.setDrawingListUnderStickyHeader(false);
|
mStickyList.setDrawingListUnderStickyHeader(false);
|
||||||
@ -229,12 +230,12 @@ public class ViewKeyCertsFragment extends Fragment
|
|||||||
private void initIndex(Cursor cursor) {
|
private void initIndex(Cursor cursor) {
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
|
||||||
mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs._ID);
|
mIndexCertId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID);
|
||||||
mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID);
|
mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID);
|
||||||
mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.RANK);
|
mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.RANK);
|
||||||
mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED);
|
mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED);
|
||||||
mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER);
|
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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.beardedhen.androidbootstrap.BootstrapButton;
|
import com.beardedhen.androidbootstrap.BootstrapButton;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
|
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.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
|
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
|
||||||
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
|
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";
|
public static final String ARG_DATA_URI = "uri";
|
||||||
|
|
||||||
|
private LinearLayout mContainer;
|
||||||
private TextView mName;
|
private TextView mName;
|
||||||
private TextView mEmail;
|
private TextView mEmail;
|
||||||
private TextView mComment;
|
private TextView mComment;
|
||||||
@ -68,7 +73,7 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
private ListView mUserIds;
|
private ListView mUserIds;
|
||||||
private ListView mKeys;
|
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_USER_IDS = 1;
|
||||||
private static final int LOADER_ID_KEYS = 2;
|
private static final int LOADER_ID_KEYS = 2;
|
||||||
|
|
||||||
@ -77,10 +82,14 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
|
|
||||||
private Uri mDataUri;
|
private Uri mDataUri;
|
||||||
|
|
||||||
|
// for activity
|
||||||
|
private boolean mSecretAvailable = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.view_key_main_fragment, container, false);
|
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);
|
mName = (TextView) view.findViewById(R.id.name);
|
||||||
mEmail = (TextView) view.findViewById(R.id.email);
|
mEmail = (TextView) view.findViewById(R.id.email);
|
||||||
mComment = (TextView) view.findViewById(R.id.comment);
|
mComment = (TextView) view.findViewById(R.id.comment);
|
||||||
@ -119,64 +128,24 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getActivity().setProgressBarIndeterminateVisibility(Boolean.TRUE);
|
||||||
|
mContainer.setVisibility(View.GONE);
|
||||||
|
|
||||||
mDataUri = dataUri;
|
mDataUri = dataUri;
|
||||||
|
|
||||||
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
|
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() {
|
mActionEncrypt.setOnClickListener(new View.OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
encryptToContact(mDataUri);
|
encryptToContact(mDataUri);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
mActionCertify.setOnClickListener(new View.OnClickListener() {
|
||||||
|
public void onClick(View view) {
|
||||||
|
certifyKey(mDataUri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
|
mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
|
||||||
mUserIds.setAdapter(mUserIdsAdapter);
|
mUserIds.setAdapter(mUserIdsAdapter);
|
||||||
@ -186,74 +155,51 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
|
|
||||||
// Prepare the loaders. Either re-connect with an existing ones,
|
// Prepare the loaders. Either re-connect with an existing ones,
|
||||||
// or start new 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_USER_IDS, null, this);
|
||||||
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
|
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final String[] KEYRING_PROJECTION =
|
static final String[] UNIFIED_PROJECTION = new String[] {
|
||||||
new String[]{KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID,
|
KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_SECRET,
|
||||||
KeychainContract.UserIds.USER_ID};
|
KeyRings.USER_ID, KeyRings.FINGERPRINT,
|
||||||
static final int KEYRING_INDEX_ID = 0;
|
KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY,
|
||||||
static final int KEYRING_INDEX_MASTER_KEY_ID = 1;
|
|
||||||
static final int KEYRING_INDEX_USER_ID = 2;
|
|
||||||
|
|
||||||
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 int INDEX_UNIFIED_MKI = 1;
|
||||||
static final String USER_IDS_SELECTION =
|
static final int INDEX_UNIFIED_HAS_SECRET = 2;
|
||||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " > 0 ";
|
static final int INDEX_UNIFIED_UID = 3;
|
||||||
static final String USER_IDS_SORT_ORDER =
|
static final int INDEX_UNIFIED_FINGERPRINT = 4;
|
||||||
KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.RANK + " COLLATE LOCALIZED ASC";
|
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[]{
|
static final String[] USER_IDS_PROJECTION = new String[] {
|
||||||
KeychainContract.Keys._ID, KeychainContract.Keys.KEY_ID,
|
UserIds._ID, UserIds.USER_ID, UserIds.RANK,
|
||||||
KeychainContract.Keys.IS_MASTER_KEY, KeychainContract.Keys.ALGORITHM,
|
};
|
||||||
KeychainContract.Keys.KEY_SIZE, KeychainContract.Keys.CAN_CERTIFY,
|
|
||||||
KeychainContract.Keys.CAN_SIGN, KeychainContract.Keys.CAN_ENCRYPT,
|
static final String[] KEYS_PROJECTION = new String[] {
|
||||||
KeychainContract.Keys.CREATION, KeychainContract.Keys.EXPIRY,
|
Keys._ID,
|
||||||
KeychainContract.Keys.FINGERPRINT
|
Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE,
|
||||||
};
|
Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
|
||||||
static final String KEYS_SORT_ORDER = KeychainContract.Keys.RANK + " ASC";
|
Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
|
||||||
static final int KEYS_INDEX_ID = 0;
|
};
|
||||||
static final int KEYS_INDEX_KEY_ID = 1;
|
static final int KEYS_INDEX_CAN_ENCRYPT = 6;
|
||||||
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;
|
|
||||||
|
|
||||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case LOADER_ID_KEYRING: {
|
case LOADER_ID_UNIFIED: {
|
||||||
Uri baseUri = mDataUri;
|
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
|
||||||
|
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
|
||||||
// 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_USER_IDS: {
|
case LOADER_ID_USER_IDS: {
|
||||||
Uri baseUri = KeychainContract.UserIds.buildUserIdsUri(mDataUri);
|
Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
|
||||||
|
return new CursorLoader(getActivity(), baseUri, USER_IDS_PROJECTION, null, null, null);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
case LOADER_ID_KEYS: {
|
case LOADER_ID_KEYS: {
|
||||||
Uri baseUri = KeychainContract.Keys.buildKeysUri(mDataUri);
|
Uri baseUri = Keys.buildKeysUri(mDataUri);
|
||||||
|
return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -262,14 +208,19 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
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
|
// Swap the new cursor in. (The framework will take care of closing the
|
||||||
// old cursor once we return.)
|
// old cursor once we return.)
|
||||||
switch (loader.getId()) {
|
switch (loader.getId()) {
|
||||||
case LOADER_ID_KEYRING:
|
case LOADER_ID_UNIFIED: {
|
||||||
if (data.moveToFirst()) {
|
if (data.moveToFirst()) {
|
||||||
// get name, email, and comment from USER_ID
|
// get name, email, and comment from USER_ID
|
||||||
String[] mainUserId = PgpKeyHelper.splitUserId(data
|
String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID));
|
||||||
.getString(KEYRING_INDEX_USER_ID));
|
|
||||||
if (mainUserId[0] != null) {
|
if (mainUserId[0] != null) {
|
||||||
getActivity().setTitle(mainUserId[0]);
|
getActivity().setTitle(mainUserId[0]);
|
||||||
mName.setText(mainUserId[0]);
|
mName.setText(mainUserId[0]);
|
||||||
@ -279,63 +230,97 @@ public class ViewKeyMainFragment extends Fragment implements
|
|||||||
}
|
}
|
||||||
mEmail.setText(mainUserId[1]);
|
mEmail.setText(mainUserId[1]);
|
||||||
mComment.setText(mainUserId[2]);
|
mComment.setText(mainUserId[2]);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
if (data.getInt(INDEX_UNIFIED_HAS_SECRET) != 0) {
|
||||||
case LOADER_ID_USER_IDS:
|
mSecretAvailable = true;
|
||||||
mUserIdsAdapter.swapCursor(data);
|
|
||||||
break;
|
mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
|
||||||
case LOADER_ID_KEYS:
|
mSecretKey.setText(R.string.secret_key_yes);
|
||||||
// the first key here is our master key
|
|
||||||
if (data.moveToFirst()) {
|
// 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
|
// get key id from MASTER_KEY_ID
|
||||||
long keyId = data.getLong(KEYS_INDEX_KEY_ID);
|
long masterKeyId = data.getLong(INDEX_UNIFIED_MKI);
|
||||||
|
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
|
||||||
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(keyId);
|
|
||||||
mKeyId.setText(keyIdStr);
|
mKeyId.setText(keyIdStr);
|
||||||
|
|
||||||
// get creation date from CREATION
|
// get creation date from CREATION
|
||||||
if (data.isNull(KEYS_INDEX_CREATION)) {
|
if (data.isNull(INDEX_UNIFIED_CREATION)) {
|
||||||
mCreation.setText(R.string.none);
|
mCreation.setText(R.string.none);
|
||||||
} else {
|
} else {
|
||||||
Date creationDate = new Date(data.getLong(KEYS_INDEX_CREATION) * 1000);
|
Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000);
|
||||||
|
|
||||||
mCreation.setText(
|
mCreation.setText(
|
||||||
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
||||||
creationDate));
|
creationDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
// get expiry date from EXPIRY
|
// get expiry date from EXPIRY
|
||||||
if (data.isNull(KEYS_INDEX_EXPIRY)) {
|
if (data.isNull(INDEX_UNIFIED_EXPIRY)) {
|
||||||
mExpiry.setText(R.string.none);
|
mExpiry.setText(R.string.none);
|
||||||
} else {
|
} else {
|
||||||
Date expiryDate = new Date(data.getLong(KEYS_INDEX_EXPIRY) * 1000);
|
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
|
||||||
|
|
||||||
mExpiry.setText(
|
mExpiry.setText(
|
||||||
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
|
||||||
expiryDate));
|
expiryDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
|
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);
|
mAlgorithm.setText(algorithmStr);
|
||||||
|
|
||||||
byte[] fingerprintBlob = data.getBlob(KEYS_INDEX_FINGERPRINT);
|
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
|
||||||
if (fingerprintBlob == null) {
|
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
|
||||||
// FALLBACK for old database entries
|
mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
|
||||||
fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), mDataUri);
|
|
||||||
}
|
|
||||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, true);
|
|
||||||
|
|
||||||
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);
|
mKeysAdapter.swapCursor(data);
|
||||||
break;
|
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) {
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
switch (loader.getId()) {
|
switch (loader.getId()) {
|
||||||
case LOADER_ID_KEYRING:
|
|
||||||
// No resources need to be freed for this ID
|
|
||||||
break;
|
|
||||||
case LOADER_ID_USER_IDS:
|
case LOADER_ID_USER_IDS:
|
||||||
mUserIdsAdapter.swapCursor(null);
|
mUserIdsAdapter.swapCursor(null);
|
||||||
break;
|
break;
|
||||||
case LOADER_ID_KEYS:
|
case LOADER_ID_KEYS:
|
||||||
mKeysAdapter.swapCursor(null);
|
mKeysAdapter.swapCursor(null);
|
||||||
break;
|
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) {
|
private void encryptToContact(Uri dataUri) {
|
||||||
|
// TODO preselect from uri? should be feasible without trivial query
|
||||||
long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
long keyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
||||||
|
|
||||||
long[] encryptionKeyIds = new long[]{keyId};
|
long[] encryptionKeyIds = new long[]{ keyId };
|
||||||
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
Intent intent = new Intent(getActivity(), EncryptActivity.class);
|
||||||
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
|
||||||
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
|
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
|
||||||
|
@ -30,6 +30,7 @@ import android.widget.CheckBox;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.LinearLayout.LayoutParams;
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
|
|
||||||
@ -43,13 +44,12 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
protected List<ImportKeysListEntry> mData;
|
protected List<ImportKeysListEntry> mData;
|
||||||
|
|
||||||
static class ViewHolder {
|
static class ViewHolder {
|
||||||
private TextView mMainUserId;
|
public TextView mainUserId;
|
||||||
private TextView mMainUserIdRest;
|
public TextView mainUserIdRest;
|
||||||
private TextView mKeyId;
|
public TextView keyId;
|
||||||
private TextView mFingerprint;
|
public TextView fingerprint;
|
||||||
private TextView mAlgorithm;
|
public TextView algorithm;
|
||||||
private TextView mStatus;
|
public TextView status;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImportKeysAdapter(Activity activity) {
|
public ImportKeysAdapter(Activity activity) {
|
||||||
@ -100,12 +100,12 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
holder = new ViewHolder();
|
holder = new ViewHolder();
|
||||||
convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
|
convertView = mInflater.inflate(R.layout.import_keys_list_entry, null);
|
||||||
holder.mMainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
|
holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
|
||||||
holder.mMainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
|
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
|
||||||
holder.mKeyId = (TextView) convertView.findViewById(R.id.keyId);
|
holder.keyId = (TextView) convertView.findViewById(R.id.keyId);
|
||||||
holder.mFingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
|
holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
|
||||||
holder.mAlgorithm = (TextView) convertView.findViewById(R.id.algorithm);
|
holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
|
||||||
holder.mStatus = (TextView) convertView.findViewById(R.id.status);
|
holder.status = (TextView) convertView.findViewById(R.id.status);
|
||||||
convertView.setTag(holder);
|
convertView.setTag(holder);
|
||||||
} else {
|
} else {
|
||||||
holder = (ViewHolder) convertView.getTag();
|
holder = (ViewHolder) convertView.getTag();
|
||||||
@ -119,36 +119,36 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
|
|||||||
// show red user id if it is a secret key
|
// show red user id if it is a secret key
|
||||||
if (entry.secretKey) {
|
if (entry.secretKey) {
|
||||||
userIdSplit[0] = mActivity.getString(R.string.secret_key) + " " + userIdSplit[0];
|
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 {
|
} else {
|
||||||
holder.mMainUserId.setText(R.string.user_id_no_name);
|
holder.mainUserId.setText(R.string.user_id_no_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// email
|
// email
|
||||||
if (userIdSplit[1] != null) {
|
if (userIdSplit[1] != null) {
|
||||||
holder.mMainUserIdRest.setText(userIdSplit[1]);
|
holder.mainUserIdRest.setText(userIdSplit[1]);
|
||||||
holder.mMainUserIdRest.setVisibility(View.VISIBLE);
|
holder.mainUserIdRest.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} 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) {
|
if (entry.fingerPrintHex != null) {
|
||||||
holder.mFingerprint.setText(mActivity.getString(R.string.fingerprint) + " " + entry.fingerPrint);
|
holder.fingerprint.setText(PgpKeyHelper.colorizeFingerprint(entry.fingerPrintHex));
|
||||||
holder.mFingerprint.setVisibility(View.VISIBLE);
|
holder.fingerprint.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} 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) {
|
if (entry.revoked) {
|
||||||
holder.mStatus.setText(R.string.revoked);
|
holder.status.setText(R.string.revoked);
|
||||||
} else {
|
} else {
|
||||||
holder.mStatus.setVisibility(View.GONE);
|
holder.status.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
LinearLayout ll = (LinearLayout) convertView.findViewById(R.id.list);
|
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.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import org.spongycastle.openpgp.PGPKeyRing;
|
import org.spongycastle.openpgp.PGPKeyRing;
|
||||||
import org.spongycastle.openpgp.PGPPublicKey;
|
import org.spongycastle.openpgp.PGPPublicKey;
|
||||||
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
import org.spongycastle.openpgp.PGPSecretKeyRing;
|
||||||
@ -34,13 +36,13 @@ import java.util.Date;
|
|||||||
|
|
||||||
public class ImportKeysListEntry implements Serializable, Parcelable {
|
public class ImportKeysListEntry implements Serializable, Parcelable {
|
||||||
private static final long serialVersionUID = -7797972103284992662L;
|
private static final long serialVersionUID = -7797972103284992662L;
|
||||||
public ArrayList<String> userIds;
|
|
||||||
|
|
||||||
|
public ArrayList<String> userIds;
|
||||||
public long keyId;
|
public long keyId;
|
||||||
|
public String keyIdHex;
|
||||||
public boolean revoked;
|
public boolean revoked;
|
||||||
public Date date; // TODO: not displayed
|
public Date date; // TODO: not displayed
|
||||||
public String fingerPrint;
|
public String fingerPrintHex;
|
||||||
public String hexKeyId;
|
|
||||||
public int bitStrength;
|
public int bitStrength;
|
||||||
public String algorithm;
|
public String algorithm;
|
||||||
public boolean secretKey;
|
public boolean secretKey;
|
||||||
@ -54,8 +56,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
this.keyId = b.keyId;
|
this.keyId = b.keyId;
|
||||||
this.revoked = b.revoked;
|
this.revoked = b.revoked;
|
||||||
this.date = b.date;
|
this.date = b.date;
|
||||||
this.fingerPrint = b.fingerPrint;
|
this.fingerPrintHex = b.fingerPrintHex;
|
||||||
this.hexKeyId = b.hexKeyId;
|
this.keyIdHex = b.keyIdHex;
|
||||||
this.bitStrength = b.bitStrength;
|
this.bitStrength = b.bitStrength;
|
||||||
this.algorithm = b.algorithm;
|
this.algorithm = b.algorithm;
|
||||||
this.secretKey = b.secretKey;
|
this.secretKey = b.secretKey;
|
||||||
@ -73,8 +75,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
dest.writeLong(keyId);
|
dest.writeLong(keyId);
|
||||||
dest.writeByte((byte) (revoked ? 1 : 0));
|
dest.writeByte((byte) (revoked ? 1 : 0));
|
||||||
dest.writeSerializable(date);
|
dest.writeSerializable(date);
|
||||||
dest.writeString(fingerPrint);
|
dest.writeString(fingerPrintHex);
|
||||||
dest.writeString(hexKeyId);
|
dest.writeString(keyIdHex);
|
||||||
dest.writeInt(bitStrength);
|
dest.writeInt(bitStrength);
|
||||||
dest.writeString(algorithm);
|
dest.writeString(algorithm);
|
||||||
dest.writeByte((byte) (secretKey ? 1 : 0));
|
dest.writeByte((byte) (secretKey ? 1 : 0));
|
||||||
@ -91,8 +93,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
vr.keyId = source.readLong();
|
vr.keyId = source.readLong();
|
||||||
vr.revoked = source.readByte() == 1;
|
vr.revoked = source.readByte() == 1;
|
||||||
vr.date = (Date) source.readSerializable();
|
vr.date = (Date) source.readSerializable();
|
||||||
vr.fingerPrint = source.readString();
|
vr.fingerPrintHex = source.readString();
|
||||||
vr.hexKeyId = source.readString();
|
vr.keyIdHex = source.readString();
|
||||||
vr.bitStrength = source.readInt();
|
vr.bitStrength = source.readInt();
|
||||||
vr.algorithm = source.readString();
|
vr.algorithm = source.readString();
|
||||||
vr.secretKey = source.readByte() == 1;
|
vr.secretKey = source.readByte() == 1;
|
||||||
@ -108,8 +110,8 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public long getKeyId() {
|
public String getKeyIdHex() {
|
||||||
return keyId;
|
return keyIdHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getBytes() {
|
public byte[] getBytes() {
|
||||||
@ -120,6 +122,82 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
this.mBytes = bytes;
|
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
|
* Constructor for later querying from keyserver
|
||||||
*/
|
*/
|
||||||
@ -131,14 +209,6 @@ public class ImportKeysListEntry implements Serializable, Parcelable {
|
|||||||
userIds = new ArrayList<String>();
|
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
|
* 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())) {
|
for (String userId : new IterableIterator<String>(pgpKeyRing.getPublicKey().getUserIDs())) {
|
||||||
userIds.add(userId);
|
userIds.add(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keyId = pgpKeyRing.getPublicKey().getKeyID();
|
this.keyId = pgpKeyRing.getPublicKey().getKeyID();
|
||||||
|
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
|
||||||
|
|
||||||
this.revoked = pgpKeyRing.getPublicKey().isRevoked();
|
this.revoked = pgpKeyRing.getPublicKey().isRevoked();
|
||||||
this.fingerPrint = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
|
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(pgpKeyRing.getPublicKey()
|
||||||
.getFingerprint(), true);
|
.getFingerprint());
|
||||||
this.hexKeyId = PgpKeyHelper.convertKeyIdToHex(keyId);
|
|
||||||
this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
|
this.bitStrength = pgpKeyRing.getPublicKey().getBitStrength();
|
||||||
int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
|
final int algorithm = pgpKeyRing.getPublicKey().getAlgorithm();
|
||||||
if (algorithm == PGPPublicKey.RSA_ENCRYPT || algorithm == PGPPublicKey.RSA_GENERAL
|
this.algorithm = getAlgorithmFromId(algorithm);
|
||||||
|| algorithm == PGPPublicKey.RSA_SIGN) {
|
}
|
||||||
this.algorithm = "RSA";
|
|
||||||
} else if (algorithm == PGPPublicKey.DSA) {
|
/**
|
||||||
this.algorithm = "DSA";
|
* Based on <a href="http://tools.ietf.org/html/rfc2440#section-9.1">OpenPGP Message Format</a>
|
||||||
} else if (algorithm == PGPPublicKey.ELGAMAL_ENCRYPT
|
*/
|
||||||
|| algorithm == PGPPublicKey.ELGAMAL_GENERAL) {
|
private static final SparseArray<String> ALGORITHM_IDS = new SparseArray<String>() {{
|
||||||
this.algorithm = "ElGamal";
|
put(-1, "unknown"); // TODO: with resources
|
||||||
} else if (algorithm == PGPPublicKey.EC || algorithm == PGPPublicKey.ECDSA) {
|
put(0, "unencrypted");
|
||||||
this.algorithm = "ECC";
|
put(PGPPublicKey.RSA_GENERAL, "RSA");
|
||||||
} else {
|
put(PGPPublicKey.RSA_ENCRYPT, "RSA");
|
||||||
// TODO: with resources
|
put(PGPPublicKey.RSA_SIGN, "RSA");
|
||||||
this.algorithm = "unknown";
|
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.content.Context;
|
||||||
import android.support.v4.content.AsyncTaskLoader;
|
import android.support.v4.content.AsyncTaskLoader;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
import org.sufficientlysecure.keychain.util.HkpKeyServer;
|
||||||
import org.sufficientlysecure.keychain.util.KeyServer;
|
import org.sufficientlysecure.keychain.util.KeyServer;
|
||||||
@ -53,7 +54,12 @@ public class ImportKeysListServerLoader
|
|||||||
return mEntryListWrapper;
|
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;
|
return mEntryListWrapper;
|
||||||
}
|
}
|
||||||
@ -84,14 +90,30 @@ public class ImportKeysListServerLoader
|
|||||||
/**
|
/**
|
||||||
* Query keyserver
|
* Query keyserver
|
||||||
*/
|
*/
|
||||||
private void queryServer(String query, String keyServer) {
|
private void queryServer(String query, String keyServer, boolean enforceFingerprint) {
|
||||||
HkpKeyServer server = new HkpKeyServer(keyServer);
|
HkpKeyServer server = new HkpKeyServer(keyServer);
|
||||||
try {
|
try {
|
||||||
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
|
||||||
|
|
||||||
mEntryList.clear();
|
mEntryList.clear();
|
||||||
// add result to data
|
// 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);
|
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
|
||||||
} catch (KeyServer.InsufficientQuery e) {
|
} catch (KeyServer.InsufficientQuery e) {
|
||||||
Log.e(Constants.TAG, "InsufficientQuery", e);
|
Log.e(Constants.TAG, "InsufficientQuery", e);
|
||||||
|
@ -20,7 +20,11 @@ package org.sufficientlysecure.keychain.ui.adapter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.widget.ArrayAdapter;
|
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> {
|
public class KeyValueSpinnerAdapter extends ArrayAdapter<String> {
|
||||||
private final HashMap<Integer, String> mData;
|
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?
|
// TODO: needed to key id to no?
|
||||||
keyId.setText(R.string.no_key);
|
keyId.setText(R.string.no_key);
|
||||||
long masterKeyId = cursor.getLong(mIndexMasterKeyId);
|
long masterKeyId = cursor.getLong(mIndexMasterKeyId);
|
||||||
keyId.setText(PgpKeyHelper.convertKeyIdToHex(masterKeyId));
|
keyId.setText(PgpKeyHelper.convertKeyIdToHexShort(masterKeyId));
|
||||||
|
|
||||||
// TODO: needed to set unknown_status?
|
// TODO: needed to set unknown_status?
|
||||||
status.setText(R.string.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>();
|
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
|
||||||
|
|
||||||
static final class TabInfo {
|
static final class TabInfo {
|
||||||
private final Class<?> mClss;
|
public final Class<?> clss;
|
||||||
private final Bundle mArgs;
|
public final Bundle args;
|
||||||
|
|
||||||
TabInfo(Class<?> mClss, Bundle mArgs) {
|
TabInfo(Class<?> clss, Bundle args) {
|
||||||
this.mClss = mClss;
|
this.clss = clss;
|
||||||
this.mArgs = mArgs;
|
this.args = args;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.
|
|||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
TabInfo info = mTabs.get(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) {
|
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||||
|
@ -18,27 +18,36 @@
|
|||||||
package org.sufficientlysecure.keychain.ui.adapter;
|
package org.sufficientlysecure.keychain.ui.adapter;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
|
import android.text.format.DateFormat;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
|
import org.sufficientlysecure.keychain.helper.OtherHelper;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
public class ViewKeyKeysAdapter extends CursorAdapter {
|
public class ViewKeyKeysAdapter extends CursorAdapter {
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
private int mIndexKeyId;
|
private int mIndexKeyId;
|
||||||
private int mIndexAlgorithm;
|
private int mIndexAlgorithm;
|
||||||
private int mIndexKeySize;
|
private int mIndexKeySize;
|
||||||
private int mIndexIsMasterKey;
|
private int mIndexRank;
|
||||||
private int mIndexCanCertify;
|
private int mIndexCanCertify;
|
||||||
private int mIndexCanEncrypt;
|
private int mIndexCanEncrypt;
|
||||||
private int mIndexCanSign;
|
private int mIndexCanSign;
|
||||||
|
private int mIndexRevokedKey;
|
||||||
|
private int mIndexExpiry;
|
||||||
|
|
||||||
|
private ColorStateList mDefaultTextColor;
|
||||||
|
|
||||||
public ViewKeyKeysAdapter(Context context, Cursor c, int flags) {
|
public ViewKeyKeysAdapter(Context context, Cursor c, int flags) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
@ -66,10 +75,12 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
|
|||||||
mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID);
|
mIndexKeyId = cursor.getColumnIndexOrThrow(Keys.KEY_ID);
|
||||||
mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM);
|
mIndexAlgorithm = cursor.getColumnIndexOrThrow(Keys.ALGORITHM);
|
||||||
mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE);
|
mIndexKeySize = cursor.getColumnIndexOrThrow(Keys.KEY_SIZE);
|
||||||
mIndexIsMasterKey = cursor.getColumnIndexOrThrow(Keys.IS_MASTER_KEY);
|
mIndexRank = cursor.getColumnIndexOrThrow(Keys.RANK);
|
||||||
mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
|
mIndexCanCertify = cursor.getColumnIndexOrThrow(Keys.CAN_CERTIFY);
|
||||||
mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
|
mIndexCanEncrypt = cursor.getColumnIndexOrThrow(Keys.CAN_ENCRYPT);
|
||||||
mIndexCanSign = cursor.getColumnIndexOrThrow(Keys.CAN_SIGN);
|
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) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
TextView keyId = (TextView) view.findViewById(R.id.keyId);
|
||||||
TextView keyDetails = (TextView) view.findViewById(R.id.keyDetails);
|
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 masterKeyIcon = (ImageView) view.findViewById(R.id.ic_masterKey);
|
||||||
ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
ImageView certifyIcon = (ImageView) view.findViewById(R.id.ic_certifyKey);
|
||||||
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
ImageView encryptIcon = (ImageView) view.findViewById(R.id.ic_encryptKey);
|
||||||
ImageView signIcon = (ImageView) view.findViewById(R.id.ic_signKey);
|
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),
|
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(cursor.getInt(mIndexAlgorithm),
|
||||||
cursor.getInt(mIndexKeySize));
|
cursor.getInt(mIndexKeySize));
|
||||||
|
|
||||||
keyId.setText(keyIdStr);
|
keyId.setText(keyIdStr);
|
||||||
|
|
||||||
keyDetails.setText("(" + algorithmStr + ")");
|
keyDetails.setText("(" + algorithmStr + ")");
|
||||||
|
|
||||||
if (cursor.getInt(mIndexIsMasterKey) != 1) {
|
if (cursor.getInt(mIndexRank) == 0) {
|
||||||
masterKeyIcon.setVisibility(View.INVISIBLE);
|
masterKeyIcon.setVisibility(View.INVISIBLE);
|
||||||
} else {
|
} else {
|
||||||
masterKeyIcon.setVisibility(View.VISIBLE);
|
masterKeyIcon.setVisibility(View.VISIBLE);
|
||||||
@ -113,11 +125,51 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
|
|||||||
} else {
|
} else {
|
||||||
signIcon.setVisibility(View.VISIBLE);
|
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
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
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 mIndexUserId, mIndexRank;
|
||||||
private int mVerifiedId;
|
private int mVerifiedId;
|
||||||
|
|
||||||
final private ArrayList<Boolean> mCheckStates;
|
private final ArrayList<Boolean> mCheckStates;
|
||||||
|
|
||||||
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
|
public ViewKeyUserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
|
||||||
super(context, c, flags);
|
super(context, c, flags);
|
||||||
@ -57,9 +57,9 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public Cursor swapCursor(Cursor newCursor) {
|
public Cursor swapCursor(Cursor newCursor) {
|
||||||
initIndex(newCursor);
|
initIndex(newCursor);
|
||||||
if(mCheckStates != null) {
|
if (mCheckStates != null) {
|
||||||
mCheckStates.clear();
|
mCheckStates.clear();
|
||||||
if(newCursor != null) {
|
if (newCursor != null) {
|
||||||
int count = newCursor.getCount();
|
int count = newCursor.getCount();
|
||||||
mCheckStates.ensureCapacity(count);
|
mCheckStates.ensureCapacity(count);
|
||||||
// initialize to true (use case knowledge: we usually want to sign all uids)
|
// 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) {
|
if (cursor != null) {
|
||||||
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
mIndexUserId = cursor.getColumnIndexOrThrow(UserIds.USER_ID);
|
||||||
mIndexRank = cursor.getColumnIndexOrThrow(UserIds.RANK);
|
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]);
|
vAddress.setText(userId[1]);
|
||||||
|
|
||||||
int verified = cursor.getInt(mVerifiedId);
|
int verified = 1; // cursor.getInt(mVerifiedId);
|
||||||
// TODO introduce own resource for this :)
|
// TODO introduce own resource for this :)
|
||||||
if(verified > 0)
|
if(verified > 0)
|
||||||
vVerified.setImageResource(android.R.drawable.presence_online);
|
vVerified.setImageResource(android.R.drawable.presence_online);
|
||||||
@ -114,8 +114,9 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
|||||||
vVerified.setImageResource(android.R.drawable.presence_invisible);
|
vVerified.setImageResource(android.R.drawable.presence_invisible);
|
||||||
|
|
||||||
// don't care further if checkboxes aren't shown
|
// don't care further if checkboxes aren't shown
|
||||||
if(mCheckStates == null)
|
if (mCheckStates == null) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox);
|
final CheckBox vCheckBox = (CheckBox) view.findViewById(R.id.checkBox);
|
||||||
final int position = cursor.getPosition();
|
final int position = cursor.getPosition();
|
||||||
@ -138,8 +139,8 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter {
|
|||||||
|
|
||||||
public ArrayList<String> getSelectedUserIds() {
|
public ArrayList<String> getSelectedUserIds() {
|
||||||
ArrayList<String> result = new ArrayList<String>();
|
ArrayList<String> result = new ArrayList<String>();
|
||||||
for(int i = 0; i < mCheckStates.size(); i++) {
|
for (int i = 0; i < mCheckStates.size(); i++) {
|
||||||
if(mCheckStates.get(i)) {
|
if (mCheckStates.get(i)) {
|
||||||
mCursor.moveToPosition(i);
|
mCursor.moveToPosition(i);
|
||||||
result.add(mCursor.getString(mIndexUserId));
|
result.add(mCursor.getString(mIndexUserId));
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import android.support.v4.app.DialogFragment;
|
|||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
import org.sufficientlysecure.keychain.Id;
|
||||||
@ -113,21 +114,8 @@ public class CreateKeyDialogFragment extends DialogFragment {
|
|||||||
public void onClick(DialogInterface di, int id) {
|
public void onClick(DialogInterface di, int id) {
|
||||||
di.dismiss();
|
di.dismiss();
|
||||||
try {
|
try {
|
||||||
int nKeyIndex = keySize.getSelectedItemPosition();
|
final String selectedItem = (String) keySize.getSelectedItem();
|
||||||
switch (nKeyIndex) {
|
mNewKeySize = Integer.parseInt(selectedItem);
|
||||||
case 0:
|
|
||||||
mNewKeySize = 512;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
mNewKeySize = 1024;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
mNewKeySize = 2048;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
mNewKeySize = 4096;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
mNewKeySize = 0;
|
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,
|
false,
|
||||||
null);
|
null);
|
||||||
|
|
||||||
// Message is received after deleting is done in ApgService
|
// Message is received after deleting is done in KeychainIntentService
|
||||||
KeychainIntentServiceHandler saveHandler =
|
KeychainIntentServiceHandler saveHandler =
|
||||||
new KeychainIntentServiceHandler(activity, deletingDialog) {
|
new KeychainIntentServiceHandler(activity, deletingDialog) {
|
||||||
public void handleMessage(Message message) {
|
public void handleMessage(Message message) {
|
||||||
// handle messages by standard ApgHandler first
|
// handle messages by standard KeychainIntentHandler first
|
||||||
super.handleMessage(message);
|
super.handleMessage(message);
|
||||||
|
|
||||||
if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
|
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
|
* 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
|
* 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.AlertDialog;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v4.app.FragmentActivity;
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.Id;
|
|
||||||
import org.sufficientlysecure.keychain.R;
|
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.KeychainDatabase;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public class DeleteKeyDialogFragment extends DialogFragment {
|
public class DeleteKeyDialogFragment extends DialogFragment {
|
||||||
private static final String ARG_MESSENGER = "messenger";
|
private static final String ARG_MESSENGER = "messenger";
|
||||||
private static final String ARG_DELETE_KEY_RING_ROW_IDS = "delete_file";
|
private static final String ARG_DELETE_MASTER_KEY_IDS = "delete_master_key_ids";
|
||||||
private static final String ARG_KEY_TYPE = "key_type";
|
|
||||||
|
|
||||||
public static final int MESSAGE_OKAY = 1;
|
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;
|
private Messenger mMessenger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new instance of this delete file dialog fragment
|
* Creates new instance of this delete file dialog fragment
|
||||||
*/
|
*/
|
||||||
public static DeleteKeyDialogFragment newInstance(Messenger messenger, long[] keyRingRowIds,
|
public static DeleteKeyDialogFragment newInstance(Messenger messenger,
|
||||||
int keyType) {
|
long[] masterKeyIds) {
|
||||||
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
|
DeleteKeyDialogFragment frag = new DeleteKeyDialogFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
args.putParcelable(ARG_MESSENGER, messenger);
|
args.putParcelable(ARG_MESSENGER, messenger);
|
||||||
args.putLongArray(ARG_DELETE_KEY_RING_ROW_IDS, keyRingRowIds);
|
args.putLongArray(ARG_DELETE_MASTER_KEY_IDS, masterKeyIds);
|
||||||
args.putInt(ARG_KEY_TYPE, keyType);
|
//We don't need the key type
|
||||||
|
|
||||||
frag.setArguments(args);
|
frag.setArguments(args);
|
||||||
|
|
||||||
return frag;
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates dialog
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
|
||||||
final FragmentActivity activity = getActivity();
|
final FragmentActivity activity = getActivity();
|
||||||
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
|
||||||
|
|
||||||
final long[] keyRingRowIds = getArguments().getLongArray(ARG_DELETE_KEY_RING_ROW_IDS);
|
final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
|
||||||
final int keyType = getArguments().getInt(ARG_KEY_TYPE);
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
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);
|
builder.setTitle(R.string.warning);
|
||||||
|
|
||||||
if (keyRingRowIds.length == 1) {
|
// If only a single key has been selected
|
||||||
Uri dataUri;
|
if (masterKeyIds.length == 1) {
|
||||||
if (keyType == Id.type.public_key) {
|
mIsSingleSelection = true;
|
||||||
dataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(String.valueOf(keyRingRowIds[0]));
|
|
||||||
} else {
|
|
||||||
dataUri = KeychainContract.KeyRings.buildSecretKeyRingsUri(String.valueOf(keyRingRowIds[0]));
|
|
||||||
}
|
|
||||||
String userId = ProviderHelper.getUserId(activity, dataUri);
|
|
||||||
|
|
||||||
builder.setMessage(getString(
|
long masterKeyId = masterKeyIds[0];
|
||||||
keyType == Id.type.public_key ? R.string.key_deletion_confirmation
|
|
||||||
: R.string.secret_key_deletion_confirmation, userId));
|
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 {
|
} 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.setIcon(R.drawable.ic_dialog_alert_holo_light);
|
||||||
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(R.string.btn_delete, new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
ArrayList<String> notDeleted = new ArrayList<String>();
|
|
||||||
|
|
||||||
if (keyType == Id.type.public_key) {
|
boolean success = false;
|
||||||
Uri queryUri = KeychainContract.KeyRings.buildPublicKeyRingsUri();
|
for(long masterKeyId : masterKeyIds) {
|
||||||
String[] projection = new String[]{
|
int count = activity.getContentResolver().delete(
|
||||||
KeychainContract.KeyRings._ID, // 0
|
KeyRingData.buildPublicKeyRingUri(Long.toString(masterKeyId)), null, null
|
||||||
KeychainContract.KeyRings.MASTER_KEY_ID, // 1
|
);
|
||||||
KeychainContract.UserIds.USER_ID // 2
|
success = count > 0;
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (success) {
|
||||||
dismiss();
|
|
||||||
|
|
||||||
if (notDeleted.size() > 0) {
|
|
||||||
Bundle data = new Bundle();
|
|
||||||
data.putStringArrayList(MESSAGE_NOT_DELETED, notDeleted);
|
|
||||||
sendMessageToHandler(MESSAGE_OKAY, data);
|
|
||||||
} else {
|
|
||||||
sendMessageToHandler(MESSAGE_OKAY, null);
|
sendMessageToHandler(MESSAGE_OKAY, null);
|
||||||
|
} else {
|
||||||
|
sendMessageToHandler(MESSAGE_ERROR, null);
|
||||||
}
|
}
|
||||||
|
dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
@ -184,6 +148,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
|||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +163,6 @@ public class DeleteKeyDialogFragment extends DialogFragment {
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
msg.setData(data);
|
msg.setData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mMessenger.send(msg);
|
mMessenger.send(msg);
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
|
@ -24,10 +24,12 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.Messenger;
|
import android.os.Messenger;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
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_OKAY = 1;
|
||||||
public static final int MESSAGE_CANCEL = 2;
|
public static final int MESSAGE_CANCEL = 2;
|
||||||
|
|
||||||
|
public static final String MESSAGE_DATA_PASSPHRASE = "passphrase";
|
||||||
|
|
||||||
private Messenger mMessenger;
|
private Messenger mMessenger;
|
||||||
private EditText mPassphraseEditText;
|
private EditText mPassphraseEditText;
|
||||||
private boolean mCanKB;
|
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
|
* Creates new instance of this dialog fragment
|
||||||
*
|
*
|
||||||
@ -114,10 +139,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
|||||||
secretKey = null;
|
secretKey = null;
|
||||||
alert.setMessage(R.string.passphrase_for_symmetric_encryption);
|
alert.setMessage(R.string.passphrase_for_symmetric_encryption);
|
||||||
} else {
|
} else {
|
||||||
// TODO: by master key id???
|
secretKey = ProviderHelper.getPGPSecretKeyRing(activity, secretKeyId).getSecretKey();
|
||||||
secretKey = PgpKeyHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity,
|
|
||||||
secretKeyId));
|
|
||||||
// secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
|
|
||||||
|
|
||||||
if (secretKey == null) {
|
if (secretKey == null) {
|
||||||
alert.setTitle(R.string.title_key_not_found);
|
alert.setTitle(R.string.title_key_not_found);
|
||||||
@ -173,7 +195,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
clickSecretKey = PgpKeyHelper.getKeyNum(ProviderHelper
|
clickSecretKey = PgpKeyHelper.getKeyNum(ProviderHelper
|
||||||
.getPGPSecretKeyRingByKeyId(activity, secretKeyId),
|
.getPGPSecretKeyRingWithKeyId(activity, secretKeyId),
|
||||||
curKeyIndex);
|
curKeyIndex);
|
||||||
curKeyIndex++; // does post-increment work like C?
|
curKeyIndex++; // does post-increment work like C?
|
||||||
continue;
|
continue;
|
||||||
@ -209,7 +231,11 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
|
|||||||
passphrase);
|
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.Constants;
|
||||||
import org.sufficientlysecure.keychain.R;
|
import org.sufficientlysecure.keychain.R;
|
||||||
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
|
||||||
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
import org.sufficientlysecure.keychain.provider.ProviderHelper;
|
||||||
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
import org.sufficientlysecure.keychain.util.QrCodeUtils;
|
||||||
|
|
||||||
@ -89,29 +90,33 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
|
|||||||
if (mFingerprintOnly) {
|
if (mFingerprintOnly) {
|
||||||
alert.setPositiveButton(R.string.btn_okay, null);
|
alert.setPositiveButton(R.string.btn_okay, null);
|
||||||
|
|
||||||
byte[] fingerprintBlob = ProviderHelper.getFingerprint(getActivity(), dataUri);
|
byte[] blob = (byte[]) ProviderHelper.getGenericData(
|
||||||
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob, false);
|
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);
|
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
|
||||||
|
|
||||||
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
|
||||||
setQrCode(content);
|
setQrCode(content);
|
||||||
} else {
|
} else {
|
||||||
mText.setText(R.string.share_qr_code_dialog_start);
|
mText.setText(R.string.share_qr_code_dialog_start);
|
||||||
|
|
||||||
// TODO
|
// TODO works, but
|
||||||
long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), dataUri);
|
||||||
|
|
||||||
// get public keyring as ascii armored string
|
// get public keyring as ascii armored string
|
||||||
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
ArrayList<String> keyringArmored = ProviderHelper.getKeyRingsAsArmoredString(
|
||||||
getActivity(), dataUri, new long[]{masterKeyId});
|
getActivity(), new long[] { masterKeyId });
|
||||||
|
|
||||||
// TODO: binary?
|
// TODO: binary?
|
||||||
|
|
||||||
content = keyringArmored.get(0);
|
content = keyringArmored.get(0);
|
||||||
|
|
||||||
// OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
|
// 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.setPositiveButton(R.string.btn_next, null);
|
||||||
alert.setNegativeButton(android.R.string.cancel, 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