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:
Vincent Breitmoser 2014-04-05 19:30:52 +02:00
commit aa6f5118f5
288 changed files with 26993 additions and 7232 deletions

View File

@ -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

View File

@ -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"

View File

@ -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);

View File

@ -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 &lt;alice@example.com&gt;"
android:id="@+id/crypto_provider_demo_account" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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)

View File

@ -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());

View File

@ -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

View File

@ -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 @@
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;--> <!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<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" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;--> <!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<data android:mimeType="application/pgp-signature" />--> <!--<data android:mimeType="application/pgp-signature" />-->
<!--</intent-filter>--> <!--</intent-filter>-->
<!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;--> <!--&lt;!&ndash; VIEW with mimeType: TODO (from email app) &ndash;&gt;-->
<!--<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" />-->
<!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;--> <!--&lt;!&ndash; mime type as defined in http://tools.ietf.org/html/rfc3156 &ndash;&gt;-->
<!--<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 -->

View File

@ -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
};
}
} }

View File

@ -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 {

View File

@ -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
*/ */

View File

@ -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) {

View File

@ -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());

View File

@ -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) {

View File

@ -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;
}
} }

View File

@ -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();
} }

View File

@ -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;

View File

@ -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()

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;
} }
/** /**

View File

@ -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;
} }
} }
} }

View File

@ -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());
} }

View File

@ -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();

View File

@ -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);
}
} }

View File

@ -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);
}
}

View File

@ -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();
} }
} }

View File

@ -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");
} }
} }

View File

@ -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";

View File

@ -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;

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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)) {

View File

@ -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;
} }

View File

@ -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 {

View File

@ -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();
} }

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -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) {

View File

@ -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);
}
}
} }

View File

@ -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);

View File

@ -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();

View File

@ -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(); ) {

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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;
}
}
}
} }

View File

@ -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;
}
}
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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));
} }

View File

@ -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();
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
} }
} }
}; };

View File

@ -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);
} }

View File

@ -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;

View File

@ -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);
} }
}); });

View File

@ -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;

View File

@ -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);
}
} }
} }

View File

@ -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);
} }

View File

@ -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;

View File

@ -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;
} }
}); });

View File

@ -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);

View File

@ -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 += ", ";

View File

@ -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;
}
} }

View File

@ -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

View File

@ -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;
} }

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}
}
}
} }

View File

@ -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;
}
} }
/** /**

View File

@ -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);
} }
} }

View File

@ -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);

View File

@ -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);

View File

@ -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));
} }
} }

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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;
} }
} }

View File

@ -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));
} }

View File

@ -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;
} }
} }

View File

@ -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) {

View File

@ -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) {

View File

@ -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);
}
}
} }

View File

@ -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