diff options
Diffstat (limited to 'OpenPGP-Keychain/src')
19 files changed, 1117 insertions, 200 deletions
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..1a57a457d --- /dev/null +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/crypto_provider/CryptoService.java @@ -0,0 +1,196 @@ +/* + * 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 org.sufficientlysecure.keychain.crypto_provider; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.helper.PgpMain; +import org.sufficientlysecure.keychain.util.InputData; +import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.service.KeychainIntentService; +import org.sufficientlysecure.keychain.service.PassphraseCacheService; + +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, "CryptoService, onCreate()"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(Constants.TAG, "CryptoService, onDestroy()"); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private synchronized void decryptAndVerifySafe(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(); + + long secretKeyId = PgpMain.getDecryptionKeyId(mContext, inputStream); + if (secretKeyId == Id.key.none) { + throw new PgpMain.PgpGeneralException(getString(R.string.error_noSecretKeyFound)); + } + + Log.d(Constants.TAG, "Got input:\n"+new String(inputBytes)); + + Log.d(Constants.TAG, "secretKeyId " + secretKeyId); + + String passphrase = PassphraseCacheService.getCachedPassphrase(mContext, secretKeyId); + + if (passphrase == null) { + Log.d(Constants.TAG, "No passphrase! Activity required!"); + // 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); + } catch (Exception e) { + Log.e(Constants.TAG, "KeychainService, Exception!", e); + + try { + callback.onError(new CryptoError(0, 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 { + decryptAndVerifySafe(inputBytes, callback); + } + + }; + + // /** + // * 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/KeychainIntentService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 1d48278eb..711cdae24 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -56,7 +56,6 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; - /** * This Service contains all important long lasting operations for APG. It receives Intents with * data from the activities or other apps, queues these intents, executes them, and stops itself @@ -541,11 +540,12 @@ public class KeychainIntentService extends IntentService implements ProgressDial /* Operation */ if (!canSign) { - PgpMain.changeSecretKeyPassphrase(this, ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId), - oldPassPhrase, newPassPhrase, this); + PgpMain.changeSecretKeyPassphrase(this, + ProviderHelper.getPGPSecretKeyRingByKeyId(this, masterKeyId), + oldPassPhrase, newPassPhrase, this); } else { - PgpMain.buildSecretKey(this, userIds, keys, keysUsages, masterKeyId, oldPassPhrase, - newPassPhrase, this); + PgpMain.buildSecretKey(this, userIds, keys, keysUsages, masterKeyId, + oldPassPhrase, newPassPhrase, this); } PassphraseCacheService.addCachedPassphrase(this, masterKeyId, newPassPhrase); diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index eb1232769..d11b8e92a 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Dominik Schürmann <dominik@dominikschuermann.de> + * Copyright (C) 2012-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. @@ -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; @@ -35,27 +39,39 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; 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.util.Log; public class PassphraseCacheService extends Service { public static final String TAG = Constants.TAG + ": PassphraseCacheService"; + public static final String ACTION_PASSPHRASE_CACHE_ADD = Constants.INTENT_PREFIX + + "PASSPHRASE_CACHE_ADD"; + public static final String ACTION_PASSPHRASE_CACHE_GET = Constants.INTENT_PREFIX + + "PASSPHRASE_CACHE_GET"; + public static final String BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE = Constants.INTENT_PREFIX - + "PASSPHRASE_CACHE_SERVICE"; + + "PASSPHRASE_CACHE_BROADCAST"; public static final String EXTRA_TTL = "ttl"; public static final String EXTRA_KEY_ID = "keyId"; public static final String EXTRA_PASSPHRASE = "passphrase"; + public static final String EXTRA_MESSENGER = "messenger"; private static final int REQUEST_ID = 0; private static final long DEFAULT_TTL = 15; private BroadcastReceiver mIntentReceiver; - // This is static to be easily retrieved by getCachedPassphrase() without the need of callback - // functions - private static HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>(); + private HashMap<Long, String> mPassphraseCache = new HashMap<Long, String>(); + + Context mContext; /** * This caches a new passphrase by sending a new command to the service. An android service is @@ -70,6 +86,7 @@ public class PassphraseCacheService extends Service { Log.d(TAG, "cacheNewPassphrase() for " + keyId); Intent intent = new Intent(context, PassphraseCacheService.class); + intent.setAction(ACTION_PASSPHRASE_CACHE_ADD); intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassPhraseCacheTtl()); intent.putExtra(EXTRA_PASSPHRASE, passphrase); intent.putExtra(EXTRA_KEY_ID, keyId); @@ -78,17 +95,65 @@ public class PassphraseCacheService extends Service { } /** - * Gets a cached passphrase from memory + * Gets a cached passphrase from memory, blocking method * * @param context * @param keyId * @return */ public static String getCachedPassphrase(Context context, long keyId) { + Log.d(TAG, "getCachedPassphrase() get masterKeyId for " + keyId); + Intent intent = new Intent(context, PassphraseCacheService.class); + intent.setAction(ACTION_PASSPHRASE_CACHE_GET); + + final Object mutex = new Object(); + final Bundle returnBundle = new Bundle(); + + HandlerThread handlerThread = new HandlerThread("getPassphrase"); + handlerThread.start(); + Handler returnHandler = new Handler(handlerThread.getLooper()) { + @Override + public void handleMessage(Message message) { + if (message.obj != null) { + String passphrase = ((Bundle) message.obj).getString(EXTRA_PASSPHRASE); + returnBundle.putString(EXTRA_PASSPHRASE, passphrase); + } + synchronized (mutex) { + mutex.notify(); + } + getLooper().quit(); + } + }; + + // Create a new Messenger for the communication back + Messenger messenger = new Messenger(returnHandler); + intent.putExtra(EXTRA_KEY_ID, keyId); + intent.putExtra(EXTRA_MESSENGER, messenger); + // send intent to this service + context.startService(intent); + + // Wait on mutex until passphrase is returned to handlerThread + synchronized (mutex) { + try { + mutex.wait(3000); + } catch (InterruptedException e) { + } + } + + if (returnBundle.containsKey(EXTRA_PASSPHRASE)) { + return returnBundle.getString(EXTRA_PASSPHRASE); + } else { + return null; + } + } + + private String getCachedPassphraseImpl(long keyId) { + Log.d(TAG, "getCachedPassphraseImpl() get masterKeyId for " + keyId); + // try to get master key id which is used as an identifier for cached passphrases long masterKeyId = keyId; if (masterKeyId != Id.key.symmetric) { - PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(context, keyId); + PGPSecretKeyRing keyRing = ProviderHelper.getPGPSecretKeyRingByKeyId(this, keyId); if (keyRing == null) { return null; } @@ -98,20 +163,60 @@ public class PassphraseCacheService extends Service { } masterKeyId = masterKey.getKeyID(); } + Log.d(TAG, "getCachedPassphraseImpl() for masterKeyId" + masterKeyId); // get cached passphrase String cachedPassphrase = mPassphraseCache.get(masterKeyId); if (cachedPassphrase == null) { + // TODO: fix! + // 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!"); - addCachedPassphrase(context, masterKeyId, cachedPassphrase); + addCachedPassphrase(this, masterKeyId, cachedPassphrase); return cachedPassphrase; } /** + * 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. @@ -154,11 +259,6 @@ public class PassphraseCacheService extends Service { return sender; } - @Override - public void onCreate() { - Log.d(TAG, "onCreate()"); - } - /** * Executed when service is started by intent */ @@ -169,20 +269,41 @@ public class PassphraseCacheService extends Service { // register broadcastreceiver registerReceiver(); - if (intent != null) { - long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL); - long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); - String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE); - - Log.d(TAG, "Received intent in onStartCommand() with keyId: " + keyId + ", ttl: " + ttl); - - // add keyId and passphrase to memory - mPassphraseCache.put(keyId, passphrase); - - // register new alarm with keyId for this passphrase - long triggerTime = new Date().getTime() + (ttl * 1000); - AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); - am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId)); + if (intent != null && intent.getAction() != null) { + if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) { + long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL); + long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE); + + Log.d(TAG, + "Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: " + + keyId + ", ttl: " + ttl); + + // add keyId and passphrase to memory + mPassphraseCache.put(keyId, passphrase); + + // register new alarm with keyId for this passphrase + long triggerTime = new Date().getTime() + (ttl * 1000); + AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); + am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId)); + } else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) { + long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER); + + String passphrase = getCachedPassphraseImpl(keyId); + + Message msg = Message.obtain(); + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_PASSPHRASE, passphrase); + msg.obj = bundle; + try { + messenger.send(msg); + } catch (RemoteException e) { + Log.e(Constants.TAG, "Sending message failed", e); + } + } else { + Log.e(Constants.TAG, "Intent or Intent Action not supported!"); + } } return START_STICKY; @@ -198,7 +319,7 @@ public class PassphraseCacheService extends Service { // remove passphrase corresponding to keyId from memory mPassphraseCache.remove(keyId); - Log.d(TAG, "Timeout of " + keyId + ", removed from memory!"); + Log.d(TAG, "Timeout of keyId " + keyId + ", removed from memory!"); // stop whole service if no cached passphrases remaining if (mPassphraseCache.isEmpty()) { @@ -208,12 +329,25 @@ public class PassphraseCacheService extends Service { } @Override + public void onCreate() { + super.onCreate(); + mContext = this; + Log.d(Constants.TAG, "PassphraseCacheService, onCreate()"); + } + + @Override public void onDestroy() { - Log.d(TAG, "onDestroy()"); + super.onDestroy(); + Log.d(Constants.TAG, "PassphraseCacheService, onDestroy()"); unregisterReceiver(mIntentReceiver); } + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + public class PassphraseCacheBinder extends Binder { public PassphraseCacheService getService() { return PassphraseCacheService.this; @@ -222,9 +356,4 @@ public class PassphraseCacheService extends Service { private final IBinder mBinder = new PassphraseCacheBinder(); - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - }
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java index 1e7fe0120..06d59b9a2 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/SignKeyActivity.java @@ -199,7 +199,8 @@ public class SignKeyActivity extends SherlockFragmentActivity { // Send all information needed to service to sign key in other thread Intent intent = new Intent(this, KeychainIntentService.class); - intent.putExtra(KeychainIntentService.EXTRA_ACTION, KeychainIntentService.ACTION_SIGN_KEYRING); + intent.putExtra(KeychainIntentService.EXTRA_ACTION, + KeychainIntentService.ACTION_SIGN_KEYRING); // fill values for this action Bundle data = new Bundle(); @@ -210,8 +211,8 @@ public class SignKeyActivity extends SherlockFragmentActivity { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after signing is done in ApgService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, R.string.progress_signing, - ProgressDialog.STYLE_SPINNER) { + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, + R.string.progress_signing, ProgressDialog.STYLE_SPINNER) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -250,7 +251,8 @@ public class SignKeyActivity extends SherlockFragmentActivity { // Send all information needed to service to upload key in other thread Intent intent = new Intent(this, KeychainIntentService.class); - intent.putExtra(KeychainIntentService.EXTRA_ACTION, KeychainIntentService.ACTION_UPLOAD_KEYRING); + intent.putExtra(KeychainIntentService.EXTRA_ACTION, + KeychainIntentService.ACTION_UPLOAD_KEYRING); // fill values for this action Bundle data = new Bundle(); @@ -264,8 +266,8 @@ public class SignKeyActivity extends SherlockFragmentActivity { intent.putExtra(KeychainIntentService.EXTRA_DATA, data); // Message is received after uploading is done in ApgService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, R.string.progress_exporting, - ProgressDialog.STYLE_HORIZONTAL) { + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(this, + R.string.progress_exporting, ProgressDialog.STYLE_HORIZONTAL) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); 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 } } - diff --git a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java index 898b05372..6d94889cf 100644 --- a/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java +++ b/OpenPGP-Keychain/src/org/sufficientlysecure/keychain/ui/widget/SectionView.java @@ -103,11 +103,11 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor } public void setCanEdit(boolean bCanEdit) { - canEdit = bCanEdit; - mPlusButton = (ImageView)findViewById(R.id.plusbutton); - if (!canEdit) { - mPlusButton.setVisibility(View.INVISIBLE); - } + canEdit = bCanEdit; + mPlusButton = (ImageView) findViewById(R.id.plusbutton); + if (!canEdit) { + mPlusButton.setVisibility(View.INVISIBLE); + } } /** {@inheritDoc} */ @@ -141,82 +141,85 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor /** {@inheritDoc} */ public void onClick(View v) { if (canEdit) { - switch (mType) { - case Id.type.user_id: { - UserIdEditor view = (UserIdEditor) mInflater.inflate(R.layout.edit_key_user_id_item, - mEditors, false); - view.setEditorListener(this); - if (mEditors.getChildCount() == 0) { - view.setIsMainUserId(true); + switch (mType) { + case Id.type.user_id: { + UserIdEditor view = (UserIdEditor) mInflater.inflate( + R.layout.edit_key_user_id_item, mEditors, false); + view.setEditorListener(this); + if (mEditors.getChildCount() == 0) { + view.setIsMainUserId(true); + } + mEditors.addView(view); + break; } - mEditors.addView(view); - break; - } - case Id.type.key: { - AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); - - View view = mInflater.inflate(R.layout.create_key, null); - dialog.setView(view); - dialog.setTitle(R.string.title_createKey); + case Id.type.key: { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); - boolean wouldBeMasterKey = (mEditors.getChildCount() == 0); + View view = mInflater.inflate(R.layout.create_key, null); + dialog.setView(view); + dialog.setTitle(R.string.title_createKey); - final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); - Vector<Choice> choices = new Vector<Choice>(); - choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString(R.string.dsa))); - if (!wouldBeMasterKey) { - choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString( - R.string.elgamal))); - } + boolean wouldBeMasterKey = (mEditors.getChildCount() == 0); - choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString(R.string.rsa))); - - ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(), - android.R.layout.simple_spinner_item, choices); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - algorithm.setAdapter(adapter); - // make RSA the default - for (int i = 0; i < choices.size(); ++i) { - if (choices.get(i).getId() == Id.choice.algorithm.rsa) { - algorithm.setSelection(i); - break; + final Spinner algorithm = (Spinner) view.findViewById(R.id.create_key_algorithm); + Vector<Choice> choices = new Vector<Choice>(); + choices.add(new Choice(Id.choice.algorithm.dsa, getResources().getString( + R.string.dsa))); + if (!wouldBeMasterKey) { + choices.add(new Choice(Id.choice.algorithm.elgamal, getResources().getString( + R.string.elgamal))); } - } - - final EditText keySize = (EditText) view.findViewById(R.id.create_key_size); - dialog.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface di, int id) { - di.dismiss(); - try { - mNewKeySize = Integer.parseInt("" + keySize.getText()); - } catch (NumberFormatException e) { - mNewKeySize = 0; + choices.add(new Choice(Id.choice.algorithm.rsa, getResources().getString( + R.string.rsa))); + + ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(getContext(), + android.R.layout.simple_spinner_item, choices); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + algorithm.setAdapter(adapter); + // make RSA the default + for (int i = 0; i < choices.size(); ++i) { + if (choices.get(i).getId() == Id.choice.algorithm.rsa) { + algorithm.setSelection(i); + break; } - - mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem(); - createKey(); } - }); - - dialog.setCancelable(true); - dialog.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface di, int id) { - di.dismiss(); - } - }); - dialog.create().show(); - break; - } + final EditText keySize = (EditText) view.findViewById(R.id.create_key_size); + + dialog.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface di, int id) { + di.dismiss(); + try { + mNewKeySize = Integer.parseInt("" + keySize.getText()); + } catch (NumberFormatException e) { + mNewKeySize = 0; + } + + mNewKeyAlgorithmChoice = (Choice) algorithm.getSelectedItem(); + createKey(); + } + }); + + dialog.setCancelable(true); + dialog.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface di, int id) { + di.dismiss(); + } + }); + + dialog.create().show(); + break; + } - default: { - break; - } - } - this.updateEditorsVisible(); + default: { + break; + } + } + this.updateEditorsVisible(); } } @@ -266,7 +269,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor // Send all information needed to service to edit key in other thread Intent intent = new Intent(mActivity, KeychainIntentService.class); - intent.putExtra(KeychainIntentService.EXTRA_ACTION, KeychainIntentService.ACTION_GENERATE_KEY); + intent.putExtra(KeychainIntentService.EXTRA_ACTION, + KeychainIntentService.ACTION_GENERATE_KEY); // fill values for this action Bundle data = new Bundle(); @@ -293,7 +297,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor ProgressDialog.STYLE_SPINNER); // Message is received after generating is done in ApgService - KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, mGeneratingDialog) { + KeychainIntentServiceHandler saveHandler = new KeychainIntentServiceHandler(mActivity, + mGeneratingDialog) { public void handleMessage(Message message) { // handle messages by standard ApgHandler first super.handleMessage(message); @@ -302,7 +307,8 @@ public class SectionView extends LinearLayout implements OnClickListener, Editor // get new key from data bundle returned from service Bundle data = message.getData(); PGPSecretKeyRing newKeyRing = (PGPSecretKeyRing) PgpConversionHelper - .BytesToPGPKeyRing(data.getByteArray(KeychainIntentService.RESULT_NEW_KEY)); + .BytesToPGPKeyRing(data + .getByteArray(KeychainIntentService.RESULT_NEW_KEY)); boolean isMasterKey = (mEditors.getChildCount() == 0); |