aboutsummaryrefslogtreecommitdiffstats
path: root/OpenPGP-Keychain
diff options
context:
space:
mode:
authorDominik Schürmann <dominik@dominikschuermann.de>2013-05-28 15:10:36 +0200
committerDominik Schürmann <dominik@dominikschuermann.de>2013-05-28 15:10:36 +0200
commitb221c0c9051d7dea697aff7b3167fca69eb1f4f8 (patch)
treee973bdd30cd4c2b7e7366e693077210df795032b /OpenPGP-Keychain
parent8c537d33678bfa1583598923608494a49e8781ea (diff)
downloadopen-keychain-b221c0c9051d7dea697aff7b3167fca69eb1f4f8.tar.gz
open-keychain-b221c0c9051d7dea697aff7b3167fca69eb1f4f8.tar.bz2
open-keychain-b221c0c9051d7dea697aff7b3167fca69eb1f4f8.zip
Crypto Provider implementation start
Diffstat (limited to 'OpenPGP-Keychain')
-rw-r--r--OpenPGP-Keychain/AndroidManifest.xml40
-rw-r--r--OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml25
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl20
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/CryptoError.java76
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java73
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl20
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java76
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl32
-rw-r--r--OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl78
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java92
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java391
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java74
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java17
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java33
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java46
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java39
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java44
-rw-r--r--OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java72
18 files changed, 1162 insertions, 86 deletions
diff --git a/OpenPGP-Keychain/AndroidManifest.xml b/OpenPGP-Keychain/AndroidManifest.xml
index 7b02d6fae..d7538b3c9 100644
--- a/OpenPGP-Keychain/AndroidManifest.xml
+++ b/OpenPGP-Keychain/AndroidManifest.xml
@@ -86,9 +86,7 @@
android:permissionGroup="org.sufficientlysecure.keychain.permission-group.keychain"
android:protectionLevel="dangerous" />
- <!--
- 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
android:name=".KeychainApplication"
android:allowBackup="false"
@@ -454,6 +452,42 @@
android:name="org.sufficientlysecure.keychain.provider.KeychainServiceBlobProvider"
android:authorities="org.sufficientlysecure.keychain.provider.apgserviceblobprovider"
android:permission="org.sufficientlysecure.keychain.permission.ACCESS_API" />
+
+ <!-- Crypto Provider other intents -->
+ <activity
+ android:name=".crypto_provider.CryptoActivity"
+ android:label="TODO crypto activity" >
+ <intent-filter>
+ <action android:name="org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <!-- Crypto Provider API -->
+ <activity
+ android:name=".crypto_provider.RegisterActivity"
+ android:label="TODO reg" >
+ <intent-filter>
+ <action android:name="com.android.crypto.REGISTER" />
+
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <service
+ android:name="org.sufficientlysecure.keychain.crypto_provider.CryptoService"
+ android:enabled="true"
+ android:exported="true"
+ android:process=":crypto" >
+ <intent-filter>
+ <action android:name="com.android.crypto.ICryptoService" />
+ </intent-filter>
+
+ <meta-data
+ android:name="api_version"
+ android:value="1" />
+ </service>
</application>
</manifest> \ No newline at end of file
diff --git a/OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml b/OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml
new file mode 100644
index 000000000..24a64f1ac
--- /dev/null
+++ b/OpenPGP-Keychain/res/layout/register_crypto_consumer_activity.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/textView1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Register?" />
+
+ <Button
+ android:id="@+id/register_crypto_consumer_allow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Allow access" />
+
+ <Button
+ android:id="@+id/register_crypto_consumer_disallow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disallow" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl b/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl
new file mode 100644
index 000000000..d1b941212
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/CryptoError.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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 com.android.crypto;
+
+// Declare CryptoError so AIDL can find it and knows that it implements the parcelable protocol.
+parcelable CryptoError; \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoError.java b/OpenPGP-Keychain/src/com/android/crypto/CryptoError.java
new file mode 100644
index 000000000..9540f4f68
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/CryptoError.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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 com.android.crypto;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class CryptoError implements Parcelable {
+ int errorId;
+ String message;
+
+ public CryptoError() {
+ }
+
+ public CryptoError(int errorId, String message) {
+ this.errorId = errorId;
+ this.message = message;
+ }
+
+ public CryptoError(CryptoError b) {
+ this.errorId = b.errorId;
+ this.message = b.message;
+ }
+
+ public int getErrorId() {
+ return errorId;
+ }
+
+ public void setErrorId(int errorId) {
+ this.errorId = errorId;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(errorId);
+ dest.writeString(message);
+ }
+
+ public static final Creator<CryptoError> CREATOR = new Creator<CryptoError>() {
+ public CryptoError createFromParcel(final Parcel source) {
+ CryptoError error = new CryptoError();
+ error.errorId = source.readInt();
+ error.message = source.readString();
+ return error;
+ }
+
+ public CryptoError[] newArray(final int size) {
+ return new CryptoError[size];
+ }
+ };
+}
diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java b/OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java
new file mode 100644
index 000000000..4d659d344
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/CryptoServiceConnection.java
@@ -0,0 +1,73 @@
+package com.android.crypto;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+
+public class CryptoServiceConnection {
+ private Context mApplicationContext;
+
+ private ICryptoService mService;
+ private boolean bound;
+ private String cryptoProviderPackageName;
+
+ private static final String TAG = "CryptoConnection";
+
+ public CryptoServiceConnection(Context context, String cryptoProviderPackageName) {
+ mApplicationContext = context.getApplicationContext();
+ this.cryptoProviderPackageName = cryptoProviderPackageName;
+ }
+
+ public ICryptoService getService() {
+ return mService;
+ }
+
+ private ServiceConnection mCryptoServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = ICryptoService.Stub.asInterface(service);
+ Log.d(TAG, "connected to service");
+ bound = true;
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ Log.d(TAG, "disconnected from service");
+ bound = false;
+ }
+ };
+
+ /**
+ * If not already bound, bind!
+ *
+ * @return
+ */
+ public boolean bindToService() {
+ if (mService == null && !bound) { // if not already connected
+ try {
+ Log.d(TAG, "not bound yet");
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setAction("com.android.crypto.ICryptoService");
+ serviceIntent.setPackage(cryptoProviderPackageName); // TODO: test
+ mApplicationContext.bindService(serviceIntent, mCryptoServiceConnection,
+ Context.BIND_AUTO_CREATE);
+
+ return true;
+ } catch (Exception e) {
+ Log.d(TAG, "Exception", e);
+ return false;
+ }
+ } else { // already connected
+ Log.d(TAG, "already bound... ");
+ return true;
+ }
+ }
+
+ public void unbindFromService() {
+ mApplicationContext.unbindService(mCryptoServiceConnection);
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl b/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl
new file mode 100644
index 000000000..21862c497
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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 com.android.crypto;
+
+// Declare CryptoSignatureResult so AIDL can find it and knows that it implements the parcelable protocol.
+parcelable CryptoSignatureResult; \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java b/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java
new file mode 100644
index 000000000..87f5f43b5
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/CryptoSignatureResult.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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 com.android.crypto;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class CryptoSignatureResult implements Parcelable {
+ String signatureUserId;
+
+ boolean signature;
+ boolean signatureSuccess;
+ boolean signatureUnknown;
+
+ public CryptoSignatureResult() {
+
+ }
+
+ public CryptoSignatureResult(String signatureUserId, boolean signature,
+ boolean signatureSuccess, boolean signatureUnknown) {
+ this.signatureUserId = signatureUserId;
+
+ this.signature = signature;
+ this.signatureSuccess = signatureSuccess;
+ this.signatureUnknown = signatureUnknown;
+ }
+
+ public CryptoSignatureResult(CryptoSignatureResult b) {
+ this.signatureUserId = b.signatureUserId;
+
+ this.signature = b.signature;
+ this.signatureSuccess = b.signatureSuccess;
+ this.signatureUnknown = b.signatureUnknown;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(signatureUserId);
+
+ dest.writeByte((byte) (signature ? 1 : 0));
+ dest.writeByte((byte) (signatureSuccess ? 1 : 0));
+ dest.writeByte((byte) (signatureUnknown ? 1 : 0));
+ }
+
+ public static final Creator<CryptoSignatureResult> CREATOR = new Creator<CryptoSignatureResult>() {
+ public CryptoSignatureResult createFromParcel(final Parcel source) {
+ CryptoSignatureResult vr = new CryptoSignatureResult();
+ vr.signatureUserId = source.readString();
+ vr.signature = source.readByte() == 1;
+ vr.signatureSuccess = source.readByte() == 1;
+ vr.signatureUnknown = source.readByte() == 1;
+ return vr;
+ }
+
+ public CryptoSignatureResult[] newArray(final int size) {
+ return new CryptoSignatureResult[size];
+ }
+ };
+}
diff --git a/OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl b/OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl
new file mode 100644
index 000000000..af6587c04
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/ICryptoCallback.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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 com.android.crypto;
+
+import com.android.crypto.CryptoSignatureResult;
+import com.android.crypto.CryptoError;
+
+interface ICryptoCallback {
+
+ oneway void onEncryptSignSuccess(in byte[] outputBytes);
+
+ oneway void onDecryptVerifySuccess(in byte[] outputBytes, in CryptoSignatureResult signatureResult);
+
+
+ oneway void onError(in CryptoError error);
+
+ oneway void onActivityRequired(in Intent intent);
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl b/OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl
new file mode 100644
index 000000000..05baa16e0
--- /dev/null
+++ b/OpenPGP-Keychain/src/com/android/crypto/ICryptoService.aidl
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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 com.android.crypto;
+
+import com.android.crypto.ICryptoCallback;
+
+/**
+ * All methods are oneway, which means they are asynchronous and non-blocking.
+ * Results are returned to the callback, which has to be implemented on client side.
+ */
+interface ICryptoService {
+
+ /**
+ * Encrypt
+ *
+ * @param inputBytes
+ * Byte array you want to encrypt
+ * @param encryptionKeyIds
+ * Ids of public keys used for encryption
+ * @param handler
+ * Results are returned to this Handler after successful encryption
+ */
+ oneway void encrypt(in byte[] inputBytes, in String[] encryptionUserIds, in ICryptoCallback callback);
+
+ /**
+ * Encrypt and sign
+ *
+ *
+ *
+ * @param inputBytes
+ * Byte array you want to encrypt
+ * @param signatureKeyId
+ * Key id of key to sign with
+ * @param handler
+ * Results are returned to this Handler after successful encryption and signing
+ */
+ oneway void encryptAndSign(in byte[] inputBytes, in String[] encryptionUserIds, String signatureUserId, in ICryptoCallback callback);
+
+ /**
+ * Sign
+ *
+ *
+ *
+ * @param inputBytes
+ * Byte array you want to encrypt
+ * @param signatureId
+ *
+ * @param handler
+ * Results are returned to this Handler after successful encryption and signing
+ */
+ oneway void sign(in byte[] inputBytes, String signatureUserId, in ICryptoCallback callback);
+
+ /**
+ * Decrypts and verifies given input bytes. If no signature is present this method
+ * will only decrypt.
+ *
+ * @param inputBytes
+ * Byte array you want to decrypt and verify
+ * @param handler
+ * Handler where to return results to after successful encryption
+ */
+ oneway void decryptAndVerify(in byte[] inputBytes, in ICryptoCallback callback);
+
+} \ No newline at end of file
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java
new file mode 100644
index 000000000..b1d248e42
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoActivity.java
@@ -0,0 +1,92 @@
+package org.sufficientlysecure.keychain.crypto_provider;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.helper.PgpMain;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
+import org.sufficientlysecure.keychain.util.Log;
+
+import com.actionbarsherlock.app.SherlockFragmentActivity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class CryptoActivity extends SherlockFragmentActivity {
+
+ public static final String ACTION_CACHE_PASSPHRASE = "org.sufficientlysecure.keychain.CRYPTO_CACHE_PASSPHRASE";
+
+ public static final String EXTRA_SECRET_KEY_ID = "secret_key_id";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ handleActions(getIntent());
+ }
+
+ protected void handleActions(Intent intent) {
+
+ // TODO: Important: Check if calling package is in list!
+
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ /**
+ * com.android.crypto actions
+ */
+ if (ACTION_CACHE_PASSPHRASE.equals(action)) {
+ long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID);
+
+ showPassphraseDialog(secretKeyId);
+ } else {
+ Log.e(Constants.TAG, "Wrong action!");
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ }
+
+ /**
+ * 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(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) {
+ setResult(RESULT_OK);
+ finish();
+ }
+ }
+ };
+
+ // 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 (PgpMain.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);
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java
new file mode 100644
index 000000000..584aa81ec
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.crypto_provider;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+
+import org.spongycastle.openpgp.PGPException;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Id;
+import org.sufficientlysecure.keychain.helper.PgpMain;
+import org.sufficientlysecure.keychain.helper.PgpMain.PgpGeneralException;
+import org.sufficientlysecure.keychain.util.InputData;
+import org.sufficientlysecure.keychain.util.Log;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.service.IKeychainApiService;
+import org.sufficientlysecure.keychain.service.KeychainIntentService;
+import org.sufficientlysecure.keychain.service.PassphraseCacheService;
+import org.sufficientlysecure.keychain.service.handler.IKeychainDecryptHandler;
+import org.sufficientlysecure.keychain.service.handler.IKeychainEncryptHandler;
+import org.sufficientlysecure.keychain.service.handler.IKeychainGetDecryptionKeyIdHandler;
+
+import com.android.crypto.CryptoError;
+import com.android.crypto.ICryptoCallback;
+import com.android.crypto.ICryptoService;
+import com.android.crypto.CryptoSignatureResult;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+public class CryptoService extends Service {
+ Context mContext;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mContext = this;
+ Log.d(Constants.TAG, "KeychainApiService, onCreate()");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(Constants.TAG, "KeychainApiService, onDestroy()");
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ private synchronized void encryptAndSignSafe(byte[] inputBytes, String inputUri,
+ boolean useAsciiArmor, int compression, long[] encryptionKeyIds,
+ String encryptionPassphrase, int symmetricEncryptionAlgorithm, long signatureKeyId,
+ int signatureHashAlgorithm, boolean signatureForceV3, String signaturePassphrase,
+ IKeychainEncryptHandler handler) throws RemoteException {
+
+ try {
+
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData input = new InputData(inputStream, inputLength);
+
+ OutputStream output = new ByteArrayOutputStream();
+
+ PgpMain.encryptAndSign(mContext, null, input, output, useAsciiArmor, compression,
+ encryptionKeyIds, encryptionPassphrase, symmetricEncryptionAlgorithm,
+ signatureKeyId, signatureHashAlgorithm, signatureForceV3, signaturePassphrase);
+
+ output.close();
+
+ // start activity from service, TOOD: Test!
+ // Intent dialogIntent = new Intent(getBaseContext(), myActivity.class);
+ // dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ // getApplication().startActivity(dialogIntent);
+
+ byte[] outputBytes = ((ByteArrayOutputStream) output).toByteArray();
+
+ // return over handler on client side
+ handler.onSuccess(outputBytes, null);
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "KeychainService, Exception!", e);
+
+ try {
+ handler.onException(getExceptionId(e), e.getMessage());
+ } catch (Exception t) {
+ Log.e(Constants.TAG, "Error returning exception to client", t);
+ }
+ }
+ }
+
+ private synchronized void decryptAndVerifySafe(byte[] inputBytes, String passphrase,
+ boolean assumeSymmetric, IKeychainDecryptHandler handler) throws RemoteException {
+
+ try {
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputData = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData, outputStream,
+ passphrase, assumeSymmetric);
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+
+ // get signature informations from bundle
+ boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
+ long signatureKeyId = outputBundle
+ .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
+ String signatureUserId = outputBundle
+ .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
+ boolean signatureSuccess = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
+ boolean signatureUnknown = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
+
+ // return over handler on client side
+ handler.onSuccess(outputBytes, null, signature, signatureKeyId, signatureUserId,
+ signatureSuccess, signatureUnknown);
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "KeychainService, Exception!", e);
+
+ try {
+ handler.onException(getExceptionId(e), e.getMessage());
+ } catch (Exception t) {
+ Log.e(Constants.TAG, "Error returning exception to client", t);
+ }
+ }
+ }
+
+ private synchronized void getDecryptionKeySafe(byte[] inputBytes, String inputUri,
+ IKeychainGetDecryptionKeyIdHandler handler) {
+
+ // TODO: implement inputUri
+
+ try {
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+
+ long secretKeyId = Id.key.none;
+ boolean symmetric;
+
+ try {
+ secretKeyId = PgpMain.getDecryptionKeyId(CryptoService.this, inputStream);
+ if (secretKeyId == Id.key.none) {
+ throw new PgpGeneralException(getString(R.string.error_noSecretKeyFound));
+ }
+ symmetric = false;
+ } catch (PgpMain.NoAsymmetricEncryptionException e) {
+ secretKeyId = Id.key.symmetric;
+ if (!PgpMain.hasSymmetricEncryption(CryptoService.this, inputStream)) {
+ throw new PgpGeneralException(getString(R.string.error_noKnownEncryptionFound));
+ }
+ symmetric = true;
+ }
+
+ handler.onSuccess(secretKeyId, symmetric);
+
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "KeychainService, Exception!", e);
+
+ try {
+ handler.onException(getExceptionId(e), e.getMessage());
+ } catch (Exception t) {
+ Log.e(Constants.TAG, "Error returning exception to client", t);
+ }
+ }
+ }
+
+ private final ICryptoService.Stub mBinder = new ICryptoService.Stub() {
+
+ @Override
+ public void encrypt(byte[] inputBytes, String[] encryptionUserIds, ICryptoCallback callback)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void encryptAndSign(byte[] inputBytes, String[] encryptionUserIds,
+ String signatureUserId, ICryptoCallback callback) throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void sign(byte[] inputBytes, String signatureUserId, ICryptoCallback callback)
+ throws RemoteException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void decryptAndVerify(byte[] inputBytes, ICryptoCallback callback)
+ throws RemoteException {
+
+ try {
+ // build InputData and write into OutputStream
+ InputStream inputStream = new ByteArrayInputStream(inputBytes);
+ long inputLength = inputBytes.length;
+ InputData inputData = new InputData(inputStream, inputLength);
+
+ OutputStream outputStream = new ByteArrayOutputStream();
+
+ // String passphrase = "";
+
+ long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream);
+ if (secretKeyId == Id.key.none) {
+ throw new PgpMain.PgpGeneralException(
+ getString(R.string.error_noSecretKeyFound));
+ }
+
+ String passphrase = PassphraseCacheService.getCachedPassphrase(mContext,
+ secretKeyId);
+
+ if (passphrase == null) {
+ // No passphrase cached for this ciphertext! Intent required to cache
+ // passphrase!
+ Intent intent = new Intent(CryptoActivity.ACTION_CACHE_PASSPHRASE);
+ intent.putExtra(CryptoActivity.EXTRA_SECRET_KEY_ID, secretKeyId);
+ callback.onActivityRequired(intent);
+ return;
+ }
+
+ // if (signedOnly) {
+ // resultData = PgpMain.verifyText(this, this, inputData, outStream,
+ // lookupUnknownKey);
+ // } else {
+ // resultData = PgpMain.decryptAndVerify(this, this, inputData, outStream,
+ // PassphraseCacheService.getCachedPassphrase(this, secretKeyId),
+ // assumeSymmetricEncryption);
+ // }
+
+ Bundle outputBundle = PgpMain.decryptAndVerify(mContext, null, inputData,
+ outputStream, passphrase, false);
+
+ outputStream.close();
+
+ byte[] outputBytes = ((ByteArrayOutputStream) outputStream).toByteArray();
+
+ // get signature informations from bundle
+ boolean signature = outputBundle.getBoolean(KeychainIntentService.RESULT_SIGNATURE);
+ long signatureKeyId = outputBundle
+ .getLong(KeychainIntentService.RESULT_SIGNATURE_KEY_ID);
+ String signatureUserId = outputBundle
+ .getString(KeychainIntentService.RESULT_SIGNATURE_USER_ID);
+ boolean signatureSuccess = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_SUCCESS);
+ boolean signatureUnknown = outputBundle
+ .getBoolean(KeychainIntentService.RESULT_SIGNATURE_UNKNOWN);
+
+ CryptoSignatureResult sigResult = new CryptoSignatureResult(signatureUserId,
+ signature, signatureSuccess, signatureUnknown);
+
+ // return over handler on client side
+ callback.onDecryptVerifySuccess(outputBytes, sigResult);
+ // handler.onSuccess(outputBytes, null, signature, signatureKeyId, signatureUserId,
+ // signatureSuccess, signatureUnknown);
+ } catch (Exception e) {
+ Log.e(Constants.TAG, "KeychainService, Exception!", e);
+
+ try {
+ callback.onError(new CryptoError(getExceptionId(e), e.getMessage()));
+ } catch (Exception t) {
+ Log.e(Constants.TAG, "Error returning exception to client", t);
+ }
+ }
+
+ }
+ //
+ // @Override
+ // public void encryptAsymmetric(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
+ // int compression, long[] encryptionKeyIds, int symmetricEncryptionAlgorithm,
+ // IKeychainEncryptHandler handler) throws RemoteException {
+ //
+ // encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, encryptionKeyIds,
+ // null, symmetricEncryptionAlgorithm, Id.key.none, 0, false, null, handler);
+ // }
+ //
+ // @Override
+ // public void encryptSymmetric(byte[] inputBytes, String inputUri, boolean useAsciiArmor,
+ // int compression, String encryptionPassphrase, int symmetricEncryptionAlgorithm,
+ // IKeychainEncryptHandler handler) throws RemoteException {
+ //
+ // encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, null,
+ // encryptionPassphrase, symmetricEncryptionAlgorithm, Id.key.none, 0, false,
+ // null, handler);
+ // }
+ //
+ // @Override
+ // public void encryptAndSignAsymmetric(byte[] inputBytes, String inputUri,
+ // boolean useAsciiArmor, int compression, long[] encryptionKeyIds,
+ // int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
+ // boolean signatureForceV3, String signaturePassphrase,
+ // IKeychainEncryptHandler handler) throws RemoteException {
+ //
+ // encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, encryptionKeyIds,
+ // null, symmetricEncryptionAlgorithm, signatureKeyId, signatureHashAlgorithm,
+ // signatureForceV3, signaturePassphrase, handler);
+ // }
+ //
+ // @Override
+ // public void encryptAndSignSymmetric(byte[] inputBytes, String inputUri,
+ // boolean useAsciiArmor, int compression, String encryptionPassphrase,
+ // int symmetricEncryptionAlgorithm, long signatureKeyId, int signatureHashAlgorithm,
+ // boolean signatureForceV3, String signaturePassphrase,
+ // IKeychainEncryptHandler handler) throws RemoteException {
+ //
+ // encryptAndSignSafe(inputBytes, inputUri, useAsciiArmor, compression, null,
+ // encryptionPassphrase, symmetricEncryptionAlgorithm, signatureKeyId,
+ // signatureHashAlgorithm, signatureForceV3, signaturePassphrase, handler);
+ // }
+ //
+ // @Override
+ // public void decryptAndVerifyAsymmetric(byte[] inputBytes, String inputUri,
+ // String keyPassphrase, IKeychainDecryptHandler handler) throws RemoteException {
+ //
+ // decryptAndVerifySafe(inputBytes, inputUri, keyPassphrase, false, handler);
+ // }
+ //
+ // @Override
+ // public void decryptAndVerifySymmetric(byte[] inputBytes, String inputUri,
+ // String encryptionPassphrase, IKeychainDecryptHandler handler)
+ // throws RemoteException {
+ //
+ // decryptAndVerifySafe(inputBytes, inputUri, encryptionPassphrase, true, handler);
+ // }
+ //
+ // @Override
+ // public void getDecryptionKeyId(byte[] inputBytes, String inputUri,
+ // IKeychainGetDecryptionKeyIdHandler handler) throws RemoteException {
+ //
+ // getDecryptionKeySafe(inputBytes, inputUri, handler);
+ // }
+
+ };
+
+ /**
+ * As we can not throw an exception through Android RPC, we assign identifiers to the exception
+ * types.
+ *
+ * @param e
+ * @return
+ */
+ private int getExceptionId(Exception e) {
+ if (e instanceof NoSuchProviderException) {
+ return 0;
+ } else if (e instanceof NoSuchAlgorithmException) {
+ return 1;
+ } else if (e instanceof SignatureException) {
+ return 2;
+ } else if (e instanceof IOException) {
+ return 3;
+ } else if (e instanceof PgpGeneralException) {
+ return 4;
+ } else if (e instanceof PGPException) {
+ return 5;
+ } else {
+ return -1;
+ }
+ }
+
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java
new file mode 100644
index 000000000..39b29f9a0
--- /dev/null
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/RegisterActivity.java
@@ -0,0 +1,74 @@
+package org.sufficientlysecure.keychain.crypto_provider;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
+import org.sufficientlysecure.keychain.util.Log;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+public class RegisterActivity extends Activity {
+
+ public static final String ACTION_REGISTER = "com.android.crypto.REGISTER";
+
+ public static final String EXTRA_PACKAGE_NAME = "packageName";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ handleActions(getIntent());
+ }
+
+ protected void handleActions(Intent intent) {
+ String action = intent.getAction();
+ Bundle extras = intent.getExtras();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+
+ final String callingPackageName = this.getCallingPackage();
+
+ /**
+ * com.android.crypto actions
+ */
+ if (ACTION_REGISTER.equals(action)) {
+ setContentView(R.layout.register_crypto_consumer_activity);
+
+ Button allowButton = (Button) findViewById(R.id.register_crypto_consumer_allow);
+ Button disallowButton = (Button) findViewById(R.id.register_crypto_consumer_disallow);
+
+ allowButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ ProviderHelper.addCryptoConsumer(RegisterActivity.this, callingPackageName);
+ Intent data = new Intent();
+ data.putExtra(EXTRA_PACKAGE_NAME, "org.sufficientlysecure.keychain");
+
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ });
+
+ disallowButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+
+ } else {
+ Log.e(Constants.TAG, "Please use com.android.crypto.REGISTER as intent action!");
+ finish();
+ }
+ }
+}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java
index b284eccaa..46928c6fa 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainContract.java
@@ -53,6 +53,10 @@ public class KeychainContract {
String RANK = "rank";
}
+ interface CryptoConsumersColumns {
+ String PACKAGE_NAME = "package_name";
+ }
+
public static final class KeyTypes {
public static final int PUBLIC = 0;
public static final int SECRET = 1;
@@ -78,6 +82,8 @@ public class KeychainContract {
public static final String PATH_USER_IDS = "user_ids";
public static final String PATH_KEYS = "keys";
+ public static final String BASE_CRYPTO_CONSUMERS = "crypto_consumers";
+
public static class KeyRings implements KeyRingsColumns, BaseColumns {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_KEY_RINGS).build();
@@ -207,6 +213,17 @@ public class KeychainContract {
}
}
+ public static class CryptoConsumers implements CryptoConsumersColumns, BaseColumns {
+ public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
+ .appendPath(BASE_CRYPTO_CONSUMERS).build();
+
+ /** Use if multiple items get returned */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.thialfihar.apg.crypto_consumers";
+
+ /** Use if a single item is returned */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.thialfihar.apg.crypto_consumers";
+ }
+
public static class DataStream {
public static final Uri CONTENT_URI = BASE_CONTENT_URI_INTERNAL.buildUpon()
.appendPath(BASE_DATA).build();
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
index ef95ce7f7..f30292b52 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainDatabase.java
@@ -18,6 +18,7 @@
package org.sufficientlysecure.keychain.provider;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumersColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeysColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIdsColumns;
@@ -28,15 +29,15 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
-
public class KeychainDatabase extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "apg.db";
- private static final int DATABASE_VERSION = 4;
+ private static final int DATABASE_VERSION = 5;
public interface Tables {
String KEY_RINGS = "key_rings";
String KEYS = "keys";
String USER_IDS = "user_ids";
+ String CRYPTO_CONSUMERS = "crypto_consumers";
}
private static final String CREATE_KEY_RINGS = "CREATE TABLE IF NOT EXISTS " + Tables.KEY_RINGS
@@ -48,13 +49,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + KeysColumns.KEY_ID
+ " INT64, " + KeysColumns.TYPE + " INTEGER, " + KeysColumns.IS_MASTER_KEY
+ " INTEGER, " + KeysColumns.ALGORITHM + " INTEGER, " + KeysColumns.KEY_SIZE
- + " INTEGER, " + KeysColumns.CAN_CERTIFY
- + " INTEGER, " + KeysColumns.CAN_SIGN + " INTEGER, " + KeysColumns.CAN_ENCRYPT
- + " INTEGER, " + KeysColumns.IS_REVOKED + " INTEGER, " + KeysColumns.CREATION
- + " INTEGER, " + KeysColumns.EXPIRY + " INTEGER, " + KeysColumns.KEY_DATA + " BLOB,"
- + KeysColumns.RANK + " INTEGER, " + KeysColumns.KEY_RING_ROW_ID
- + " INTEGER NOT NULL, FOREIGN KEY(" + KeysColumns.KEY_RING_ROW_ID + ") REFERENCES "
- + Tables.KEY_RINGS + "(" + BaseColumns._ID + ") ON DELETE CASCADE)";
+ + " 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.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
+ " (" + BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
@@ -63,6 +64,11 @@ public class KeychainDatabase extends SQLiteOpenHelper {
+ UserIdsColumns.KEY_RING_ROW_ID + ") REFERENCES " + Tables.KEY_RINGS + "("
+ BaseColumns._ID + ") ON DELETE CASCADE)";
+ private static final String CREATE_CRYPTO_CONSUMERS = "CREATE TABLE IF NOT EXISTS "
+ + Tables.CRYPTO_CONSUMERS + " (" + BaseColumns._ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + CryptoConsumersColumns.PACKAGE_NAME
+ + " TEXT UNIQUE)";
+
KeychainDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@@ -74,6 +80,7 @@ public class KeychainDatabase extends SQLiteOpenHelper {
db.execSQL(CREATE_KEY_RINGS);
db.execSQL(CREATE_KEYS);
db.execSQL(CREATE_USER_IDS);
+ db.execSQL(CREATE_CRYPTO_CONSUMERS);
}
@Override
@@ -95,9 +102,13 @@ public class KeychainDatabase extends SQLiteOpenHelper {
switch (version) {
case 3:
- db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY + " INTEGER DEFAULT 0;");
- db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
+ db.execSQL("ALTER TABLE " + Tables.KEYS + " ADD COLUMN " + KeysColumns.CAN_CERTIFY
+ + " INTEGER DEFAULT 0;");
+ db.execSQL("UPDATE " + Tables.KEYS + " SET " + KeysColumns.CAN_CERTIFY
+ + " = 1 WHERE " + KeysColumns.IS_MASTER_KEY + "= 1;");
break;
+ case 4:
+ db.execSQL(CREATE_CRYPTO_CONSUMERS);
default:
break;
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java
index c63d9e772..49286b9ce 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/KeychainProvider.java
@@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.HashMap;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingsColumns;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyTypes;
@@ -80,7 +81,9 @@ public class KeychainProvider extends ContentProvider {
private static final int SECRET_KEY_RING_USER_ID = 221;
private static final int SECRET_KEY_RING_USER_ID_BY_ROW_ID = 222;
- private static final int DATA_STREAM = 301;
+ private static final int CRYPTO_CONSUMERS = 301;
+
+ // private static final int DATA_STREAM = 401;
protected boolean mInternalProvider;
protected UriMatcher mUriMatcher;
@@ -126,8 +129,7 @@ public class KeychainProvider extends ContentProvider {
PUBLIC_KEY_RING_BY_EMAILS);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_EMAILS,
- PUBLIC_KEY_RING_BY_EMAILS); // without emails
- // specified
+ PUBLIC_KEY_RING_BY_EMAILS); // without emails specified
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_PUBLIC + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
PUBLIC_KEY_RING_BY_LIKE_EMAIL);
@@ -189,8 +191,7 @@ public class KeychainProvider extends ContentProvider {
SECRET_KEY_RING_BY_EMAILS);
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_EMAILS,
- SECRET_KEY_RING_BY_EMAILS); // without emails
- // specified
+ SECRET_KEY_RING_BY_EMAILS); // without emails specified
matcher.addURI(authority, KeychainContract.BASE_KEY_RINGS + "/"
+ KeychainContract.PATH_SECRET + "/" + KeychainContract.PATH_BY_LIKE_EMAIL + "/*",
SECRET_KEY_RING_BY_LIKE_EMAIL);
@@ -226,13 +227,18 @@ public class KeychainProvider extends ContentProvider {
SECRET_KEY_RING_USER_ID_BY_ROW_ID);
/**
+ * Crypto Consumers
+ */
+ matcher.addURI(authority, KeychainContract.BASE_CRYPTO_CONSUMERS, CRYPTO_CONSUMERS);
+
+ /**
* data stream
*
* <pre>
* data / _
* </pre>
*/
- matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
+ // matcher.addURI(authority, KeychainContract.BASE_DATA + "/*", DATA_STREAM);
return matcher;
}
@@ -284,6 +290,9 @@ public class KeychainProvider extends ContentProvider {
case SECRET_KEY_RING_USER_ID_BY_ROW_ID:
return UserIds.CONTENT_ITEM_TYPE;
+ case CRYPTO_CONSUMERS:
+ return CryptoConsumers.CONTENT_TYPE;
+
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
@@ -591,6 +600,11 @@ public class KeychainProvider extends ContentProvider {
qb.appendWhereEscapeString(uri.getLastPathSegment());
break;
+
+ case CRYPTO_CONSUMERS:
+ qb.setTables(Tables.CRYPTO_CONSUMERS);
+
+ break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
@@ -869,16 +883,16 @@ public class KeychainProvider extends ContentProvider {
return BaseColumns._ID + "=" + rowId + andForeignKeyRing + andSelection;
}
- @Override
- public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- int match = mUriMatcher.match(uri);
- if (match != DATA_STREAM) {
- throw new FileNotFoundException();
- }
- String fileName = uri.getLastPathSegment();
- File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- }
+ // @Override
+ // public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ // int match = mUriMatcher.match(uri);
+ // if (match != DATA_STREAM) {
+ // throw new FileNotFoundException();
+ // }
+ // String fileName = uri.getLastPathSegment();
+ // File file = new File(getContext().getFilesDir().getAbsolutePath(), fileName);
+ // return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ // }
/**
* This broadcast is send system wide to inform other application that a keyring was inserted,
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 2665456ea..57d3b54d6 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -31,6 +31,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.helper.PgpConversionHelper;
import org.sufficientlysecure.keychain.helper.PgpHelper;
import org.sufficientlysecure.keychain.helper.PgpMain;
+import org.sufficientlysecure.keychain.provider.KeychainContract.CryptoConsumers;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
@@ -516,10 +517,13 @@ public class ProviderHelper {
* @return
*/
private static boolean getMasterKeyCanSign(Context context, Uri queryUri, long keyRingRowId) {
- String[] projection = new String[] { KeyRings.MASTER_KEY_ID, "(SELECT COUNT(sign_keys." +
- Keys._ID + ") FROM " + Tables.KEYS + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
- + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID + " AND sign_keys."
- + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY + " = 1) AS sign", };
+ String[] projection = new String[] {
+ KeyRings.MASTER_KEY_ID,
+ "(SELECT COUNT(sign_keys." + Keys._ID + ") FROM " + Tables.KEYS
+ + " AS sign_keys WHERE sign_keys." + Keys.KEY_RING_ROW_ID + " = "
+ + KeychainDatabase.Tables.KEY_RINGS + "." + KeyRings._ID
+ + " AND sign_keys." + Keys.CAN_SIGN + " = '1' AND " + Keys.IS_MASTER_KEY
+ + " = 1) AS sign", };
ContentResolver cr = context.getContentResolver();
Cursor cursor = cr.query(queryUri, projection, null, null, null);
@@ -713,4 +717,31 @@ public class ProviderHelper {
return cursor;
}
+
+ public static ArrayList<String> getCryptoConsumers(Context context) {
+ Cursor cursor = context.getContentResolver().query(CryptoConsumers.CONTENT_URI, null, null,
+ null, null);
+
+ ArrayList<String> packageNames = new ArrayList<String>();
+ if (cursor != null) {
+ int packageNameCol = cursor.getColumnIndex(CryptoConsumers.PACKAGE_NAME);
+ if (cursor.moveToFirst()) {
+ do {
+ packageNames.add(cursor.getString(packageNameCol));
+ } while (cursor.moveToNext());
+ }
+ }
+
+ if (cursor != null) {
+ cursor.close();
+ }
+
+ return packageNames;
+ }
+
+ public static void addCryptoConsumer(Context context, String packageName) {
+ ContentValues values = new ContentValues();
+ values.put(CryptoConsumers.PACKAGE_NAME, packageName);
+ context.getContentResolver().insert(CryptoConsumers.CONTENT_URI, values);
+ }
}
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
index eb1232769..f29514e79 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java
@@ -19,8 +19,12 @@ package org.sufficientlysecure.keychain.service;
import java.util.Date;
import java.util.HashMap;
+import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
+import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.Id;
import org.sufficientlysecure.keychain.helper.PgpHelper;
@@ -102,7 +106,14 @@ public class PassphraseCacheService extends Service {
// get cached passphrase
String cachedPassphrase = mPassphraseCache.get(masterKeyId);
if (cachedPassphrase == null) {
- return null;
+ // check if secret key has a passphrase
+ if (!hasPassphrase(context, masterKeyId)) {
+ // cache empty passphrase
+ addCachedPassphrase(context, masterKeyId, "");
+ return "";
+ } else {
+ return null;
+ }
}
// set it again to reset the cache life cycle
Log.d(TAG, "Cache passphrase again when getting it!");
@@ -112,6 +123,37 @@ public class PassphraseCacheService extends Service {
}
/**
+ * Checks if key has a passphrase.
+ *
+ * @param secretKeyId
+ * @return true if it has a passphrase
+ */
+ public static boolean hasPassphrase(Context context, long secretKeyId) {
+ // check if the key has no passphrase
+ try {
+ PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
+ .getPGPSecretKeyRingByKeyId(context, secretKeyId));
+
+ Log.d(Constants.TAG, "Check if key has no passphrase...");
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ "SC").build("".toCharArray());
+ PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
+ if (testKey != null) {
+ Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
+
+ // cache empty passphrase
+ PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
+
+ return false;
+ }
+ } catch (PGPException e) {
+ // silently catch
+ }
+
+ return true;
+ }
+
+ /**
* Register BroadcastReceiver that is unregistered when service is destroyed. This
* BroadcastReceiver hears on intents with ACTION_PASSPHRASE_CACHE_SERVICE to then timeout
* specific passphrases in memory.
diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
index 10fc74551..9e42b1f4e 100644
--- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
+++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java
@@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.ui.dialog;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
import org.spongycastle.openpgp.PGPSecretKey;
-import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
@@ -44,7 +43,6 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
-
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -80,7 +78,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
long secretKeyId) throws PgpGeneralException {
// check if secret key has a passphrase
if (!(secretKeyId == Id.key.symmetric || secretKeyId == Id.key.none)) {
- if (!hasPassphrase(context, secretKeyId)) {
+ if (!PassphraseCacheService.hasPassphrase(context, secretKeyId)) {
throw new PgpMain.PgpGeneralException("No passphrase! No passphrase dialog needed!");
}
}
@@ -95,39 +93,6 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
return frag;
}
- /**
- * Checks if key has a passphrase
- *
- * @param secretKeyId
- * @return true if it has a passphrase
- */
- private static boolean hasPassphrase(Context context, long secretKeyId) {
- // check if the key has no passphrase
- try {
- PGPSecretKey secretKey = PgpHelper.getMasterKey(ProviderHelper
- .getPGPSecretKeyRingByKeyId(context, secretKeyId));
- // PGPSecretKey secretKey =
- // PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
-
- Log.d(Constants.TAG, "Check if key has no passphrase...");
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
- "SC").build("".toCharArray());
- PGPPrivateKey testKey = secretKey.extractPrivateKey(keyDecryptor);
- if (testKey != null) {
- Log.d(Constants.TAG, "Key has no passphrase! Caches empty passphrase!");
-
- // cache empty passphrase
- PassphraseCacheService.addCachedPassphrase(context, secretKey.getKeyID(), "");
-
- return false;
- }
- } catch (PGPException e) {
- // silently catch
- }
-
- return true;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -153,7 +118,8 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
alert.setMessage(R.string.passPhraseForSymmetricEncryption);
} else {
// TODO: by master key id???
- secretKey = PgpHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity, secretKeyId));
+ secretKey = PgpHelper.getMasterKey(ProviderHelper.getPGPSecretKeyRingByKeyId(activity,
+ secretKeyId));
// secretKey = PGPHelper.getMasterKey(PGPMain.getSecretKeyRing(secretKeyId));
if (secretKey == null) {
@@ -181,7 +147,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
mPassphraseEditText = (EditText) view.findViewById(R.id.passphrase_passphrase);
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-
+
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
@@ -189,38 +155,42 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
boolean keyOK = true;
String passPhrase = mPassphraseEditText.getText().toString();
long keyId;
- PGPSecretKey clickSecretKey = secretKey;
+ PGPSecretKey clickSecretKey = secretKey;
if (clickSecretKey != null) {
while (keyOK == true) {
- if (clickSecretKey != null) { //check again for loop
+ if (clickSecretKey != null) { // check again for loop
try {
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
.setProvider(PgpMain.BOUNCY_CASTLE_PROVIDER_NAME).build(
passPhrase.toCharArray());
- PGPPrivateKey testKey = clickSecretKey.extractPrivateKey(keyDecryptor);
+ PGPPrivateKey testKey = clickSecretKey
+ .extractPrivateKey(keyDecryptor);
if (testKey == null) {
if (!clickSecretKey.isMasterKey()) {
- Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
+ Toast.makeText(activity,
+ R.string.error_couldNotExtractPrivateKey,
Toast.LENGTH_SHORT).show();
return;
} else {
- clickSecretKey = PgpHelper.getKeyNum(ProviderHelper.getPGPSecretKeyRingByKeyId(activity, secretKeyId), curKeyIndex);
- curKeyIndex++; //does post-increment work like C?
+ clickSecretKey = PgpHelper.getKeyNum(ProviderHelper
+ .getPGPSecretKeyRingByKeyId(activity, secretKeyId),
+ curKeyIndex);
+ curKeyIndex++; // does post-increment work like C?
continue;
}
} else {
keyOK = false;
}
} catch (PGPException e) {
- Toast.makeText(activity, R.string.wrongPassPhrase, Toast.LENGTH_SHORT)
- .show();
+ Toast.makeText(activity, R.string.wrongPassPhrase,
+ Toast.LENGTH_SHORT).show();
return;
}
} else {
Toast.makeText(activity, R.string.error_couldNotExtractPrivateKey,
Toast.LENGTH_SHORT).show();
- return; //ran out of keys to try
+ return; // ran out of keys to try
}
}
keyId = secretKey.getKeyID();
@@ -232,7 +202,8 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
Log.d(Constants.TAG, "Everything okay! Caching entered passphrase");
PassphraseCacheService.addCachedPassphrase(activity, keyId, passPhrase);
if (keyOK == false && clickSecretKey.getKeyID() != keyId) {
- PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(), passPhrase);
+ PassphraseCacheService.addCachedPassphrase(activity, clickSecretKey.getKeyID(),
+ passPhrase);
}
sendMessageToHandler(MESSAGE_OKAY);
@@ -240,7 +211,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
});
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-
+
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
@@ -255,7 +226,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
public void onActivityCreated(Bundle arg0) {
super.onActivityCreated(arg0);
if (canKB) {
- // request focus and open soft keyboard
+ // request focus and open soft keyboard
mPassphraseEditText.requestFocus();
getDialog().getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
@@ -298,4 +269,3 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
}
}
-